diff options
448 files changed, 16529 insertions, 2602 deletions
diff --git a/Android.bp b/Android.bp index 979ed1cbf06c..3754c81f9e04 100644 --- a/Android.bp +++ b/Android.bp @@ -215,7 +215,6 @@ java_library { "core/java/android/os/IDeviceIdleController.aidl", "core/java/android/os/IHardwarePropertiesManager.aidl", "core/java/android/os/IIncidentManager.aidl", - "core/java/android/os/IIncidentReportCompletedListener.aidl", "core/java/android/os/IIncidentReportStatusListener.aidl", "core/java/android/os/IMaintenanceActivityListener.aidl", "core/java/android/os/IMessenger.aidl", @@ -671,6 +670,7 @@ java_library { "libphonenumber-platform", "nist-sip", "tagsoup", + "rappor", ], dxflags: ["--core-library"], } diff --git a/Android.mk b/Android.mk index 103536297838..00f877a2ecfb 100644 --- a/Android.mk +++ b/Android.mk @@ -274,6 +274,9 @@ non_base_dirs := \ ../opt/net/voip/src/java/android/net/rtp \ ../opt/net/voip/src/java/android/net/sip \ +framework_base_android_test_base_src_files := \ + $(call all-java-files-under, test-base/src/junit) + framework_base_android_test_mock_src_files := \ $(call all-java-files-under, test-mock/src/android/test/mock) @@ -284,7 +287,6 @@ framework_base_android_test_runner_src_files := \ # to document and check apis files_to_check_apis := \ $(call find-other-java-files, \ - test-base/src \ $(non_base_dirs) \ ) @@ -308,6 +310,7 @@ files_to_check_apis_generated := \ files_to_document := \ $(files_to_check_apis) \ $(call find-other-java-files,\ + test-base/src \ test-runner/src) # These are relative to frameworks/base @@ -327,6 +330,7 @@ framework_docs_LOCAL_SRC_FILES := \ # These are relative to frameworks/base framework_docs_LOCAL_API_CHECK_SRC_FILES := \ + $(framework_base_android_test_base_src_files) \ $(framework_base_android_test_mock_src_files) \ $(framework_base_android_test_runner_src_files) \ $(files_to_check_apis) \ diff --git a/api/current.txt b/api/current.txt index 1bc38d402cbc..c4f9e4a38c74 100644 --- a/api/current.txt +++ b/api/current.txt @@ -3953,7 +3953,7 @@ package android.app { field public static final android.os.Parcelable.Creator<android.app.ActivityManager.RunningAppProcessInfo> CREATOR; field public static final deprecated int IMPORTANCE_BACKGROUND = 400; // 0x190 field public static final int IMPORTANCE_CACHED = 400; // 0x190 - field public static final int IMPORTANCE_CANT_SAVE_STATE = 270; // 0x10e + field public static final int IMPORTANCE_CANT_SAVE_STATE = 350; // 0x15e field public static final deprecated int IMPORTANCE_EMPTY = 500; // 0x1f4 field public static final int IMPORTANCE_FOREGROUND = 100; // 0x64 field public static final int IMPORTANCE_FOREGROUND_SERVICE = 125; // 0x7d @@ -16643,6 +16643,17 @@ package android.icu.lang { field public static final int KNOTTED_HEH = 21; // 0x15 field public static final int LAM = 22; // 0x16 field public static final int LAMADH = 23; // 0x17 + field public static final int MALAYALAM_BHA = 89; // 0x59 + field public static final int MALAYALAM_JA = 90; // 0x5a + field public static final int MALAYALAM_LLA = 91; // 0x5b + field public static final int MALAYALAM_LLLA = 92; // 0x5c + field public static final int MALAYALAM_NGA = 93; // 0x5d + field public static final int MALAYALAM_NNA = 94; // 0x5e + field public static final int MALAYALAM_NNNA = 95; // 0x5f + field public static final int MALAYALAM_NYA = 96; // 0x60 + field public static final int MALAYALAM_RA = 97; // 0x61 + field public static final int MALAYALAM_SSA = 98; // 0x62 + field public static final int MALAYALAM_TTA = 99; // 0x63 field public static final int MANICHAEAN_ALEPH = 58; // 0x3a field public static final int MANICHAEAN_AYIN = 59; // 0x3b field public static final int MANICHAEAN_BETH = 60; // 0x3c @@ -16898,6 +16909,8 @@ package android.icu.lang { field public static final int CJK_UNIFIED_IDEOGRAPHS_EXTENSION_D_ID = 209; // 0xd1 field public static final android.icu.lang.UCharacter.UnicodeBlock CJK_UNIFIED_IDEOGRAPHS_EXTENSION_E; field public static final int CJK_UNIFIED_IDEOGRAPHS_EXTENSION_E_ID = 256; // 0x100 + field public static final android.icu.lang.UCharacter.UnicodeBlock CJK_UNIFIED_IDEOGRAPHS_EXTENSION_F; + field public static final int CJK_UNIFIED_IDEOGRAPHS_EXTENSION_F_ID = 274; // 0x112 field public static final int CJK_UNIFIED_IDEOGRAPHS_ID = 71; // 0x47 field public static final android.icu.lang.UCharacter.UnicodeBlock COMBINING_DIACRITICAL_MARKS; field public static final android.icu.lang.UCharacter.UnicodeBlock COMBINING_DIACRITICAL_MARKS_EXTENDED; @@ -17043,6 +17056,8 @@ package android.icu.lang { field public static final int JAVANESE_ID = 181; // 0xb5 field public static final android.icu.lang.UCharacter.UnicodeBlock KAITHI; field public static final int KAITHI_ID = 193; // 0xc1 + field public static final android.icu.lang.UCharacter.UnicodeBlock KANA_EXTENDED_A; + field public static final int KANA_EXTENDED_A_ID = 275; // 0x113 field public static final android.icu.lang.UCharacter.UnicodeBlock KANA_SUPPLEMENT; field public static final int KANA_SUPPLEMENT_ID = 203; // 0xcb field public static final android.icu.lang.UCharacter.UnicodeBlock KANBUN; @@ -17115,6 +17130,8 @@ package android.icu.lang { field public static final int MANICHAEAN_ID = 234; // 0xea field public static final android.icu.lang.UCharacter.UnicodeBlock MARCHEN; field public static final int MARCHEN_ID = 268; // 0x10c + field public static final android.icu.lang.UCharacter.UnicodeBlock MASARAM_GONDI; + field public static final int MASARAM_GONDI_ID = 276; // 0x114 field public static final android.icu.lang.UCharacter.UnicodeBlock MATHEMATICAL_ALPHANUMERIC_SYMBOLS; field public static final int MATHEMATICAL_ALPHANUMERIC_SYMBOLS_ID = 93; // 0x5d field public static final android.icu.lang.UCharacter.UnicodeBlock MATHEMATICAL_OPERATORS; @@ -17174,6 +17191,8 @@ package android.icu.lang { field public static final android.icu.lang.UCharacter.UnicodeBlock NO_BLOCK; field public static final android.icu.lang.UCharacter.UnicodeBlock NUMBER_FORMS; field public static final int NUMBER_FORMS_ID = 45; // 0x2d + field public static final android.icu.lang.UCharacter.UnicodeBlock NUSHU; + field public static final int NUSHU_ID = 277; // 0x115 field public static final android.icu.lang.UCharacter.UnicodeBlock OGHAM; field public static final int OGHAM_ID = 34; // 0x22 field public static final android.icu.lang.UCharacter.UnicodeBlock OLD_HUNGARIAN; @@ -17252,6 +17271,8 @@ package android.icu.lang { field public static final int SMALL_FORM_VARIANTS_ID = 84; // 0x54 field public static final android.icu.lang.UCharacter.UnicodeBlock SORA_SOMPENG; field public static final int SORA_SOMPENG_ID = 218; // 0xda + field public static final android.icu.lang.UCharacter.UnicodeBlock SOYOMBO; + field public static final int SOYOMBO_ID = 278; // 0x116 field public static final android.icu.lang.UCharacter.UnicodeBlock SPACING_MODIFIER_LETTERS; field public static final int SPACING_MODIFIER_LETTERS_ID = 6; // 0x6 field public static final android.icu.lang.UCharacter.UnicodeBlock SPECIALS; @@ -17284,6 +17305,8 @@ package android.icu.lang { field public static final int SYLOTI_NAGRI_ID = 143; // 0x8f field public static final android.icu.lang.UCharacter.UnicodeBlock SYRIAC; field public static final int SYRIAC_ID = 13; // 0xd + field public static final android.icu.lang.UCharacter.UnicodeBlock SYRIAC_SUPPLEMENT; + field public static final int SYRIAC_SUPPLEMENT_ID = 279; // 0x117 field public static final android.icu.lang.UCharacter.UnicodeBlock TAGALOG; field public static final int TAGALOG_ID = 98; // 0x62 field public static final android.icu.lang.UCharacter.UnicodeBlock TAGBANWA; @@ -17344,6 +17367,8 @@ package android.icu.lang { field public static final int YI_RADICALS_ID = 73; // 0x49 field public static final android.icu.lang.UCharacter.UnicodeBlock YI_SYLLABLES; field public static final int YI_SYLLABLES_ID = 72; // 0x48 + field public static final android.icu.lang.UCharacter.UnicodeBlock ZANABAZAR_SQUARE; + field public static final int ZANABAZAR_SQUARE_ID = 280; // 0x118 } public static abstract interface UCharacter.WordBreak { @@ -17494,6 +17519,11 @@ package android.icu.lang { field public static final int DIACRITIC = 7; // 0x7 field public static final int DOUBLE_START = 12288; // 0x3000 field public static final int EAST_ASIAN_WIDTH = 4100; // 0x1004 + field public static final int EMOJI = 57; // 0x39 + field public static final int EMOJI_COMPONENT = 61; // 0x3d + field public static final int EMOJI_MODIFIER = 59; // 0x3b + field public static final int EMOJI_MODIFIER_BASE = 60; // 0x3c + field public static final int EMOJI_PRESENTATION = 58; // 0x3a field public static final int EXTENDER = 8; // 0x8 field public static final int FULL_COMPOSITION_EXCLUSION = 9; // 0x9 field public static final int GENERAL_CATEGORY = 4101; // 0x1005 @@ -17541,8 +17571,10 @@ package android.icu.lang { field public static final int POSIX_GRAPH = 46; // 0x2e field public static final int POSIX_PRINT = 47; // 0x2f field public static final int POSIX_XDIGIT = 48; // 0x30 + field public static final int PREPENDED_CONCATENATION_MARK = 63; // 0x3f field public static final int QUOTATION_MARK = 25; // 0x19 field public static final int RADICAL = 26; // 0x1a + field public static final int REGIONAL_INDICATOR = 62; // 0x3e field public static final int SCRIPT = 4106; // 0x100a field public static final int SCRIPT_EXTENSIONS = 28672; // 0x7000 field public static final int SEGMENT_STARTER = 41; // 0x29 @@ -17684,6 +17716,7 @@ package android.icu.lang { field public static final int MANDAIC = 84; // 0x54 field public static final int MANICHAEAN = 121; // 0x79 field public static final int MARCHEN = 169; // 0xa9 + field public static final int MASARAM_GONDI = 175; // 0xaf field public static final int MATHEMATICAL_NOTATION = 128; // 0x80 field public static final int MAYAN_HIEROGLYPHS = 85; // 0x55 field public static final int MEITEI_MAYEK = 115; // 0x73 @@ -17738,6 +17771,7 @@ package android.icu.lang { field public static final int SINDHI = 145; // 0x91 field public static final int SINHALA = 33; // 0x21 field public static final int SORA_SOMPENG = 152; // 0x98 + field public static final int SOYOMBO = 176; // 0xb0 field public static final int SUNDANESE = 113; // 0x71 field public static final int SYLOTI_NAGRI = 58; // 0x3a field public static final int SYMBOLS = 129; // 0x81 @@ -17768,6 +17802,7 @@ package android.icu.lang { field public static final int WESTERN_SYRIAC = 96; // 0x60 field public static final int WOLEAI = 155; // 0x9b field public static final int YI = 41; // 0x29 + field public static final int ZANABAZAR_SQUARE = 177; // 0xb1 } public static final class UScript.ScriptUsage extends java.lang.Enum { @@ -18562,11 +18597,14 @@ package android.icu.text { method public android.icu.util.Currency getCurrency(); method public java.lang.String getCurrencySymbol(); method public char getDecimalSeparator(); + method public java.lang.String getDecimalSeparatorString(); method public char getDigit(); + method public java.lang.String[] getDigitStrings(); method public char[] getDigits(); method public java.lang.String getExponentMultiplicationSign(); method public java.lang.String getExponentSeparator(); method public char getGroupingSeparator(); + method public java.lang.String getGroupingSeparatorString(); method public java.lang.String getInfinity(); method public static android.icu.text.DecimalFormatSymbols getInstance(); method public static android.icu.text.DecimalFormatSymbols getInstance(java.util.Locale); @@ -18574,37 +18612,52 @@ package android.icu.text { method public java.lang.String getInternationalCurrencySymbol(); method public java.util.Locale getLocale(); method public char getMinusSign(); + method public java.lang.String getMinusSignString(); method public char getMonetaryDecimalSeparator(); + method public java.lang.String getMonetaryDecimalSeparatorString(); method public char getMonetaryGroupingSeparator(); + method public java.lang.String getMonetaryGroupingSeparatorString(); method public java.lang.String getNaN(); method public char getPadEscape(); method public java.lang.String getPatternForCurrencySpacing(int, boolean); method public char getPatternSeparator(); method public char getPerMill(); + method public java.lang.String getPerMillString(); method public char getPercent(); + method public java.lang.String getPercentString(); method public char getPlusSign(); + method public java.lang.String getPlusSignString(); method public char getSignificantDigit(); method public android.icu.util.ULocale getULocale(); method public char getZeroDigit(); method public void setCurrency(android.icu.util.Currency); method public void setCurrencySymbol(java.lang.String); method public void setDecimalSeparator(char); + method public void setDecimalSeparatorString(java.lang.String); method public void setDigit(char); + method public void setDigitStrings(java.lang.String[]); method public void setExponentMultiplicationSign(java.lang.String); method public void setExponentSeparator(java.lang.String); method public void setGroupingSeparator(char); + method public void setGroupingSeparatorString(java.lang.String); method public void setInfinity(java.lang.String); method public void setInternationalCurrencySymbol(java.lang.String); method public void setMinusSign(char); + method public void setMinusSignString(java.lang.String); method public void setMonetaryDecimalSeparator(char); + method public void setMonetaryDecimalSeparatorString(java.lang.String); method public void setMonetaryGroupingSeparator(char); + method public void setMonetaryGroupingSeparatorString(java.lang.String); method public void setNaN(java.lang.String); method public void setPadEscape(char); method public void setPatternForCurrencySpacing(int, boolean, java.lang.String); method public void setPatternSeparator(char); method public void setPerMill(char); + method public void setPerMillString(java.lang.String); method public void setPercent(char); + method public void setPercentString(java.lang.String); method public void setPlusSign(char); + method public void setPlusSignString(java.lang.String); method public void setSignificantDigit(char); method public void setZeroDigit(char); field public static final int CURRENCY_SPC_CURRENCY_MATCH = 0; // 0x0 @@ -18625,7 +18678,9 @@ package android.icu.text { enum_constant public static final android.icu.text.DisplayContext DIALECT_NAMES; enum_constant public static final android.icu.text.DisplayContext LENGTH_FULL; enum_constant public static final android.icu.text.DisplayContext LENGTH_SHORT; + enum_constant public static final android.icu.text.DisplayContext NO_SUBSTITUTE; enum_constant public static final android.icu.text.DisplayContext STANDARD_NAMES; + enum_constant public static final android.icu.text.DisplayContext SUBSTITUTE; } public static final class DisplayContext.Type extends java.lang.Enum { @@ -18634,6 +18689,7 @@ package android.icu.text { enum_constant public static final android.icu.text.DisplayContext.Type CAPITALIZATION; enum_constant public static final android.icu.text.DisplayContext.Type DIALECT_HANDLING; enum_constant public static final android.icu.text.DisplayContext.Type DISPLAY_LENGTH; + enum_constant public static final android.icu.text.DisplayContext.Type SUBSTITUTE_HANDLING; } public abstract class IDNA { @@ -18741,6 +18797,7 @@ package android.icu.text { method public static android.icu.text.MeasureFormat getInstance(java.util.Locale, android.icu.text.MeasureFormat.FormatWidth, android.icu.text.NumberFormat); method public final android.icu.util.ULocale getLocale(); method public android.icu.text.NumberFormat getNumberFormat(); + method public java.lang.String getUnitDisplayName(android.icu.util.MeasureUnit); method public android.icu.text.MeasureFormat.FormatWidth getWidth(); method public final int hashCode(); method public android.icu.util.Measure parseObject(java.lang.String, java.text.ParsePosition); @@ -20279,6 +20336,7 @@ package android.icu.util { field public static final android.icu.util.MeasureUnit MILLILITER; field public static final android.icu.util.MeasureUnit MILLIMETER; field public static final android.icu.util.MeasureUnit MILLIMETER_OF_MERCURY; + field public static final android.icu.util.MeasureUnit MILLIMOLE_PER_LITER; field public static final android.icu.util.MeasureUnit MILLISECOND; field public static final android.icu.util.MeasureUnit MILLIWATT; field public static final android.icu.util.TimeUnit MINUTE; @@ -20290,6 +20348,7 @@ package android.icu.util { field public static final android.icu.util.MeasureUnit OUNCE; field public static final android.icu.util.MeasureUnit OUNCE_TROY; field public static final android.icu.util.MeasureUnit PARSEC; + field public static final android.icu.util.MeasureUnit PART_PER_MILLION; field public static final android.icu.util.MeasureUnit PICOMETER; field public static final android.icu.util.MeasureUnit PINT; field public static final android.icu.util.MeasureUnit PINT_METRIC; @@ -20413,6 +20472,9 @@ package android.icu.util { public static final class TimeZone.SystemTimeZoneType extends java.lang.Enum { method public static android.icu.util.TimeZone.SystemTimeZoneType valueOf(java.lang.String); method public static final android.icu.util.TimeZone.SystemTimeZoneType[] values(); + enum_constant public static final android.icu.util.TimeZone.SystemTimeZoneType ANY; + enum_constant public static final android.icu.util.TimeZone.SystemTimeZoneType CANONICAL; + enum_constant public static final android.icu.util.TimeZone.SystemTimeZoneType CANONICAL_LOCATION; } public final class ULocale implements java.lang.Comparable java.io.Serializable { @@ -20614,6 +20676,7 @@ package android.icu.util { field public static final android.icu.util.VersionInfo ICU_VERSION; field public static final android.icu.util.VersionInfo UCOL_BUILDER_VERSION; field public static final android.icu.util.VersionInfo UCOL_RUNTIME_VERSION; + field public static final android.icu.util.VersionInfo UNICODE_10_0; field public static final android.icu.util.VersionInfo UNICODE_1_0; field public static final android.icu.util.VersionInfo UNICODE_1_0_1; field public static final android.icu.util.VersionInfo UNICODE_1_1_0; @@ -37629,11 +37692,8 @@ package android.service.autofill { field public static final android.os.Parcelable.Creator<android.service.autofill.EditDistanceScorer> CREATOR; } - public final class FieldClassification implements android.os.Parcelable { - method public int describeContents(); + public final class FieldClassification { method public java.util.List<android.service.autofill.FieldClassification.Match> getMatches(); - method public void writeToParcel(android.os.Parcel, int); - field public static final android.os.Parcelable.Creator<android.service.autofill.FieldClassification> CREATOR; } public static final class FieldClassification.Match { @@ -40281,6 +40341,7 @@ package android.telephony { public class CarrierConfigManager { method public android.os.PersistableBundle getConfig(); method public android.os.PersistableBundle getConfigForSubId(int); + method public static boolean isConfigForIdentifiedCarrier(android.os.PersistableBundle); method public void notifyConfigChangedForSubId(int); field public static final java.lang.String ACTION_CARRIER_CONFIG_CHANGED = "android.telephony.action.CARRIER_CONFIG_CHANGED"; field public static final int DATA_CYCLE_THRESHOLD_DISABLED = -2; // 0xfffffffe @@ -41036,11 +41097,12 @@ package android.telephony { method public java.lang.String iccTransmitApduBasicChannel(int, int, int, int, int, java.lang.String); method public java.lang.String iccTransmitApduLogicalChannel(int, int, int, int, int, int, java.lang.String); method public boolean isConcurrentVoiceAndDataSupported(); - method public boolean isDataEnabled(); + method public deprecated boolean isDataEnabled(); method public boolean isHearingAidCompatibilitySupported(); method public boolean isNetworkRoaming(); method public boolean isSmsCapable(); method public deprecated boolean isTtyModeSupported(); + method public boolean isUserMobileDataEnabled(); method public boolean isVoiceCapable(); method public boolean isVoicemailVibrationEnabled(android.telecom.PhoneAccountHandle); method public boolean isWorldPhone(); @@ -41049,10 +41111,11 @@ package android.telephony { method public java.lang.String sendEnvelopeWithStatus(java.lang.String); method public void sendUssdRequest(java.lang.String, android.telephony.TelephonyManager.UssdResponseCallback, android.os.Handler); method public void sendVisualVoicemailSms(java.lang.String, int, java.lang.String, android.app.PendingIntent); - method public void setDataEnabled(boolean); + method public deprecated void setDataEnabled(boolean); method public boolean setLine1NumberForDisplay(java.lang.String, java.lang.String); method public boolean setOperatorBrandOverride(java.lang.String); method public boolean setPreferredNetworkTypeToGlobal(); + method public void setUserMobileDataEnabled(boolean); method public void setVisualVoicemailSmsFilterSettings(android.telephony.VisualVoicemailSmsFilterSettings); method public boolean setVoiceMailNumber(java.lang.String, java.lang.String); method public deprecated void setVoicemailRingtoneUri(android.telecom.PhoneAccountHandle, android.net.Uri); @@ -41448,61 +41511,6 @@ package android.telephony.mbms { } -package android.test { - - public deprecated class AndroidTestCase extends junit.framework.TestCase { - ctor public AndroidTestCase(); - method public void assertActivityRequiresPermission(java.lang.String, java.lang.String, java.lang.String); - method public void assertReadingContentUriRequiresPermission(android.net.Uri, java.lang.String); - method public void assertWritingContentUriRequiresPermission(android.net.Uri, java.lang.String); - method public android.content.Context getContext(); - method protected void scrubClass(java.lang.Class<?>) throws java.lang.IllegalAccessException; - method public void setContext(android.content.Context); - method public void testAndroidTestCaseSetupProperly(); - field protected android.content.Context mContext; - } - - public abstract deprecated class FlakyTest implements java.lang.annotation.Annotation { - } - - public deprecated class InstrumentationTestCase extends junit.framework.TestCase { - ctor public InstrumentationTestCase(); - method public android.app.Instrumentation getInstrumentation(); - method public deprecated void injectInsrumentation(android.app.Instrumentation); - method public void injectInstrumentation(android.app.Instrumentation); - method public final <T extends android.app.Activity> T launchActivity(java.lang.String, java.lang.Class<T>, android.os.Bundle); - method public final <T extends android.app.Activity> T launchActivityWithIntent(java.lang.String, java.lang.Class<T>, android.content.Intent); - method public void runTestOnUiThread(java.lang.Runnable) throws java.lang.Throwable; - method public void sendKeys(java.lang.String); - method public void sendKeys(int...); - method public void sendRepeatedKeys(int...); - } - - public deprecated class InstrumentationTestSuite extends junit.framework.TestSuite { - ctor public InstrumentationTestSuite(android.app.Instrumentation); - ctor public InstrumentationTestSuite(java.lang.String, android.app.Instrumentation); - ctor public InstrumentationTestSuite(java.lang.Class, android.app.Instrumentation); - method public void addTestSuite(java.lang.Class); - } - - public abstract deprecated interface PerformanceTestCase { - method public abstract boolean isPerformanceOnly(); - method public abstract int startPerformance(android.test.PerformanceTestCase.Intermediates); - } - - public static abstract interface PerformanceTestCase.Intermediates { - method public abstract void addIntermediate(java.lang.String); - method public abstract void addIntermediate(java.lang.String, long); - method public abstract void finishTiming(boolean); - method public abstract void setInternalIterations(int); - method public abstract void startTiming(boolean); - } - - public abstract deprecated class UiThreadTest implements java.lang.annotation.Annotation { - } - -} - package android.test.mock { public deprecated class MockApplication extends android.app.Application { @@ -41796,25 +41804,6 @@ package android.test.mock { } -package android.test.suitebuilder.annotation { - - public abstract deprecated class LargeTest implements java.lang.annotation.Annotation { - } - - public abstract deprecated class MediumTest implements java.lang.annotation.Annotation { - } - - public abstract deprecated class SmallTest implements java.lang.annotation.Annotation { - } - - public abstract deprecated class Smoke implements java.lang.annotation.Annotation { - } - - public abstract deprecated class Suppress implements java.lang.annotation.Annotation { - } - -} - package android.text { public class AlteredCharSequence implements java.lang.CharSequence android.text.GetChars { @@ -49130,9 +49119,13 @@ package android.view.textclassifier { method public default android.view.textclassifier.TextClassification classifyText(java.lang.CharSequence, int, int, android.os.LocaleList); method public default android.view.textclassifier.TextLinks generateLinks(java.lang.CharSequence, android.view.textclassifier.TextLinks.Options); method public default android.view.textclassifier.TextLinks generateLinks(java.lang.CharSequence); + method public default java.util.Collection<java.lang.String> getEntitiesForPreset(int); method public default android.view.textclassifier.TextSelection suggestSelection(java.lang.CharSequence, int, int, android.view.textclassifier.TextSelection.Options); method public default android.view.textclassifier.TextSelection suggestSelection(java.lang.CharSequence, int, int); method public default android.view.textclassifier.TextSelection suggestSelection(java.lang.CharSequence, int, int, android.os.LocaleList); + field public static final int ENTITY_PRESET_ALL = 0; // 0x0 + field public static final int ENTITY_PRESET_BASE = 2; // 0x2 + field public static final int ENTITY_PRESET_NONE = 1; // 0x1 field public static final android.view.textclassifier.TextClassifier NO_OP; field public static final java.lang.String TYPE_ADDRESS = "address"; field public static final java.lang.String TYPE_EMAIL = "email"; @@ -49142,6 +49135,13 @@ package android.view.textclassifier { field public static final java.lang.String TYPE_URL = "url"; } + public static final class TextClassifier.EntityConfig { + ctor public TextClassifier.EntityConfig(int); + method public android.view.textclassifier.TextClassifier.EntityConfig excludeEntities(java.lang.String...); + method public java.util.List<java.lang.String> getEntities(android.view.textclassifier.TextClassifier); + method public android.view.textclassifier.TextClassifier.EntityConfig includeEntities(java.lang.String...); + } + public final class TextLinks { method public boolean apply(android.text.SpannableString, java.util.function.Function<android.view.textclassifier.TextLinks.TextLink, android.text.style.ClickableSpan>); method public java.util.Collection<android.view.textclassifier.TextLinks.TextLink> getLinks(); @@ -49156,7 +49156,9 @@ package android.view.textclassifier { public static final class TextLinks.Options { ctor public TextLinks.Options(); method public android.os.LocaleList getDefaultLocales(); + method public android.view.textclassifier.TextClassifier.EntityConfig getEntityConfig(); method public android.view.textclassifier.TextLinks.Options setDefaultLocales(android.os.LocaleList); + method public android.view.textclassifier.TextLinks.Options setEntityConfig(android.view.textclassifier.TextClassifier.EntityConfig); } public static final class TextLinks.TextLink { @@ -49857,6 +49859,7 @@ package android.webkit { method public android.print.PrintDocumentAdapter createPrintDocumentAdapter(java.lang.String); method public android.webkit.WebMessagePort[] createWebMessageChannel(); method public void destroy(); + method public static void disableWebView(); method public void documentHasImages(android.os.Message); method public static void enableSlowWholeDocumentDraw(); method public void evaluateJavascript(java.lang.String, android.webkit.ValueCallback<java.lang.String>); @@ -49917,6 +49920,7 @@ package android.webkit { method public void saveWebArchive(java.lang.String); method public void saveWebArchive(java.lang.String, boolean, android.webkit.ValueCallback<java.lang.String>); method public deprecated void setCertificate(android.net.http.SslCertificate); + method public static void setDataDirectorySuffix(java.lang.String); method public void setDownloadListener(android.webkit.DownloadListener); method public void setFindListener(android.webkit.WebView.FindListener); method public deprecated void setHorizontalScrollbarOverlay(boolean); diff --git a/api/system-current.txt b/api/system-current.txt index 227b4833cf76..5859504126fa 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -4159,7 +4159,7 @@ package android.telephony { method public deprecated boolean isVisualVoicemailEnabled(android.telecom.PhoneAccountHandle); method public boolean needsOtaServiceProvisioning(); method public int setAllowedCarriers(int, java.util.List<android.service.carrier.CarrierIdentifier>); - method public void setDataEnabled(int, boolean); + method public deprecated void setDataEnabled(int, boolean); method public boolean setRadio(boolean); method public boolean setRadioPower(boolean); method public deprecated void setVisualVoicemailEnabled(android.telecom.PhoneAccountHandle, boolean); @@ -4605,6 +4605,7 @@ package android.webkit { method public boolean canInvokeDrawGlFunctor(android.view.View); method public void detachDrawGlFunctor(android.view.View, long); method public android.app.Application getApplication(); + method public java.lang.String getDataDirectorySuffix(); method public java.lang.String getErrorString(android.content.Context, int); method public int getPackageId(android.content.res.Resources, java.lang.String); method public void invokeDrawGlFunctor(android.view.View, long, boolean); diff --git a/api/test-current.txt b/api/test-current.txt index 5635b5653a77..e64c32087f1e 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -326,6 +326,13 @@ package android.net { field public static final int INVALID_SECURITY_PARAMETER_INDEX = 0; // 0x0 } + public class TrafficStats { + method public static long getLoopbackRxBytes(); + method public static long getLoopbackRxPackets(); + method public static long getLoopbackTxBytes(); + method public static long getLoopbackTxPackets(); + } + } package android.os { diff --git a/cmds/statsd/src/anomaly/AnomalyTracker.cpp b/cmds/statsd/src/anomaly/AnomalyTracker.cpp index 4777dcd32377..162a34b957f8 100644 --- a/cmds/statsd/src/anomaly/AnomalyTracker.cpp +++ b/cmds/statsd/src/anomaly/AnomalyTracker.cpp @@ -23,6 +23,7 @@ #include <android/os/IIncidentManager.h> #include <android/os/IncidentReportArgs.h> #include <binder/IServiceManager.h> +#include <statslog.h> #include <time.h> namespace android { @@ -224,6 +225,9 @@ void AnomalyTracker::declareAnomaly(const uint64_t& timestampNs) { } StatsdStats::getInstance().noteAnomalyDeclared(mConfigKey, mAlert.name()); + + android::util::stats_write(android::util::ANOMALY_DETECTED, mConfigKey.GetUid(), + mConfigKey.GetName().c_str(), mAlert.name().c_str()); } void AnomalyTracker::declareAnomalyIfAlarmExpired(const HashableDimensionKey& dimensionKey, diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto index a39acb218193..1c6d9b09a839 100644 --- a/cmds/statsd/src/atoms.proto +++ b/cmds/statsd/src/atoms.proto @@ -79,25 +79,26 @@ message Atom { IsolatedUidChanged isolated_uid_changed = 43; PacketWakeupOccurred packet_wakeup_occurred = 44; DropboxErrorChanged dropbox_error_changed = 45; - AppHook app_hook = 46; + AnomalyDetected anomaly_detected = 46; + AppHook app_hook = 47; // TODO: Reorder the numbering so that the most frequent occur events occur in the first 15. } // Pulled events will start at field 1000. oneof pulled { - WifiBytesTransferred wifi_bytes_transferred = 1000; - WifiBytesTransferredByFgBg wifi_bytes_transferred_by_fg_bg = 1001; - MobileBytesTransferred mobile_bytes_transferred = 1002; - MobileBytesTransferredByFgBg mobile_bytes_transferred_by_fg_bg = 1003; - KernelWakelockPulled kernel_wakelock_pulled = 1004; - PowerStatePlatformSleepStatePulled power_state_platform_sleep_state_pulled = 1005; - PowerStateVoterPulled power_state_voter_pulled = 1006; - PowerStateSubsystemSleepStatePulled power_state_subsystem_sleep_state_pulled = 1007; - CpuTimePerFreqPulled cpu_time_per_freq_pulled = 1008; - CpuTimePerUidPulled cpu_time_per_uid_pulled = 1009; - CpuTimePerUidFreqPulled cpu_time_per_uid_freq_pulled = 1010; - WifiActivityEnergyInfoPulled wifi_activity_energy_info_pulled = 1011; - ModemActivityInfoPulled modem_activity_info_pulled = 1012; + WifiBytesTransfer wifi_bytes_transfer = 1000; + WifiBytesTransferByFgBg wifi_bytes_transfer_by_fg_bg = 1001; + MobileBytesTransfer mobile_bytes_transfer = 1002; + MobileBytesTransferByFgBg mobile_bytes_transfer_by_fg_bg = 1003; + KernelWakelock kernel_wakelock = 1004; + PlatformSleepState platform_sleep_state = 1005; + SleepStateVoter sleep_state_voter = 1006; + SubsystemSleepState subsystem_sleep_state = 1007; + CpuTimePerFreq cpu_time_per_freq = 1008; + CpuTimePerUid cpu_time_per_uid = 1009; + CpuTimePerUidFreq cpu_time_per_uid_freq = 1010; + WifiActivityEnergyInfo wifi_activity_energy_info = 1011; + ModemActivityInfo modem_activity_info = 1012; AttributionChainDummyAtom attribution_chain_dummy_atom = 10000; } } @@ -841,12 +842,29 @@ message AppHook { } /** + * Logs when statsd detects an anomaly. + * + * Logged from: + * frameworks/base/cmds/statsd/src/anomaly/AnomalyTracker.cpp + */ +message AnomalyDetected { + // Uid that owns the config whose anomaly detection alert fired. + optional int32 config_uid = 1; + + // Name of the config whose anomaly detection alert fired. + optional string config_name = 2; + + // Name of the alert (i.e. name of the anomaly that was detected). + optional string alert_name = 3; +} + +/** * Pulls bytes transferred via wifi (Sum of foreground and background usage). * * Pulled from: * StatsCompanionService (using BatteryStats to get which interfaces are wifi) */ -message WifiBytesTransferred { +message WifiBytesTransfer { optional int32 uid = 1; optional int64 rx_bytes = 2; @@ -864,7 +882,7 @@ message WifiBytesTransferred { * Pulled from: * StatsCompanionService (using BatteryStats to get which interfaces are wifi) */ -message WifiBytesTransferredByFgBg { +message WifiBytesTransferByFgBg { optional int32 uid = 1; // 1 denotes foreground and 0 denotes background. This is called Set in NetworkStats. @@ -885,7 +903,7 @@ message WifiBytesTransferredByFgBg { * Pulled from: * StatsCompanionService (using BatteryStats to get which interfaces are mobile data) */ -message MobileBytesTransferred { +message MobileBytesTransfer { optional int32 uid = 1; optional int64 rx_bytes = 2; @@ -903,7 +921,7 @@ message MobileBytesTransferred { * Pulled from: * StatsCompanionService (using BatteryStats to get which interfaces are mobile data) */ -message MobileBytesTransferredByFgBg { +message MobileBytesTransferByFgBg { optional int32 uid = 1; // 1 denotes foreground and 0 denotes background. This is called Set in NetworkStats. @@ -925,7 +943,7 @@ message MobileBytesTransferredByFgBg { * Pulled from: * StatsCompanionService using KernelWakelockReader. */ -message KernelWakelockPulled { +message KernelWakelock { optional string name = 1; optional int32 count = 2; @@ -941,7 +959,7 @@ message KernelWakelockPulled { * Definition here: * hardware/interfaces/power/1.0/types.hal */ -message PowerStatePlatformSleepStatePulled { +message PlatformSleepState { optional string name = 1; optional uint64 residency_in_msec_since_boot = 2; optional uint64 total_transitions = 3; @@ -954,9 +972,9 @@ message PowerStatePlatformSleepStatePulled { * Definition here: * hardware/interfaces/power/1.0/types.hal */ -message PowerStateVoterPulled { - optional string power_state_platform_sleep_state_name = 1; - optional string power_state_voter_name = 2; +message SleepStateVoter { + optional string platform_sleep_state_name = 1; + optional string voter_name = 2; optional uint64 total_time_in_msec_voted_for_since_boot = 3; optional uint64 total_number_of_times_voted_since_boot = 4; } @@ -967,9 +985,9 @@ message PowerStateVoterPulled { * Definition here: * hardware/interfaces/power/1.1/types.hal */ -message PowerStateSubsystemSleepStatePulled { - optional string power_state_subsystem_name = 1; - optional string power_state_subsystem_sleep_state_name = 2; +message SubsystemSleepState { + optional string subsystem_name = 1; + optional string subsystem_sleep_state_name = 2; optional uint64 residency_in_msec_since_boot = 3; optional uint64 total_transitions = 4; optional uint64 last_entry_timestamp_ms = 5; @@ -1006,17 +1024,17 @@ message IsolatedUidChanged { * if current time is smaller than last value, there must be a cpu * hotplug event, and the current time is taken as delta. */ -message CpuTimePerFreqPulled { +message CpuTimePerFreq { optional uint32 cluster = 1; optional uint32 freq_index = 2; - optional uint64 time = 3; + optional uint64 time_ms = 3; } /** * Pulls Cpu Time Per Uid. * Note that isolated process uid time should be attributed to host uids. */ -message CpuTimePerUidPulled { +message CpuTimePerUid { optional uint64 uid = 1; optional uint64 user_time_ms = 2; optional uint64 sys_time_ms = 3; @@ -1027,7 +1045,7 @@ message CpuTimePerUidPulled { * Note that isolated process uid time should be attributed to host uids. * For each uid, we order the time by descending frequencies. */ -message CpuTimePerUidFreqPulled { +message CpuTimePerUidFreq { optional uint64 uid = 1; optional uint64 freq_idx = 2; optional uint64 time_ms = 3; @@ -1065,7 +1083,7 @@ message PacketWakeupOccurred { /** * Pulls Wifi Controller Activity Energy Info */ -message WifiActivityEnergyInfoPulled { +message WifiActivityEnergyInfo { // timestamp(wall clock) of record creation optional uint64 timestamp_ms = 1; // stack reported state @@ -1084,7 +1102,7 @@ message WifiActivityEnergyInfoPulled { /** * Pulls Modem Activity Energy Info */ -message ModemActivityInfoPulled { +message ModemActivityInfo { // timestamp(wall clock) of record creation optional uint64 timestamp_ms = 1; // sleep time in ms. diff --git a/cmds/statsd/src/external/CpuTimePerUidFreqPuller.cpp b/cmds/statsd/src/external/CpuTimePerUidFreqPuller.cpp index e2745d21af3d..97387602627b 100644 --- a/cmds/statsd/src/external/CpuTimePerUidFreqPuller.cpp +++ b/cmds/statsd/src/external/CpuTimePerUidFreqPuller.cpp @@ -70,7 +70,7 @@ bool CpuTimePerUidFreqPuller::Pull(const int tagId, vector<shared_ptr<LogEvent>> int idx = 0; do { timeMs = std::stoull(pch); - auto ptr = make_shared<LogEvent>(android::util::CPU_TIME_PER_UID_FREQ_PULLED, timestamp); + auto ptr = make_shared<LogEvent>(android::util::CPU_TIME_PER_UID_FREQ, timestamp); ptr->write(uid); ptr->write(idx); ptr->write(timeMs); diff --git a/cmds/statsd/src/external/CpuTimePerUidPuller.cpp b/cmds/statsd/src/external/CpuTimePerUidPuller.cpp index e0572dca679d..f69b9b537943 100644 --- a/cmds/statsd/src/external/CpuTimePerUidPuller.cpp +++ b/cmds/statsd/src/external/CpuTimePerUidPuller.cpp @@ -65,7 +65,7 @@ bool CpuTimePerUidPuller::Pull(const int tagId, vector<shared_ptr<LogEvent>>* da pch = strtok(buf, " "); uint64_t sysTimeMs = std::stoull(pch); - auto ptr = make_shared<LogEvent>(android::util::CPU_TIME_PER_UID_PULLED, timestamp); + auto ptr = make_shared<LogEvent>(android::util::CPU_TIME_PER_UID, timestamp); ptr->write(uid); ptr->write(userTimeMs); ptr->write(sysTimeMs); diff --git a/cmds/statsd/src/external/ResourcePowerManagerPuller.cpp b/cmds/statsd/src/external/ResourcePowerManagerPuller.cpp index 3ee636dfcde5..cb9f1cc15ce9 100644 --- a/cmds/statsd/src/external/ResourcePowerManagerPuller.cpp +++ b/cmds/statsd/src/external/ResourcePowerManagerPuller.cpp @@ -92,7 +92,7 @@ bool ResourcePowerManagerPuller::Pull(const int tagId, vector<shared_ptr<LogEven const PowerStatePlatformSleepState& state = states[i]; auto statePtr = make_shared<LogEvent>( - android::util::POWER_STATE_PLATFORM_SLEEP_STATE_PULLED, timestamp); + android::util::PLATFORM_SLEEP_STATE, timestamp); statePtr->write(state.name); statePtr->write(state.residencyInMsecSinceBoot); statePtr->write(state.totalTransitions); @@ -104,7 +104,7 @@ bool ResourcePowerManagerPuller::Pull(const int tagId, vector<shared_ptr<LogEven (long long)state.totalTransitions, state.supportedOnlyInSuspend ? 1 : 0); for (auto voter : state.voters) { auto voterPtr = - make_shared<LogEvent>(android::util::POWER_STATE_VOTER_PULLED, timestamp); + make_shared<LogEvent>(android::util::SLEEP_STATE_VOTER, timestamp); voterPtr->write(state.name); voterPtr->write(voter.name); voterPtr->write(voter.totalTimeInMsecVotedForSinceBoot); @@ -138,7 +138,7 @@ bool ResourcePowerManagerPuller::Pull(const int tagId, vector<shared_ptr<LogEven for (size_t j = 0; j < subsystem.states.size(); j++) { const PowerStateSubsystemSleepState& state = subsystem.states[j]; auto subsystemStatePtr = make_shared<LogEvent>( - android::util::POWER_STATE_SUBSYSTEM_SLEEP_STATE_PULLED, timestamp); + android::util::SUBSYSTEM_SLEEP_STATE, timestamp); subsystemStatePtr->write(subsystem.name); subsystemStatePtr->write(state.name); subsystemStatePtr->write(state.residencyInMsecSinceBoot); diff --git a/cmds/statsd/src/external/StatsPullerManagerImpl.cpp b/cmds/statsd/src/external/StatsPullerManagerImpl.cpp index 529ee2b0617a..d707f8561b21 100644 --- a/cmds/statsd/src/external/StatsPullerManagerImpl.cpp +++ b/cmds/statsd/src/external/StatsPullerManagerImpl.cpp @@ -50,24 +50,24 @@ StatsPullerManagerImpl::StatsPullerManagerImpl() shared_ptr<StatsPuller> cpuTimePerUidPuller = make_shared<CpuTimePerUidPuller>(); shared_ptr<StatsPuller> cpuTimePerUidFreqPuller = make_shared<CpuTimePerUidFreqPuller>(); - mPullers.insert({android::util::KERNEL_WAKELOCK_PULLED, + mPullers.insert({android::util::KERNEL_WAKELOCK, statsCompanionServicePuller}); - mPullers.insert({android::util::WIFI_BYTES_TRANSFERRED, + mPullers.insert({android::util::WIFI_BYTES_TRANSFER, statsCompanionServicePuller}); - mPullers.insert({android::util::MOBILE_BYTES_TRANSFERRED, + mPullers.insert({android::util::MOBILE_BYTES_TRANSFER, statsCompanionServicePuller}); - mPullers.insert({android::util::WIFI_BYTES_TRANSFERRED_BY_FG_BG, + mPullers.insert({android::util::WIFI_BYTES_TRANSFER_BY_FG_BG, statsCompanionServicePuller}); - mPullers.insert({android::util::MOBILE_BYTES_TRANSFERRED_BY_FG_BG, + mPullers.insert({android::util::MOBILE_BYTES_TRANSFER_BY_FG_BG, statsCompanionServicePuller}); - mPullers.insert({android::util::POWER_STATE_PLATFORM_SLEEP_STATE_PULLED, + mPullers.insert({android::util::PLATFORM_SLEEP_STATE, resourcePowerManagerPuller}); - mPullers.insert({android::util::POWER_STATE_VOTER_PULLED, + mPullers.insert({android::util::SLEEP_STATE_VOTER, resourcePowerManagerPuller}); - mPullers.insert({android::util::POWER_STATE_SUBSYSTEM_SLEEP_STATE_PULLED, + mPullers.insert({android::util::SUBSYSTEM_SLEEP_STATE, resourcePowerManagerPuller}); - mPullers.insert({android::util::CPU_TIME_PER_UID_PULLED, cpuTimePerUidPuller}); - mPullers.insert({android::util::CPU_TIME_PER_UID_FREQ_PULLED, cpuTimePerUidFreqPuller}); + mPullers.insert({android::util::CPU_TIME_PER_UID, cpuTimePerUidPuller}); + mPullers.insert({android::util::CPU_TIME_PER_UID_FREQ, cpuTimePerUidFreqPuller}); mStatsCompanionService = StatsService::getStatsCompanionService(); } diff --git a/cmds/statsd/src/metrics/metrics_manager_util.cpp b/cmds/statsd/src/metrics/metrics_manager_util.cpp index 200ef0bb700b..5d0e97e24f79 100644 --- a/cmds/statsd/src/metrics/metrics_manager_util.cpp +++ b/cmds/statsd/src/metrics/metrics_manager_util.cpp @@ -206,7 +206,7 @@ bool initMetrics(const ConfigKey& key, const StatsdConfig& config, const long ti // clock will not grow very aggressive. New metrics will be delayed up to // MIN_BUCKET_SIZE_SEC before starting. long currentTimeSec = time(nullptr); - uint64_t startTimeNs = (currentTimeSec + kMinBucketSizeSec - + uint64_t startTimeNs = (currentTimeSec - kMinBucketSizeSec - (currentTimeSec - timeBaseSec) % kMinBucketSizeSec) * NS_PER_SEC; @@ -410,12 +410,15 @@ bool initMetrics(const ConfigKey& key, const StatsdConfig& config, const long ti return false; } - if (((!metric.gauge_fields().has_include_all() || - (metric.gauge_fields().has_include_all() && - metric.gauge_fields().include_all() == false)) && - metric.gauge_fields().field_num_size() == 0) || - (metric.gauge_fields().has_include_all() && metric.gauge_fields().include_all() == true && - metric.gauge_fields().field_num_size() > 0)) { + if ((!metric.gauge_fields().has_include_all() || + (metric.gauge_fields().include_all() == false)) && + metric.gauge_fields().field_num_size() == 0) { + ALOGW("Incorrect field filter setting in GaugeMetric %s", metric.name().c_str()); + return false; + } + if ((metric.gauge_fields().has_include_all() && + metric.gauge_fields().include_all() == true) && + metric.gauge_fields().field_num_size() > 0) { ALOGW("Incorrect field filter setting in GaugeMetric %s", metric.name().c_str()); return false; } diff --git a/cmds/statsd/tests/guardrail/StatsdStats_test.cpp b/cmds/statsd/tests/guardrail/StatsdStats_test.cpp index 312de1b16664..765804475eef 100644 --- a/cmds/statsd/tests/guardrail/StatsdStats_test.cpp +++ b/cmds/statsd/tests/guardrail/StatsdStats_test.cpp @@ -214,7 +214,7 @@ TEST(StatsdStatsTest, TestAtomLog) { stats.noteAtomLogged(android::util::SENSOR_STATE_CHANGED, now + 2); stats.noteAtomLogged(android::util::DROPBOX_ERROR_CHANGED, now + 3); // pulled event, should ignore - stats.noteAtomLogged(android::util::WIFI_BYTES_TRANSFERRED, now + 4); + stats.noteAtomLogged(android::util::WIFI_BYTES_TRANSFER, now + 4); vector<uint8_t> output; stats.dumpStats(&output, false); diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java index 847082a0a4e1..19e3a23dae2c 100644 --- a/core/java/android/app/ActivityManager.java +++ b/core/java/android/app/ActivityManager.java @@ -457,6 +457,19 @@ public class ActivityManager { /** @hide User operation call: one of related users cannot be stopped. */ public static final int USER_OP_ERROR_RELATED_USERS_CANNOT_STOP = -4; + /** + * Process states, describing the kind of state a particular process is in. + * When updating these, make sure to also check all related references to the + * constant in code, and update these arrays: + * + * com.android.internal.app.procstats.ProcessState#PROCESS_STATE_TO_STATE + * com.android.server.am.ProcessList#sProcStateToProcMem + * com.android.server.am.ProcessList#sFirstAwakePssTimes + * com.android.server.am.ProcessList#sSameAwakePssTimes + * com.android.server.am.ProcessList#sTestFirstPssTimes + * com.android.server.am.ProcessList#sTestSamePssTimes + */ + /** @hide Not a real process state. */ public static final int PROCESS_STATE_UNKNOWN = -1; @@ -491,20 +504,20 @@ public class ActivityManager { /** @hide Process is in the background running a backup/restore operation. */ public static final int PROCESS_STATE_BACKUP = 9; - /** @hide Process is in the background, but it can't restore its state so we want - * to try to avoid killing it. */ - public static final int PROCESS_STATE_HEAVY_WEIGHT = 10; - /** @hide Process is in the background running a service. Unlike oom_adj, this level * is used for both the normal running in background state and the executing * operations state. */ - public static final int PROCESS_STATE_SERVICE = 11; + public static final int PROCESS_STATE_SERVICE = 10; /** @hide Process is in the background running a receiver. Note that from the * perspective of oom_adj, receivers run at a higher foreground level, but for our * prioritization here that is not necessary and putting them below services means * many fewer changes in some process states as they receive broadcasts. */ - public static final int PROCESS_STATE_RECEIVER = 12; + public static final int PROCESS_STATE_RECEIVER = 11; + + /** @hide Process is in the background, but it can't restore its state so we want + * to try to avoid killing it. */ + public static final int PROCESS_STATE_HEAVY_WEIGHT = 12; /** @hide Process is in the background but hosts the home activity. */ public static final int PROCESS_STATE_HOME = 13; @@ -810,7 +823,7 @@ public class ActivityManager { * impose on your application to let the overall system work best. The * returned value is in megabytes; the baseline Android memory class is * 16 (which happens to be the Java heap limit of those devices); some - * device with more memory may return 24 or even higher numbers. + * devices with more memory may return 24 or even higher numbers. */ public int getMemoryClass() { return staticGetMemoryClass(); @@ -837,7 +850,7 @@ public class ActivityManager { * constrained devices, or it may be significantly larger on devices with * a large amount of available RAM. * - * <p>The is the size of the application's Dalvik heap if it has + * <p>This is the size of the application's Dalvik heap if it has * specified <code>android:largeHeap="true"</code> in its manifest. */ public int getLargeMemoryClass() { @@ -2942,14 +2955,6 @@ public class ActivityManager { public static final int IMPORTANCE_CANT_SAVE_STATE_PRE_26 = 170; /** - * Constant for {@link #importance}: This process is running an - * application that can not save its state, and thus can't be killed - * while in the background. This will be used with apps that have - * {@link android.R.attr#cantSaveState} set on their application tag. - */ - public static final int IMPORTANCE_CANT_SAVE_STATE = 270; - - /** * Constant for {@link #importance}: This process is contains services * that should remain running. These are background services apps have * started, not something the user is aware of, so they may be killed by @@ -2959,6 +2964,14 @@ public class ActivityManager { public static final int IMPORTANCE_SERVICE = 300; /** + * Constant for {@link #importance}: This process is running an + * application that can not save its state, and thus can't be killed + * while in the background. This will be used with apps that have + * {@link android.R.attr#cantSaveState} set on their application tag. + */ + public static final int IMPORTANCE_CANT_SAVE_STATE = 350; + + /** * Constant for {@link #importance}: This process process contains * cached code that is expendable, not actively running any app components * we care about. @@ -2993,10 +3006,10 @@ public class ActivityManager { return IMPORTANCE_GONE; } else if (procState >= PROCESS_STATE_HOME) { return IMPORTANCE_CACHED; - } else if (procState >= PROCESS_STATE_SERVICE) { - return IMPORTANCE_SERVICE; } else if (procState == PROCESS_STATE_HEAVY_WEIGHT) { return IMPORTANCE_CANT_SAVE_STATE; + } else if (procState >= PROCESS_STATE_SERVICE) { + return IMPORTANCE_SERVICE; } else if (procState >= PROCESS_STATE_TRANSIENT_BACKGROUND) { return IMPORTANCE_PERCEPTIBLE; } else if (procState >= PROCESS_STATE_IMPORTANT_FOREGROUND) { @@ -3049,10 +3062,10 @@ public class ActivityManager { return PROCESS_STATE_NONEXISTENT; } else if (importance >= IMPORTANCE_CACHED) { return PROCESS_STATE_HOME; - } else if (importance >= IMPORTANCE_SERVICE) { - return PROCESS_STATE_SERVICE; } else if (importance == IMPORTANCE_CANT_SAVE_STATE) { return PROCESS_STATE_HEAVY_WEIGHT; + } else if (importance >= IMPORTANCE_SERVICE) { + return PROCESS_STATE_SERVICE; } else if (importance >= IMPORTANCE_PERCEPTIBLE) { return PROCESS_STATE_TRANSIENT_BACKGROUND; } else if (importance >= IMPORTANCE_VISIBLE) { @@ -3837,7 +3850,7 @@ public class ActivityManager { pw.println(); dumpService(pw, fd, ProcessStats.SERVICE_NAME, new String[] { packageName }); pw.println(); - dumpService(pw, fd, "usagestats", new String[] { "--packages", packageName }); + dumpService(pw, fd, "usagestats", new String[] { packageName }); pw.println(); dumpService(pw, fd, BatteryStats.SERVICE_NAME, new String[] { packageName }); pw.flush(); diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index 84f032d432ea..de346f315016 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -1152,7 +1152,7 @@ public final class ActivityThread extends ClientTransactionHandler { int N = stats.dbStats.size(); if (N > 0) { pw.println(" DATABASES"); - printRow(pw, " %8s %8s %14s %14s %s", "pgsz", "dbsz", "Lookaside(b)", "cache", + printRow(pw, DB_INFO_FORMAT, "pgsz", "dbsz", "Lookaside(b)", "cache", "Dbname"); for (int i = 0; i < N; i++) { DbStats dbStats = stats.dbStats.get(i); @@ -1184,6 +1184,124 @@ public final class ActivityThread extends ClientTransactionHandler { } @Override + public void dumpMemInfoProto(ParcelFileDescriptor pfd, Debug.MemoryInfo mem, + boolean dumpFullInfo, boolean dumpDalvik, boolean dumpSummaryOnly, + boolean dumpUnreachable, String[] args) { + ProtoOutputStream proto = new ProtoOutputStream(pfd.getFileDescriptor()); + try { + dumpMemInfo(proto, mem, dumpFullInfo, dumpDalvik, dumpSummaryOnly, dumpUnreachable); + } finally { + proto.flush(); + IoUtils.closeQuietly(pfd); + } + } + + private void dumpMemInfo(ProtoOutputStream proto, Debug.MemoryInfo memInfo, + boolean dumpFullInfo, boolean dumpDalvik, + boolean dumpSummaryOnly, boolean dumpUnreachable) { + long nativeMax = Debug.getNativeHeapSize() / 1024; + long nativeAllocated = Debug.getNativeHeapAllocatedSize() / 1024; + long nativeFree = Debug.getNativeHeapFreeSize() / 1024; + + Runtime runtime = Runtime.getRuntime(); + runtime.gc(); // Do GC since countInstancesOfClass counts unreachable objects. + long dalvikMax = runtime.totalMemory() / 1024; + long dalvikFree = runtime.freeMemory() / 1024; + long dalvikAllocated = dalvikMax - dalvikFree; + + Class[] classesToCount = new Class[] { + ContextImpl.class, + Activity.class, + WebView.class, + OpenSSLSocketImpl.class + }; + long[] instanceCounts = VMDebug.countInstancesOfClasses(classesToCount, true); + long appContextInstanceCount = instanceCounts[0]; + long activityInstanceCount = instanceCounts[1]; + long webviewInstanceCount = instanceCounts[2]; + long openSslSocketCount = instanceCounts[3]; + + long viewInstanceCount = ViewDebug.getViewInstanceCount(); + long viewRootInstanceCount = ViewDebug.getViewRootImplCount(); + int globalAssetCount = AssetManager.getGlobalAssetCount(); + int globalAssetManagerCount = AssetManager.getGlobalAssetManagerCount(); + int binderLocalObjectCount = Debug.getBinderLocalObjectCount(); + int binderProxyObjectCount = Debug.getBinderProxyObjectCount(); + int binderDeathObjectCount = Debug.getBinderDeathObjectCount(); + long parcelSize = Parcel.getGlobalAllocSize(); + long parcelCount = Parcel.getGlobalAllocCount(); + SQLiteDebug.PagerStats stats = SQLiteDebug.getDatabaseInfo(); + + final long mToken = proto.start(MemInfoProto.AppData.PROCESS_MEMORY); + proto.write(MemInfoProto.ProcessMemory.PID, Process.myPid()); + proto.write(MemInfoProto.ProcessMemory.PROCESS_NAME, + (mBoundApplication != null) ? mBoundApplication.processName : "unknown"); + dumpMemInfoTable(proto, memInfo, dumpDalvik, dumpSummaryOnly, + nativeMax, nativeAllocated, nativeFree, + dalvikMax, dalvikAllocated, dalvikFree); + proto.end(mToken); + + final long oToken = proto.start(MemInfoProto.AppData.OBJECTS); + proto.write(MemInfoProto.AppData.ObjectStats.VIEW_INSTANCE_COUNT, viewInstanceCount); + proto.write(MemInfoProto.AppData.ObjectStats.VIEW_ROOT_INSTANCE_COUNT, + viewRootInstanceCount); + proto.write(MemInfoProto.AppData.ObjectStats.APP_CONTEXT_INSTANCE_COUNT, + appContextInstanceCount); + proto.write(MemInfoProto.AppData.ObjectStats.ACTIVITY_INSTANCE_COUNT, + activityInstanceCount); + proto.write(MemInfoProto.AppData.ObjectStats.GLOBAL_ASSET_COUNT, globalAssetCount); + proto.write(MemInfoProto.AppData.ObjectStats.GLOBAL_ASSET_MANAGER_COUNT, + globalAssetManagerCount); + proto.write(MemInfoProto.AppData.ObjectStats.LOCAL_BINDER_OBJECT_COUNT, + binderLocalObjectCount); + proto.write(MemInfoProto.AppData.ObjectStats.PROXY_BINDER_OBJECT_COUNT, + binderProxyObjectCount); + proto.write(MemInfoProto.AppData.ObjectStats.PARCEL_MEMORY_KB, parcelSize / 1024); + proto.write(MemInfoProto.AppData.ObjectStats.PARCEL_COUNT, parcelCount); + proto.write(MemInfoProto.AppData.ObjectStats.BINDER_OBJECT_DEATH_COUNT, + binderDeathObjectCount); + proto.write(MemInfoProto.AppData.ObjectStats.OPEN_SSL_SOCKET_COUNT, openSslSocketCount); + proto.write(MemInfoProto.AppData.ObjectStats.WEBVIEW_INSTANCE_COUNT, + webviewInstanceCount); + proto.end(oToken); + + // SQLite mem info + final long sToken = proto.start(MemInfoProto.AppData.SQL); + proto.write(MemInfoProto.AppData.SqlStats.MEMORY_USED_KB, stats.memoryUsed / 1024); + proto.write(MemInfoProto.AppData.SqlStats.PAGECACHE_OVERFLOW_KB, + stats.pageCacheOverflow / 1024); + proto.write(MemInfoProto.AppData.SqlStats.MALLOC_SIZE_KB, stats.largestMemAlloc / 1024); + int n = stats.dbStats.size(); + for (int i = 0; i < n; i++) { + DbStats dbStats = stats.dbStats.get(i); + + final long dToken = proto.start(MemInfoProto.AppData.SqlStats.DATABASES); + proto.write(MemInfoProto.AppData.SqlStats.Database.NAME, dbStats.dbName); + proto.write(MemInfoProto.AppData.SqlStats.Database.PAGE_SIZE, dbStats.pageSize); + proto.write(MemInfoProto.AppData.SqlStats.Database.DB_SIZE, dbStats.dbSize); + proto.write(MemInfoProto.AppData.SqlStats.Database.LOOKASIDE_B, dbStats.lookaside); + proto.write(MemInfoProto.AppData.SqlStats.Database.CACHE, dbStats.cache); + proto.end(dToken); + } + proto.end(sToken); + + // Asset details. + String assetAlloc = AssetManager.getAssetAllocations(); + if (assetAlloc != null) { + proto.write(MemInfoProto.AppData.ASSET_ALLOCATIONS, assetAlloc); + } + + // Unreachable native memory + if (dumpUnreachable) { + int flags = mBoundApplication == null ? 0 : mBoundApplication.appInfo.flags; + boolean showContents = (flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0 + || android.os.Build.IS_DEBUGGABLE; + proto.write(MemInfoProto.AppData.UNREACHABLE_MEMORY, + Debug.getUnreachableMemory(100, showContents)); + } + } + + @Override public void dumpGfxInfo(ParcelFileDescriptor pfd, String[] args) { nDumpGraphicsInfo(pfd.getFileDescriptor()); WindowManagerGlobal.getInstance().dumpGfxInfo(pfd.getFileDescriptor(), args); @@ -2335,23 +2453,23 @@ public final class ActivityThread extends ClientTransactionHandler { * * @param hasSwappedOutPss determines whether to use dirtySwap or dirtySwapPss */ - private static void dumpHeap(ProtoOutputStream proto, long fieldId, String name, + private static void dumpMemoryInfo(ProtoOutputStream proto, long fieldId, String name, int pss, int cleanPss, int sharedDirty, int privateDirty, int sharedClean, int privateClean, boolean hasSwappedOutPss, int dirtySwap, int dirtySwapPss) { final long token = proto.start(fieldId); - proto.write(MemInfoProto.NativeProcess.MemoryInfo.NAME, name); - proto.write(MemInfoProto.NativeProcess.MemoryInfo.TOTAL_PSS_KB, pss); - proto.write(MemInfoProto.NativeProcess.MemoryInfo.CLEAN_PSS_KB, cleanPss); - proto.write(MemInfoProto.NativeProcess.MemoryInfo.SHARED_DIRTY_KB, sharedDirty); - proto.write(MemInfoProto.NativeProcess.MemoryInfo.PRIVATE_DIRTY_KB, privateDirty); - proto.write(MemInfoProto.NativeProcess.MemoryInfo.SHARED_CLEAN_KB, sharedClean); - proto.write(MemInfoProto.NativeProcess.MemoryInfo.PRIVATE_CLEAN_KB, privateClean); + proto.write(MemInfoProto.ProcessMemory.MemoryInfo.NAME, name); + proto.write(MemInfoProto.ProcessMemory.MemoryInfo.TOTAL_PSS_KB, pss); + proto.write(MemInfoProto.ProcessMemory.MemoryInfo.CLEAN_PSS_KB, cleanPss); + proto.write(MemInfoProto.ProcessMemory.MemoryInfo.SHARED_DIRTY_KB, sharedDirty); + proto.write(MemInfoProto.ProcessMemory.MemoryInfo.PRIVATE_DIRTY_KB, privateDirty); + proto.write(MemInfoProto.ProcessMemory.MemoryInfo.SHARED_CLEAN_KB, sharedClean); + proto.write(MemInfoProto.ProcessMemory.MemoryInfo.PRIVATE_CLEAN_KB, privateClean); if (hasSwappedOutPss) { - proto.write(MemInfoProto.NativeProcess.MemoryInfo.DIRTY_SWAP_PSS_KB, dirtySwapPss); + proto.write(MemInfoProto.ProcessMemory.MemoryInfo.DIRTY_SWAP_PSS_KB, dirtySwapPss); } else { - proto.write(MemInfoProto.NativeProcess.MemoryInfo.DIRTY_SWAP_KB, dirtySwap); + proto.write(MemInfoProto.ProcessMemory.MemoryInfo.DIRTY_SWAP_KB, dirtySwap); } proto.end(token); @@ -2366,26 +2484,26 @@ public final class ActivityThread extends ClientTransactionHandler { long dalvikMax, long dalvikAllocated, long dalvikFree) { if (!dumpSummaryOnly) { - final long nhToken = proto.start(MemInfoProto.NativeProcess.NATIVE_HEAP); - dumpHeap(proto, MemInfoProto.NativeProcess.HeapInfo.MEM_INFO, "Native Heap", + final long nhToken = proto.start(MemInfoProto.ProcessMemory.NATIVE_HEAP); + dumpMemoryInfo(proto, MemInfoProto.ProcessMemory.HeapInfo.MEM_INFO, "Native Heap", memInfo.nativePss, memInfo.nativeSwappablePss, memInfo.nativeSharedDirty, memInfo.nativePrivateDirty, memInfo.nativeSharedClean, memInfo.nativePrivateClean, memInfo.hasSwappedOutPss, memInfo.nativeSwappedOut, memInfo.nativeSwappedOutPss); - proto.write(MemInfoProto.NativeProcess.HeapInfo.HEAP_SIZE_KB, nativeMax); - proto.write(MemInfoProto.NativeProcess.HeapInfo.HEAP_ALLOC_KB, nativeAllocated); - proto.write(MemInfoProto.NativeProcess.HeapInfo.HEAP_FREE_KB, nativeFree); + proto.write(MemInfoProto.ProcessMemory.HeapInfo.HEAP_SIZE_KB, nativeMax); + proto.write(MemInfoProto.ProcessMemory.HeapInfo.HEAP_ALLOC_KB, nativeAllocated); + proto.write(MemInfoProto.ProcessMemory.HeapInfo.HEAP_FREE_KB, nativeFree); proto.end(nhToken); - final long dvToken = proto.start(MemInfoProto.NativeProcess.DALVIK_HEAP); - dumpHeap(proto, MemInfoProto.NativeProcess.HeapInfo.MEM_INFO, "Dalvik Heap", + final long dvToken = proto.start(MemInfoProto.ProcessMemory.DALVIK_HEAP); + dumpMemoryInfo(proto, MemInfoProto.ProcessMemory.HeapInfo.MEM_INFO, "Dalvik Heap", memInfo.dalvikPss, memInfo.dalvikSwappablePss, memInfo.dalvikSharedDirty, memInfo.dalvikPrivateDirty, memInfo.dalvikSharedClean, memInfo.dalvikPrivateClean, memInfo.hasSwappedOutPss, memInfo.dalvikSwappedOut, memInfo.dalvikSwappedOutPss); - proto.write(MemInfoProto.NativeProcess.HeapInfo.HEAP_SIZE_KB, dalvikMax); - proto.write(MemInfoProto.NativeProcess.HeapInfo.HEAP_ALLOC_KB, dalvikAllocated); - proto.write(MemInfoProto.NativeProcess.HeapInfo.HEAP_FREE_KB, dalvikFree); + proto.write(MemInfoProto.ProcessMemory.HeapInfo.HEAP_SIZE_KB, dalvikMax); + proto.write(MemInfoProto.ProcessMemory.HeapInfo.HEAP_ALLOC_KB, dalvikAllocated); + proto.write(MemInfoProto.ProcessMemory.HeapInfo.HEAP_FREE_KB, dalvikFree); proto.end(dvToken); int otherPss = memInfo.otherPss; @@ -2409,7 +2527,7 @@ public final class ActivityThread extends ClientTransactionHandler { if (myPss != 0 || mySharedDirty != 0 || myPrivateDirty != 0 || mySharedClean != 0 || myPrivateClean != 0 || (memInfo.hasSwappedOutPss ? mySwappedOutPss : mySwappedOut) != 0) { - dumpHeap(proto, MemInfoProto.NativeProcess.OTHER_HEAPS, + dumpMemoryInfo(proto, MemInfoProto.ProcessMemory.OTHER_HEAPS, Debug.MemoryInfo.getOtherLabel(i), myPss, mySwappablePss, mySharedDirty, myPrivateDirty, mySharedClean, myPrivateClean, @@ -2426,21 +2544,21 @@ public final class ActivityThread extends ClientTransactionHandler { } } - dumpHeap(proto, MemInfoProto.NativeProcess.UNKNOWN_HEAP, "Unknown", + dumpMemoryInfo(proto, MemInfoProto.ProcessMemory.UNKNOWN_HEAP, "Unknown", otherPss, otherSwappablePss, otherSharedDirty, otherPrivateDirty, otherSharedClean, otherPrivateClean, memInfo.hasSwappedOutPss, otherSwappedOut, otherSwappedOutPss); - final long tToken = proto.start(MemInfoProto.NativeProcess.TOTAL_HEAP); - dumpHeap(proto, MemInfoProto.NativeProcess.HeapInfo.MEM_INFO, "TOTAL", + final long tToken = proto.start(MemInfoProto.ProcessMemory.TOTAL_HEAP); + dumpMemoryInfo(proto, MemInfoProto.ProcessMemory.HeapInfo.MEM_INFO, "TOTAL", memInfo.getTotalPss(), memInfo.getTotalSwappablePss(), memInfo.getTotalSharedDirty(), memInfo.getTotalPrivateDirty(), memInfo.getTotalSharedClean(), memInfo.getTotalPrivateClean(), memInfo.hasSwappedOutPss, memInfo.getTotalSwappedOut(), memInfo.getTotalSwappedOutPss()); - proto.write(MemInfoProto.NativeProcess.HeapInfo.HEAP_SIZE_KB, nativeMax + dalvikMax); - proto.write(MemInfoProto.NativeProcess.HeapInfo.HEAP_ALLOC_KB, + proto.write(MemInfoProto.ProcessMemory.HeapInfo.HEAP_SIZE_KB, nativeMax + dalvikMax); + proto.write(MemInfoProto.ProcessMemory.HeapInfo.HEAP_ALLOC_KB, nativeAllocated + dalvikAllocated); - proto.write(MemInfoProto.NativeProcess.HeapInfo.HEAP_FREE_KB, nativeFree + dalvikFree); + proto.write(MemInfoProto.ProcessMemory.HeapInfo.HEAP_FREE_KB, nativeFree + dalvikFree); proto.end(tToken); if (dumpDalvik) { @@ -2458,7 +2576,7 @@ public final class ActivityThread extends ClientTransactionHandler { if (myPss != 0 || mySharedDirty != 0 || myPrivateDirty != 0 || mySharedClean != 0 || myPrivateClean != 0 || (memInfo.hasSwappedOutPss ? mySwappedOutPss : mySwappedOut) != 0) { - dumpHeap(proto, MemInfoProto.NativeProcess.DALVIK_DETAILS, + dumpMemoryInfo(proto, MemInfoProto.ProcessMemory.DALVIK_DETAILS, Debug.MemoryInfo.getOtherLabel(i), myPss, mySwappablePss, mySharedDirty, myPrivateDirty, mySharedClean, myPrivateClean, @@ -2468,24 +2586,24 @@ public final class ActivityThread extends ClientTransactionHandler { } } - final long asToken = proto.start(MemInfoProto.NativeProcess.APP_SUMMARY); - proto.write(MemInfoProto.NativeProcess.AppSummary.JAVA_HEAP_PSS_KB, + final long asToken = proto.start(MemInfoProto.ProcessMemory.APP_SUMMARY); + proto.write(MemInfoProto.ProcessMemory.AppSummary.JAVA_HEAP_PSS_KB, memInfo.getSummaryJavaHeap()); - proto.write(MemInfoProto.NativeProcess.AppSummary.NATIVE_HEAP_PSS_KB, + proto.write(MemInfoProto.ProcessMemory.AppSummary.NATIVE_HEAP_PSS_KB, memInfo.getSummaryNativeHeap()); - proto.write(MemInfoProto.NativeProcess.AppSummary.CODE_PSS_KB, memInfo.getSummaryCode()); - proto.write(MemInfoProto.NativeProcess.AppSummary.STACK_PSS_KB, memInfo.getSummaryStack()); - proto.write(MemInfoProto.NativeProcess.AppSummary.GRAPHICS_PSS_KB, + proto.write(MemInfoProto.ProcessMemory.AppSummary.CODE_PSS_KB, memInfo.getSummaryCode()); + proto.write(MemInfoProto.ProcessMemory.AppSummary.STACK_PSS_KB, memInfo.getSummaryStack()); + proto.write(MemInfoProto.ProcessMemory.AppSummary.GRAPHICS_PSS_KB, memInfo.getSummaryGraphics()); - proto.write(MemInfoProto.NativeProcess.AppSummary.PRIVATE_OTHER_PSS_KB, + proto.write(MemInfoProto.ProcessMemory.AppSummary.PRIVATE_OTHER_PSS_KB, memInfo.getSummaryPrivateOther()); - proto.write(MemInfoProto.NativeProcess.AppSummary.SYSTEM_PSS_KB, + proto.write(MemInfoProto.ProcessMemory.AppSummary.SYSTEM_PSS_KB, memInfo.getSummarySystem()); if (memInfo.hasSwappedOutPss) { - proto.write(MemInfoProto.NativeProcess.AppSummary.TOTAL_SWAP_PSS, + proto.write(MemInfoProto.ProcessMemory.AppSummary.TOTAL_SWAP_PSS, memInfo.getSummaryTotalSwapPss()); } else { - proto.write(MemInfoProto.NativeProcess.AppSummary.TOTAL_SWAP_PSS, + proto.write(MemInfoProto.ProcessMemory.AppSummary.TOTAL_SWAP_PSS, memInfo.getSummaryTotalSwap()); } proto.end(asToken); diff --git a/core/java/android/app/IApplicationThread.aidl b/core/java/android/app/IApplicationThread.aidl index b25d7782652f..893a41cc992b 100644 --- a/core/java/android/app/IApplicationThread.aidl +++ b/core/java/android/app/IApplicationThread.aidl @@ -110,6 +110,9 @@ oneway interface IApplicationThread { void dumpMemInfo(in ParcelFileDescriptor fd, in Debug.MemoryInfo mem, boolean checkin, boolean dumpInfo, boolean dumpDalvik, boolean dumpSummaryOnly, boolean dumpUnreachable, in String[] args); + void dumpMemInfoProto(in ParcelFileDescriptor fd, in Debug.MemoryInfo mem, + boolean dumpInfo, boolean dumpDalvik, boolean dumpSummaryOnly, boolean dumpUnreachable, + in String[] args); void dumpGfxInfo(in ParcelFileDescriptor fd, in String[] args); void dumpProvider(in ParcelFileDescriptor fd, IBinder servicetoken, in String[] args); diff --git a/core/java/android/app/IWallpaperManager.aidl b/core/java/android/app/IWallpaperManager.aidl index 49d58eb2c38d..80782e3adb08 100644 --- a/core/java/android/app/IWallpaperManager.aidl +++ b/core/java/android/app/IWallpaperManager.aidl @@ -155,4 +155,9 @@ interface IWallpaperManager { * Unregister a callback that was receiving color updates */ void unregisterWallpaperColorsCallback(IWallpaperManagerCallback cb, int userId); + + /** + * Called from SystemUI when it shows the AoD UI. + */ + void setInAmbientMode(boolean inAmbienMode); } diff --git a/core/java/android/app/WallpaperInfo.java b/core/java/android/app/WallpaperInfo.java index 9d40381fcefd..35a17892eec3 100644 --- a/core/java/android/app/WallpaperInfo.java +++ b/core/java/android/app/WallpaperInfo.java @@ -16,18 +16,15 @@ package android.app; -import org.xmlpull.v1.XmlPullParser; -import org.xmlpull.v1.XmlPullParserException; - import android.content.ComponentName; import android.content.Context; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; +import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.ResolveInfo; import android.content.pm.ServiceInfo; -import android.content.pm.PackageManager.NameNotFoundException; -import android.content.res.Resources.NotFoundException; import android.content.res.Resources; +import android.content.res.Resources.NotFoundException; import android.content.res.TypedArray; import android.content.res.XmlResourceParser; import android.graphics.drawable.Drawable; @@ -39,6 +36,9 @@ import android.util.AttributeSet; import android.util.Printer; import android.util.Xml; +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + import java.io.IOException; /** @@ -76,6 +76,7 @@ public final class WallpaperInfo implements Parcelable { final int mContextUriResource; final int mContextDescriptionResource; final boolean mShowMetadataInPreview; + final boolean mSupportsAmbientMode; /** * Constructor. @@ -89,15 +90,7 @@ public final class WallpaperInfo implements Parcelable { mService = service; ServiceInfo si = service.serviceInfo; - PackageManager pm = context.getPackageManager(); - String settingsActivityComponent = null; - int thumbnailRes = -1; - int authorRes = -1; - int descriptionRes = -1; - int contextUriRes = -1; - int contextDescriptionRes = -1; - boolean showMetadataInPreview = false; - + final PackageManager pm = context.getPackageManager(); XmlResourceParser parser = null; try { parser = si.loadXmlMetaData(pm, WallpaperService.SERVICE_META_DATA); @@ -123,27 +116,30 @@ public final class WallpaperInfo implements Parcelable { TypedArray sa = res.obtainAttributes(attrs, com.android.internal.R.styleable.Wallpaper); - settingsActivityComponent = sa.getString( + mSettingsActivityName = sa.getString( com.android.internal.R.styleable.Wallpaper_settingsActivity); - - thumbnailRes = sa.getResourceId( + + mThumbnailResource = sa.getResourceId( com.android.internal.R.styleable.Wallpaper_thumbnail, -1); - authorRes = sa.getResourceId( + mAuthorResource = sa.getResourceId( com.android.internal.R.styleable.Wallpaper_author, -1); - descriptionRes = sa.getResourceId( + mDescriptionResource = sa.getResourceId( com.android.internal.R.styleable.Wallpaper_description, -1); - contextUriRes = sa.getResourceId( + mContextUriResource = sa.getResourceId( com.android.internal.R.styleable.Wallpaper_contextUri, -1); - contextDescriptionRes = sa.getResourceId( + mContextDescriptionResource = sa.getResourceId( com.android.internal.R.styleable.Wallpaper_contextDescription, -1); - showMetadataInPreview = sa.getBoolean( + mShowMetadataInPreview = sa.getBoolean( com.android.internal.R.styleable.Wallpaper_showMetadataInPreview, false); + mSupportsAmbientMode = sa.getBoolean( + com.android.internal.R.styleable.Wallpaper_supportsAmbientMode, + false); sa.recycle(); } catch (NameNotFoundException e) { @@ -152,14 +148,6 @@ public final class WallpaperInfo implements Parcelable { } finally { if (parser != null) parser.close(); } - - mSettingsActivityName = settingsActivityComponent; - mThumbnailResource = thumbnailRes; - mAuthorResource = authorRes; - mDescriptionResource = descriptionRes; - mContextUriResource = contextUriRes; - mContextDescriptionResource = contextDescriptionRes; - mShowMetadataInPreview = showMetadataInPreview; } WallpaperInfo(Parcel source) { @@ -170,6 +158,7 @@ public final class WallpaperInfo implements Parcelable { mContextUriResource = source.readInt(); mContextDescriptionResource = source.readInt(); mShowMetadataInPreview = source.readInt() != 0; + mSupportsAmbientMode = source.readInt() != 0; mService = ResolveInfo.CREATOR.createFromParcel(source); } @@ -326,6 +315,16 @@ public final class WallpaperInfo implements Parcelable { } /** + * Returns whether a wallpaper was optimized or not for ambient mode. + * + * @return {@code true} if wallpaper can draw in ambient mode. + * @hide + */ + public boolean getSupportsAmbientMode() { + return mSupportsAmbientMode; + } + + /** * Return the class name of an activity that provides a settings UI for * the wallpaper. You can launch this activity be starting it with * an {@link android.content.Intent} whose action is MAIN and with an @@ -366,6 +365,7 @@ public final class WallpaperInfo implements Parcelable { dest.writeInt(mContextUriResource); dest.writeInt(mContextDescriptionResource); dest.writeInt(mShowMetadataInPreview ? 1 : 0); + dest.writeInt(mSupportsAmbientMode ? 1 : 0); mService.writeToParcel(dest, flags); } diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index eabfa96ef681..9ad990af3bb5 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -2787,6 +2787,7 @@ public class DevicePolicyManager { * @see UserManager#DISALLOW_UNIFIED_PASSWORD */ public boolean isUsingUnifiedPassword(@NonNull ComponentName admin) { + throwIfParentInstance("isUsingUnifiedPassword"); if (mService != null) { try { return mService.isUsingUnifiedPassword(admin); diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java index e2fd82d1f3e1..21e203bc50d7 100644 --- a/core/java/android/content/pm/PackageParser.java +++ b/core/java/android/content/pm/PackageParser.java @@ -87,7 +87,6 @@ import android.util.SparseArray; import android.util.TypedValue; import android.util.apk.ApkSignatureSchemeV2Verifier; import android.util.apk.ApkSignatureVerifier; -import android.util.apk.SignatureNotFoundException; import android.view.Gravity; import com.android.internal.R; @@ -1561,41 +1560,35 @@ public class PackageParser { boolean systemDir = (parseFlags & PARSE_IS_SYSTEM_DIR) != 0; int minSignatureScheme = ApkSignatureVerifier.VERSION_JAR_SIGNATURE_SCHEME; - if ((parseFlags & PARSE_IS_EPHEMERAL) != 0 || pkg.applicationInfo.isStaticSharedLibrary()) { + if (pkg.applicationInfo.isStaticSharedLibrary()) { // must use v2 signing scheme minSignatureScheme = ApkSignatureVerifier.VERSION_APK_SIGNATURE_SCHEME_V2; } - try { - ApkSignatureVerifier.Result verified = - ApkSignatureVerifier.verify(apkPath, minSignatureScheme, systemDir); - if (pkg.mCertificates == null) { - pkg.mCertificates = verified.certs; - pkg.mSignatures = verified.sigs; - pkg.mSigningKeys = new ArraySet<>(verified.certs.length); - for (int i = 0; i < verified.certs.length; i++) { - Certificate[] signerCerts = verified.certs[i]; - Certificate signerCert = signerCerts[0]; - pkg.mSigningKeys.add(signerCert.getPublicKey()); - } - } else { - if (!Signature.areExactMatch(pkg.mSignatures, verified.sigs)) { - throw new PackageParserException( - INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES, - apkPath + " has mismatched certificates"); - } - } - } catch (SignatureNotFoundException e) { + ApkSignatureVerifier.Result verified = + ApkSignatureVerifier.verify(apkPath, minSignatureScheme, systemDir); + if (verified.signatureSchemeVersion + < ApkSignatureVerifier.VERSION_APK_SIGNATURE_SCHEME_V2) { + // TODO (b/68860689): move this logic to packagemanagerserivce if ((parseFlags & PARSE_IS_EPHEMERAL) != 0) { throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES, - "No APK Signature Scheme v2 signature in ephemeral package " + apkPath, - e); + "No APK Signature Scheme v2 signature in ephemeral package " + apkPath); } - if (pkg.applicationInfo.isStaticSharedLibrary()) { - throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES, - "Static shared libs must use v2 signature scheme " + apkPath); + } + if (pkg.mCertificates == null) { + pkg.mCertificates = verified.certs; + pkg.mSignatures = verified.sigs; + pkg.mSigningKeys = new ArraySet<>(verified.certs.length); + for (int i = 0; i < verified.certs.length; i++) { + Certificate[] signerCerts = verified.certs[i]; + Certificate signerCert = signerCerts[0]; + pkg.mSigningKeys.add(signerCert.getPublicKey()); + } + } else { + if (!Signature.areExactMatch(pkg.mSignatures, verified.sigs)) { + throw new PackageParserException( + INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES, + apkPath + " has mismatched certificates"); } - throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES, - "No APK Signature Scheme v2 signature in package " + apkPath, e); } } diff --git a/core/java/android/content/pm/ShortcutManager.java b/core/java/android/content/pm/ShortcutManager.java index 61b0eb0b51f8..30222b74bdc1 100644 --- a/core/java/android/content/pm/ShortcutManager.java +++ b/core/java/android/content/pm/ShortcutManager.java @@ -36,15 +36,26 @@ import com.android.internal.annotations.VisibleForTesting; import java.util.List; /** - * The ShortcutManager manages an app's <em>shortcuts</em>. Shortcuts provide users with quick - * access to activities other than an app's main activity in the currently-active launcher, provided - * that the launcher supports app shortcuts. For example, an email app may publish the "compose new - * email" action, which will directly open the compose activity. The {@link ShortcutInfo} class - * contains information about each of the shortcuts themselves. + * The ShortcutManager performs operations on an app's set of <em>shortcuts</em>. The + * {@link ShortcutInfo} class contains information about each of the shortcuts themselves. + * + * <p>An app's shortcuts represent specific tasks and actions that users can perform within your + * app. When a user selects a shortcut in the currently-active launcher, your app opens an activity + * other than the app's starting activity, provided that the currently-active launcher supports app + * shortcuts.</p> + * + * <p>The types of shortcuts that you create for your app depend on the app's key use cases. For + * example, an email app may publish the "compose new email" shortcut, which allows the app to + * directly open the compose activity.</p> + * + * <p class="note"><b>Note:</b> Only main activities—activities that handle the + * {@link Intent#ACTION_MAIN} action and the {@link Intent#CATEGORY_LAUNCHER} category—can + * have shortcuts. If an app has multiple main activities, you need to define the set of shortcuts + * for <em>each</em> activity. * * <p>This page discusses the implementation details of the <code>ShortcutManager</code> class. For - * guidance on performing operations on app shortcuts within your app, see the - * <a href="/guide/topics/ui/shortcuts.html">App Shortcuts</a> feature guide. + * definitions of key terms and guidance on performing operations on shortcuts within your app, see + * the <a href="/guide/topics/ui/shortcuts.html">App Shortcuts</a> feature guide. * * <h3>Shortcut characteristics</h3> * @@ -69,8 +80,8 @@ import java.util.List; * <ul> * <li>The user removes it. * <li>The publisher app associated with the shortcut is uninstalled. - * <li>The user performs the clear data action on the publisher app from the device's - * <b>Settings</b> app. + * <li>The user selects <b>Clear data</b> from the publisher app's <i>Storage</i> screen, within + * the system's <b>Settings</b> app. * </ul> * * <p>Because the system performs @@ -83,12 +94,17 @@ import java.util.List; * * <p>When the launcher displays an app's shortcuts, they should appear in the following order: * - * <ul> - * <li>Static shortcuts (if {@link ShortcutInfo#isDeclaredInManifest()} is {@code true}), - * and then show dynamic shortcuts (if {@link ShortcutInfo#isDynamic()} is {@code true}). - * <li>Within each shortcut type (static and dynamic), sort the shortcuts in order of increasing - * rank according to {@link ShortcutInfo#getRank()}. - * </ul> + * <ol> + * <li><b>Static shortcuts:</b> Shortcuts whose {@link ShortcutInfo#isDeclaredInManifest()} method + * returns {@code true}.</li> + * <li><b>Dynamic shortcuts:</b> Shortcuts whose {@link ShortcutInfo#isDynamic()} method returns + * {@code true}.</li> + * </ol> + * + * <p>Within each shortcut type (static and dynamic), shortcuts are sorted in order of increasing + * rank according to {@link ShortcutInfo#getRank()}.</p> + * + * <h4>Shortcut ranks</h4> * * <p>Shortcut ranks are non-negative, sequential integers that determine the order in which * shortcuts appear, assuming that the shortcuts are all in the same category. You can update ranks @@ -103,64 +119,99 @@ import java.util.List; * * <h3>Options for static shortcuts</h3> * - * The following list includes descriptions for the different attributes within a static shortcut: + * The following list includes descriptions for the different attributes within a static shortcut. + * You must provide a value for {@code android:shortcutId} and {@code android:shortcutShortLabel}; + * all other values are optional. + * * <dl> * <dt>{@code android:shortcutId}</dt> - * <dd>Mandatory shortcut ID. - * <p> - * This must be a string literal. - * A resource string, such as <code>@string/foo</code>, cannot be used. + * <dd><p>A string literal, which represents the shortcut when a {@code ShortcutManager} object + * performs operations on it.</p> + * <p class="note"><b>Note: </b>You cannot set this attribute's value to a resource string, such + * as <code>@string/foo</code>.</p> * </dd> * * <dt>{@code android:enabled}</dt> - * <dd>Default is {@code true}. Can be set to {@code false} in order - * to disable a static shortcut that was published in a previous version and set a custom - * disabled message. If a custom disabled message is not needed, then a static shortcut can - * be simply removed from the XML file rather than keeping it with {@code enabled="false"}.</dd> + * <dd><p>Whether the user can interact with the shortcut from a supported launcher.</p> + * <p>The default value is {@code true}. If you set it to {@code false}, you should also set + * {@code android:shortcutDisabledMessage} to a message that explains why you've disabled the + * shortcut. If you don't think you need to provide such a message, it's easiest to just remove + * the shortcut from the XML file entirely, rather than changing the values of the shortcut's + * {@code android:enabled} and {@code android:shortcutDisabledMessage} attributes. + * </dd> * * <dt>{@code android:icon}</dt> - * <dd>Shortcut icon.</dd> + * <dd><p>The <a href="/topic/performance/graphics/index.html">bitmap</a> or + * <a href="/guide/practices/ui_guidelines/icon_design_adaptive.html">adaptive icon</a> that the + * launcher uses when displaying the shortcut to the user. This value can be either the path to an + * image or the resource file that contains the image. Use adaptive icons whenever possible to + * improve performance and consistency.</p> + * <p class="note"><b>Note: </b>Shortcut icons cannot include + * <a href="/training/material/drawables.html#DrawableTint">tints</a>. + * </dd> * * <dt>{@code android:shortcutShortLabel}</dt> - * <dd>Mandatory shortcut short label. - * See {@link ShortcutInfo.Builder#setShortLabel(CharSequence)}. - * <p> - * This must be a resource string, such as <code>@string/shortcut_label</code>. + * <dd><p>A concise phrase that describes the shortcut's purpose. For more information, see + * {@link ShortcutInfo.Builder#setShortLabel(CharSequence)}.</p> + * <p class="note"><b>Note: </b>This attribute's value must be a resource string, such as + * <code>@string/shortcut_short_label</code>.</p> * </dd> * * <dt>{@code android:shortcutLongLabel}</dt> - * <dd>Shortcut long label. - * See {@link ShortcutInfo.Builder#setLongLabel(CharSequence)}. - * <p> - * This must be a resource string, such as <code>@string/shortcut_long_label</code>. + * <dd><p>An extended phrase that describes the shortcut's purpose. If there's enough space, the + * launcher displays this value instead of {@code android:shortcutShortLabel}. For more + * information, see {@link ShortcutInfo.Builder#setLongLabel(CharSequence)}.</p> + * <p class="note"><b>Note: </b>This attribute's value must be a resource string, such as + * <code>@string/shortcut_long_label</code>.</p> * </dd> * * <dt>{@code android:shortcutDisabledMessage}</dt> - * <dd>When {@code android:enabled} is set to - * {@code false}, this attribute is used to display a custom disabled message. - * <p> - * This must be a resource string, such as <code>@string/shortcut_disabled_message</code>. + * <dd><p>The message that appears in a supported launcher when the user attempts to launch a + * disabled shortcut. The message should explain to the user why the shortcut is now disabled. + * This attribute's value has no effect if {@code android:enabled} is {@code true}.</p> + * <p class="note"><b>Note: </b>This attribute's value must be a resource string, such as + * <code>@string/shortcut_disabled_message</code>.</p> * </dd> + * </dl> + * + * <h3>Inner elements that define static shortcuts</h3> + * + * <p>The XML file that lists an app's static shortcuts supports the following elements inside each + * {@code <shortcut>} element. You must include an {@code intent} inner element for each + * static shortcut that you define.</p> * + * <dl> * <dt>{@code intent}</dt> - * <dd>Intent to launch when the user selects the shortcut. - * {@code android:action} is mandatory. - * See <a href="{@docRoot}guide/topics/ui/settings.html#Intents">Using intents</a> for the - * other supported tags. - * <p>You can provide multiple intents for a single shortcut so that the last defined activity is - * launched with the other activities in the + * <dd><p>The action that the system launches when the user selects the shortcut. This intent must + * provide a value for the {@code android:action} attribute.</p> + * <p>You can provide multiple intents for a single shortcut. If you do so, the last defined + * activity is launched, and the other activities are placed in the * <a href="/guide/components/tasks-and-back-stack.html">back stack</a>. See - * {@link android.app.TaskStackBuilder} for details. - * <p><b>Note:</b> String resources may not be used within an {@code <intent>} element. + * <a href="/guide/topics/ui/shortcuts.html#static">Using Static Shortcuts</a> and the + * {@link android.app.TaskStackBuilder} class reference for details.</p> + * <p class="note"><b>Note:</b> This {@code intent} element cannot include string resources.</p> + * <p>To learn more about how to configure intents, see + * <a href="{@docRoot}guide/topics/ui/settings.html#Intents">Using intents</a>.</p> * </dd> + * * <dt>{@code categories}</dt> - * <dd>Specify shortcut categories. Currently only - * {@link ShortcutInfo#SHORTCUT_CATEGORY_CONVERSATION} is defined in the framework. + * <dd><p>Provides a grouping for the types of actions that your app's shortcuts perform, such as + * creating new chat messages.</p> + * <p>For a list of supported shortcut categories, see the {@link ShortcutInfo} class reference + * for a list of supported shortcut categories. * </dd> * </dl> * * <h3>Updating shortcuts</h3> * + * <p>Each app's launcher icon can contain at most {@link #getMaxShortcutCountPerActivity()} number + * of static and dynamic shortcuts combined. There is no limit to the number of pinned shortcuts + * that an app can create, though. + * + * <p>When a dynamic shortcut is pinned, even when the publisher removes it as a dynamic shortcut, + * the pinned shortcut is still visible and launchable. This allows an app to have more than + * {@link #getMaxShortcutCountPerActivity()} number of shortcuts. + * * <p>As an example, suppose {@link #getMaxShortcutCountPerActivity()} is 5: * <ol> * <li>A chat app publishes 5 dynamic shortcuts for the 5 most recent @@ -168,18 +219,13 @@ import java.util.List; * * <li>The user pins all 5 of the shortcuts. * - * <li>Later, the user has started 3 additional conversations (c6, c7, and c8), - * so the publisher app - * re-publishes its dynamic shortcuts. The new dynamic shortcut list is: - * c4, c5, ..., c8. - * The publisher app has to remove c1, c2, and c3 because it can't have more than - * 5 dynamic shortcuts. - * - * <li>However, even though c1, c2, and c3 are no longer dynamic shortcuts, the pinned - * shortcuts for these conversations are still available and launchable. - * - * <li>At this point, the user can access a total of 8 shortcuts that link to activities in - * the publisher app, including the 3 pinned shortcuts, even though an app can have at most 5 + * <li>Later, the user has started 3 additional conversations (c6, c7, and c8), so the publisher + * app re-publishes its dynamic shortcuts. The new dynamic shortcut list is: c4, c5, ..., c8. + * <p>The publisher app has to remove c1, c2, and c3 because it can't have more than 5 dynamic + * shortcuts. However, c1, c2, and c3 are still pinned shortcuts that the user can access and + * launch. + * <p>At this point, the user can access a total of 8 shortcuts that link to activities in the + * publisher app, including the 3 pinned shortcuts, even though an app can have at most 5 * dynamic shortcuts. * * <li>The app can use {@link #updateShortcuts(List)} to update <em>any</em> of the existing @@ -196,44 +242,23 @@ import java.util.List; * Dynamic shortcuts can be published with any set of {@link Intent#addFlags Intent} flags. * Typically, {@link Intent#FLAG_ACTIVITY_CLEAR_TASK} is specified, possibly along with other * flags; otherwise, if the app is already running, the app is simply brought to - * the foreground, and the target activity may not appear. + * the foreground, and the target activity might not appear. * * <p>Static shortcuts <b>cannot</b> have custom intent flags. * The first intent of a static shortcut will always have {@link Intent#FLAG_ACTIVITY_NEW_TASK} * and {@link Intent#FLAG_ACTIVITY_CLEAR_TASK} set. This means, when the app is already running, all - * the existing activities in your app will be destroyed when a static shortcut is launched. + * the existing activities in your app are destroyed when a static shortcut is launched. * If this behavior is not desirable, you can use a <em>trampoline activity</em>, or an invisible * activity that starts another activity in {@link Activity#onCreate}, then calls * {@link Activity#finish()}: * <ol> * <li>In the <code>AndroidManifest.xml</code> file, the trampoline activity should include the * attribute assignment {@code android:taskAffinity=""}. - * <li>In the shortcuts resource file, the intent within the static shortcut should point at + * <li>In the shortcuts resource file, the intent within the static shortcut should reference * the trampoline activity. * </ol> * - * <h3>Handling system locale changes</h3> - * - * <p>Apps should update dynamic and pinned shortcuts when the system locale changes using the - * {@link Intent#ACTION_LOCALE_CHANGED} broadcast. When the system locale changes, - * <a href="/guide/topics/ui/shortcuts.html#rate-limit">rate limiting</a> is reset, so even - * background apps can add and update dynamic shortcuts until the rate limit is reached again. - * - * <h3>Shortcut limits</h3> - * - * <p>Only main activities—activities that handle the {@code MAIN} action and the - * {@code LAUNCHER} category—can have shortcuts. If an app has multiple main activities, you - * need to define the set of shortcuts for <em>each</em> activity. - * - * <p>Each launcher icon can have at most {@link #getMaxShortcutCountPerActivity()} number of - * static and dynamic shortcuts combined. There is no limit to the number of pinned shortcuts that - * an app can create. - * - * <p>When a dynamic shortcut is pinned, even when the publisher removes it as a dynamic shortcut, - * the pinned shortcut is still visible and launchable. This allows an app to have more than - * {@link #getMaxShortcutCountPerActivity()} number of shortcuts. - * - * <h4>Rate limiting</h4> + * <h3>Rate limiting</h3> * * <p>When <a href="/guide/topics/ui/shortcuts.html#rate-limit">rate limiting</a> is active, * {@link #isRateLimitingActive()} returns {@code true}. @@ -243,8 +268,20 @@ import java.util.List; * <ul> * <li>An app comes to the foreground. * <li>The system locale changes. - * <li>The user performs the <strong>inline reply</strong> action on a notification. + * <li>The user performs the <a href="/guide/topics/ui/notifiers/notifications.html#direct">inline + * reply</a> action on a notification. * </ul> + * + * <h3>Handling system locale changes</h3> + * + * <p>Apps should update dynamic and pinned shortcuts when they receive the + * {@link Intent#ACTION_LOCALE_CHANGED} broadcast, indicating that the system locale has changed. + * <p>When the system locale changes, <a href="/guide/topics/ui/shortcuts.html#rate-limit">rate + * limiting</a> is reset, so even background apps can add and update dynamic shortcuts until the + * rate limit is reached again. + * + * <h3>Retrieving class instances</h3> + * <!-- Provides a heading for the content filled in by the @SystemService annotation below --> */ @SystemService(Context.SHORTCUT_SERVICE) public class ShortcutManager { diff --git a/core/java/android/database/sqlite/SQLiteCompatibilityWalFlags.java b/core/java/android/database/sqlite/SQLiteCompatibilityWalFlags.java index e02e68dced95..5bf3a7c43640 100644 --- a/core/java/android/database/sqlite/SQLiteCompatibilityWalFlags.java +++ b/core/java/android/database/sqlite/SQLiteCompatibilityWalFlags.java @@ -37,7 +37,7 @@ public class SQLiteCompatibilityWalFlags { private static final String TAG = "SQLiteCompatibilityWalFlags"; - private static volatile boolean sInitialized = true; // Temporarily disable flags + private static volatile boolean sInitialized; private static volatile boolean sFlagsSet; private static volatile boolean sCompatibilityWalSupported; private static volatile String sWALSyncMode; diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java index 8071e8b83738..11d338d05c68 100644 --- a/core/java/android/net/ConnectivityManager.java +++ b/core/java/android/net/ConnectivityManager.java @@ -1794,7 +1794,7 @@ public class ConnectivityManager { ITelephony it = ITelephony.Stub.asInterface(b); int subId = SubscriptionManager.getDefaultDataSubscriptionId(); Log.d("ConnectivityManager", "getMobileDataEnabled()+ subId=" + subId); - boolean retVal = it.getDataEnabled(subId); + boolean retVal = it.isUserDataEnabled(subId); Log.d("ConnectivityManager", "getMobileDataEnabled()- subId=" + subId + " retVal=" + retVal); return retVal; diff --git a/core/java/android/net/TrafficStats.java b/core/java/android/net/TrafficStats.java index 954e59c2c424..d701550817df 100644 --- a/core/java/android/net/TrafficStats.java +++ b/core/java/android/net/TrafficStats.java @@ -19,6 +19,7 @@ package android.net; import android.annotation.RequiresPermission; import android.annotation.SuppressLint; import android.annotation.SystemApi; +import android.annotation.TestApi; import android.app.DownloadManager; import android.app.backup.BackupManager; import android.app.usage.NetworkStatsManager; @@ -154,6 +155,8 @@ public class TrafficStats { private static Object sProfilingLock = new Object(); + private static final String LOOPBACK_IFACE = "lo"; + /** * Set active tag to use when accounting {@link Socket} traffic originating * from the current thread. Only one active tag per thread is supported. @@ -542,6 +545,30 @@ public class TrafficStats { return nativeGetIfaceStat(iface, TYPE_RX_BYTES); } + /** {@hide} */ + @TestApi + public static long getLoopbackTxPackets() { + return nativeGetIfaceStat(LOOPBACK_IFACE, TYPE_TX_PACKETS); + } + + /** {@hide} */ + @TestApi + public static long getLoopbackRxPackets() { + return nativeGetIfaceStat(LOOPBACK_IFACE, TYPE_RX_PACKETS); + } + + /** {@hide} */ + @TestApi + public static long getLoopbackTxBytes() { + return nativeGetIfaceStat(LOOPBACK_IFACE, TYPE_TX_BYTES); + } + + /** {@hide} */ + @TestApi + public static long getLoopbackRxBytes() { + return nativeGetIfaceStat(LOOPBACK_IFACE, TYPE_RX_BYTES); + } + /** * Return number of packets transmitted since device boot. Counts packets * across all network interfaces, and always increases monotonically since diff --git a/core/java/android/os/Debug.java b/core/java/android/os/Debug.java index 2acf36fed85f..848ab88d3cbc 100644 --- a/core/java/android/os/Debug.java +++ b/core/java/android/os/Debug.java @@ -1136,7 +1136,7 @@ public final class Debug int intervalUs) { VMDebug.startMethodTracing(fixTracePath(tracePath), bufferSize, 0, true, intervalUs); } - + /** * Formats name of trace log file for method tracing. */ @@ -1706,11 +1706,11 @@ public final class Debug * Retrieves information about this processes memory usages. This information is broken down by * how much is in use by dalvik, the native heap, and everything else. * - * <p><b>Note:</b> this method directly retrieves memory information for the give process + * <p><b>Note:</b> this method directly retrieves memory information for the given process * from low-level data available to it. It may not be able to retrieve information about * some protected allocations, such as graphics. If you want to be sure you can see - * all information about allocations by the process, use instead - * {@link android.app.ActivityManager#getProcessMemoryInfo(int[])}.</p> + * all information about allocations by the process, use + * {@link android.app.ActivityManager#getProcessMemoryInfo(int[])} instead.</p> */ public static native void getMemoryInfo(MemoryInfo memoryInfo); diff --git a/core/java/android/os/IIncidentManager.aidl b/core/java/android/os/IIncidentManager.aidl index 1a76648c2565..b67b99fabb9e 100644 --- a/core/java/android/os/IIncidentManager.aidl +++ b/core/java/android/os/IIncidentManager.aidl @@ -16,7 +16,6 @@ package android.os; -import android.os.IIncidentReportCompletedListener; import android.os.IIncidentReportStatusListener; import android.os.IncidentReportArgs; diff --git a/core/java/android/os/storage/StorageVolume.java b/core/java/android/os/storage/StorageVolume.java index 1fc0b820cf06..070b8c1b0008 100644 --- a/core/java/android/os/storage/StorageVolume.java +++ b/core/java/android/os/storage/StorageVolume.java @@ -19,7 +19,6 @@ package android.os.storage; import android.annotation.Nullable; import android.content.Context; import android.content.Intent; -import android.net.TrafficStats; import android.net.Uri; import android.os.Environment; import android.os.Parcel; @@ -78,13 +77,11 @@ import java.io.File; public final class StorageVolume implements Parcelable { private final String mId; - private final int mStorageId; private final File mPath; private final String mDescription; private final boolean mPrimary; private final boolean mRemovable; private final boolean mEmulated; - private final long mMtpReserveSize; private final boolean mAllowMassStorage; private final long mMaxFileSize; private final UserHandle mOwner; @@ -121,17 +118,15 @@ public final class StorageVolume implements Parcelable { public static final int STORAGE_ID_PRIMARY = 0x00010001; /** {@hide} */ - public StorageVolume(String id, int storageId, File path, String description, boolean primary, - boolean removable, boolean emulated, long mtpReserveSize, boolean allowMassStorage, + public StorageVolume(String id, File path, String description, boolean primary, + boolean removable, boolean emulated, boolean allowMassStorage, long maxFileSize, UserHandle owner, String fsUuid, String state) { mId = Preconditions.checkNotNull(id); - mStorageId = storageId; mPath = Preconditions.checkNotNull(path); mDescription = Preconditions.checkNotNull(description); mPrimary = primary; mRemovable = removable; mEmulated = emulated; - mMtpReserveSize = mtpReserveSize; mAllowMassStorage = allowMassStorage; mMaxFileSize = maxFileSize; mOwner = Preconditions.checkNotNull(owner); @@ -141,13 +136,11 @@ public final class StorageVolume implements Parcelable { private StorageVolume(Parcel in) { mId = in.readString(); - mStorageId = in.readInt(); mPath = new File(in.readString()); mDescription = in.readString(); mPrimary = in.readInt() != 0; mRemovable = in.readInt() != 0; mEmulated = in.readInt() != 0; - mMtpReserveSize = in.readLong(); mAllowMassStorage = in.readInt() != 0; mMaxFileSize = in.readLong(); mOwner = in.readParcelable(null); @@ -211,34 +204,6 @@ public final class StorageVolume implements Parcelable { } /** - * Returns the MTP storage ID for the volume. - * this is also used for the storage_id column in the media provider. - * - * @return MTP storage ID - * @hide - */ - public int getStorageId() { - return mStorageId; - } - - /** - * Number of megabytes of space to leave unallocated by MTP. - * MTP will subtract this value from the free space it reports back - * to the host via GetStorageInfo, and will not allow new files to - * be added via MTP if there is less than this amount left free in the storage. - * If MTP has dedicated storage this value should be zero, but if MTP is - * sharing storage with the rest of the system, set this to a positive value - * to ensure that MTP activity does not result in the storage being - * too close to full. - * - * @return MTP reserve space - * @hide - */ - public int getMtpReserveSpace() { - return (int) (mMtpReserveSize / TrafficStats.MB_IN_BYTES); - } - - /** * Returns true if this volume can be shared via USB mass storage. * * @return whether mass storage is allowed @@ -385,13 +350,11 @@ public final class StorageVolume implements Parcelable { pw.println("StorageVolume:"); pw.increaseIndent(); pw.printPair("mId", mId); - pw.printPair("mStorageId", mStorageId); pw.printPair("mPath", mPath); pw.printPair("mDescription", mDescription); pw.printPair("mPrimary", mPrimary); pw.printPair("mRemovable", mRemovable); pw.printPair("mEmulated", mEmulated); - pw.printPair("mMtpReserveSize", mMtpReserveSize); pw.printPair("mAllowMassStorage", mAllowMassStorage); pw.printPair("mMaxFileSize", mMaxFileSize); pw.printPair("mOwner", mOwner); @@ -420,13 +383,11 @@ public final class StorageVolume implements Parcelable { @Override public void writeToParcel(Parcel parcel, int flags) { parcel.writeString(mId); - parcel.writeInt(mStorageId); parcel.writeString(mPath.toString()); parcel.writeString(mDescription); parcel.writeInt(mPrimary ? 1 : 0); parcel.writeInt(mRemovable ? 1 : 0); parcel.writeInt(mEmulated ? 1 : 0); - parcel.writeLong(mMtpReserveSize); parcel.writeInt(mAllowMassStorage ? 1 : 0); parcel.writeLong(mMaxFileSize); parcel.writeParcelable(mOwner, flags); diff --git a/core/java/android/os/storage/VolumeInfo.java b/core/java/android/os/storage/VolumeInfo.java index 76f79f13d9a7..d3877cac17b0 100644 --- a/core/java/android/os/storage/VolumeInfo.java +++ b/core/java/android/os/storage/VolumeInfo.java @@ -343,9 +343,7 @@ public class VolumeInfo implements Parcelable { String description = null; String derivedFsUuid = fsUuid; - long mtpReserveSize = 0; long maxFileSize = 0; - int mtpStorageId = StorageVolume.STORAGE_ID_INVALID; if (type == TYPE_EMULATED) { emulated = true; @@ -356,12 +354,6 @@ public class VolumeInfo implements Parcelable { derivedFsUuid = privateVol.fsUuid; } - if (isPrimary()) { - mtpStorageId = StorageVolume.STORAGE_ID_PRIMARY; - } - - mtpReserveSize = storage.getStorageLowBytes(userPath); - if (ID_EMULATED_INTERNAL.equals(id)) { removable = false; } else { @@ -374,14 +366,6 @@ public class VolumeInfo implements Parcelable { description = storage.getBestVolumeDescription(this); - if (isPrimary()) { - mtpStorageId = StorageVolume.STORAGE_ID_PRIMARY; - } else { - // Since MediaProvider currently persists this value, we need a - // value that is stable over time. - mtpStorageId = buildStableMtpStorageId(fsUuid); - } - if ("vfat".equals(fsType)) { maxFileSize = 4294967295L; } @@ -394,8 +378,8 @@ public class VolumeInfo implements Parcelable { description = context.getString(android.R.string.unknownName); } - return new StorageVolume(id, mtpStorageId, userPath, description, isPrimary(), removable, - emulated, mtpReserveSize, allowMassStorage, maxFileSize, new UserHandle(userId), + return new StorageVolume(id, userPath, description, isPrimary(), removable, + emulated, allowMassStorage, maxFileSize, new UserHandle(userId), derivedFsUuid, envState); } diff --git a/core/java/android/privacy/DifferentialPrivacyConfig.java b/core/java/android/privacy/DifferentialPrivacyConfig.java new file mode 100644 index 000000000000..e14893e7dd03 --- /dev/null +++ b/core/java/android/privacy/DifferentialPrivacyConfig.java @@ -0,0 +1,34 @@ +/* + * Copyright 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.privacy; + +/** + * An interface for differential privacy configuration. + * {@link DifferentialPrivacyEncoder} will apply this configuration to do differential privacy + * encoding. + * + * @hide + */ +public interface DifferentialPrivacyConfig { + + /** + * Returns the name of the algorithm used in differential privacy config. + * + * @return The name of the algorithm + */ + String getAlgorithm(); +} diff --git a/core/java/android/privacy/DifferentialPrivacyEncoder.java b/core/java/android/privacy/DifferentialPrivacyEncoder.java new file mode 100644 index 000000000000..9355d6a5c723 --- /dev/null +++ b/core/java/android/privacy/DifferentialPrivacyEncoder.java @@ -0,0 +1,78 @@ +/* + * Copyright 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.privacy; + +/** + * An interface for differential privacy encoder. + * Applications can use it to convert privacy sensitive data to privacy protected report. + * There is no decoder implemented in Android as it is not possible decode a single report by + * design. + * + * <p>Each type of log should have its own encoder, otherwise it may leak + * some information about Permanent Randomized Response(PRR, is used to create a “noisy” + * answer which is memoized by the client and permanently reused in place of the real answer). + * + * <p>Some encoders may not support all encoding methods, and it will throw {@link + * UnsupportedOperationException} if you call unsupported encoding method. + * + * <p><b>WARNING:</b> Privacy protection works only when encoder uses a suitable DP configuration, + * and the configuration and algorithm that is suitable is highly dependent on the use case. + * If the configuration is not suitable for the use case, it may hurt privacy or utility or both. + * + * @hide + */ +public interface DifferentialPrivacyEncoder { + + /** + * Apply differential privacy to encode a string. + * + * @param original An arbitrary string + * @return Differential privacy encoded bytes derived from the string + */ + byte[] encodeString(String original); + + /** + * Apply differential privacy to encode a boolean. + * + * @param original An arbitrary boolean. + * @return Differential privacy encoded bytes derived from the boolean + */ + byte[] encodeBoolean(boolean original); + + /** + * Apply differential privacy to encode sequence of bytes. + * + * @param original An arbitrary byte array. + * @return Differential privacy encoded bytes derived from the bytes + */ + byte[] encodeBits(byte[] original); + + /** + * Returns the configuration that this encoder is using. + */ + DifferentialPrivacyConfig getConfig(); + + /** + * Return True if the output from encoder is NOT securely randomized, otherwise encoder should + * be secure to randomize input. + * + * <b> A non-secure encoder is intended only for testing only and must not be used to process + * real data. + * </b> + */ + boolean isInsecureEncoderForTest(); +} diff --git a/core/java/android/privacy/internal/longitudinalreporting/LongitudinalReportingConfig.java b/core/java/android/privacy/internal/longitudinalreporting/LongitudinalReportingConfig.java new file mode 100644 index 000000000000..ee910fc1a39d --- /dev/null +++ b/core/java/android/privacy/internal/longitudinalreporting/LongitudinalReportingConfig.java @@ -0,0 +1,107 @@ +/* + * Copyright 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.privacy.internal.longitudinalreporting; + +import android.privacy.DifferentialPrivacyConfig; +import android.privacy.internal.rappor.RapporConfig; +import android.text.TextUtils; + +import com.android.internal.util.Preconditions; + +/** + * A class to store {@link LongitudinalReportingEncoder} configuration. + * + * <ul> + * <li> f is probability to flip input value, used in IRR. + * <li> p is probability to override input value, used in PRR1. + * <li> q is probability to set input value as 1 when result of PRR(p) is true, used in PRR2. + * </ul> + * + * @hide + */ +public class LongitudinalReportingConfig implements DifferentialPrivacyConfig { + + private static final String ALGORITHM_NAME = "LongitudinalReporting"; + + // Probability to flip input value. + private final double mProbabilityF; + + // Probability to override original value. + private final double mProbabilityP; + // Probability to override value with 1. + private final double mProbabilityQ; + + // IRR config to randomize original value + private final RapporConfig mIRRConfig; + + private final String mEncoderId; + + /** + * Constructor to create {@link LongitudinalReportingConfig} used for {@link + * LongitudinalReportingEncoder} + * + * @param encoderId Unique encoder id. + * @param probabilityF Probability F used in Longitudinal Reporting algorithm. + * @param probabilityP Probability P used in Longitudinal Reporting algorithm. This will be + * quantized to the nearest 1/256. + * @param probabilityQ Probability Q used in Longitudinal Reporting algorithm. This will be + * quantized to the nearest 1/256. + */ + public LongitudinalReportingConfig(String encoderId, double probabilityF, + double probabilityP, double probabilityQ) { + Preconditions.checkArgument(probabilityF >= 0 && probabilityF <= 1, + "probabilityF must be in range [0.0, 1.0]"); + this.mProbabilityF = probabilityF; + Preconditions.checkArgument(probabilityP >= 0 && probabilityP <= 1, + "probabilityP must be in range [0.0, 1.0]"); + this.mProbabilityP = probabilityP; + Preconditions.checkArgument(probabilityQ >= 0 && probabilityQ <= 1, + "probabilityQ must be in range [0.0, 1.0]"); + this.mProbabilityQ = probabilityQ; + Preconditions.checkArgument(!TextUtils.isEmpty(encoderId), "encoderId cannot be empty"); + mEncoderId = encoderId; + mIRRConfig = new RapporConfig(encoderId, 1, 0.0, probabilityF, 1 - probabilityF, 1, 1); + } + + @Override + public String getAlgorithm() { + return ALGORITHM_NAME; + } + + RapporConfig getIRRConfig() { + return mIRRConfig; + } + + double getProbabilityP() { + return mProbabilityP; + } + + double getProbabilityQ() { + return mProbabilityQ; + } + + String getEncoderId() { + return mEncoderId; + } + + @Override + public String toString() { + return String.format("EncoderId: %s, ProbabilityF: %.3f, ProbabilityP: %.3f" + + ", ProbabilityQ: %.3f", + mEncoderId, mProbabilityF, mProbabilityP, mProbabilityQ); + } +} diff --git a/core/java/android/privacy/internal/longitudinalreporting/LongitudinalReportingEncoder.java b/core/java/android/privacy/internal/longitudinalreporting/LongitudinalReportingEncoder.java new file mode 100644 index 000000000000..219868d663d2 --- /dev/null +++ b/core/java/android/privacy/internal/longitudinalreporting/LongitudinalReportingEncoder.java @@ -0,0 +1,170 @@ +/* + * Copyright 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.privacy.internal.longitudinalreporting; + +import android.privacy.DifferentialPrivacyEncoder; +import android.privacy.internal.rappor.RapporConfig; +import android.privacy.internal.rappor.RapporEncoder; + +import com.android.internal.annotations.VisibleForTesting; + +/** + * Differential privacy encoder by using Longitudinal Reporting algorithm. + * + * <b> + * Notes: It supports encodeBoolean() only for now. + * </b> + * + * <p> + * Definition: + * PRR = Permanent Randomized Response + * IRR = Instantaneous Randomized response + * + * Algorithm: + * Step 1: Create long-term secrets x(ignoreOriginalInput)=Ber(P), y=Ber(Q), where Ber denotes + * Bernoulli distribution on {0, 1}, and we use it as a long-term secret, we implement Ber(x) by + * using PRR(2x, 0) when x < 1/2, PRR(2(1-x), 1) when x >= 1/2. + * Step 2: If x is 0, report IRR(F, original), otherwise report IRR(F, y) + * </p> + * + * Reference: go/bit-reporting-with-longitudinal-privacy + * TODO: Add a public blog / site to explain how it works. + * + * @hide + */ +public class LongitudinalReportingEncoder implements DifferentialPrivacyEncoder { + + // Suffix that will be added to Rappor's encoder id. There's a (relatively) small risk some + // other Rappor encoder may re-use the same encoder id. + private static final String PRR1_ENCODER_ID = "prr1_encoder_id"; + private static final String PRR2_ENCODER_ID = "prr2_encoder_id"; + + private final LongitudinalReportingConfig mConfig; + + // IRR encoder to encode input value. + private final RapporEncoder mIRREncoder; + + // A value that used to replace original value as input, so there's always a chance we are + // doing IRR on a fake value not actual original value. + // Null if original value does not need to be replaced. + private final Boolean mFakeValue; + + // True if encoder is securely randomized. + private final boolean mIsSecure; + + /** + * Create {@link LongitudinalReportingEncoder} with + * {@link LongitudinalReportingConfig} provided. + * + * @param config Longitudinal Reporting parameters to encode input + * @param userSecret User generated secret that used to generate PRR + * @return {@link LongitudinalReportingEncoder} instance + */ + public static LongitudinalReportingEncoder createEncoder(LongitudinalReportingConfig config, + byte[] userSecret) { + return new LongitudinalReportingEncoder(config, true, userSecret); + } + + /** + * Create <strong>insecure</strong> {@link LongitudinalReportingEncoder} with + * {@link LongitudinalReportingConfig} provided. + * Should not use it to process sensitive data. + * + * @param config Rappor parameters to encode input. + * @return {@link LongitudinalReportingEncoder} instance. + */ + @VisibleForTesting + public static LongitudinalReportingEncoder createInsecureEncoderForTest( + LongitudinalReportingConfig config) { + return new LongitudinalReportingEncoder(config, false, null); + } + + private LongitudinalReportingEncoder(LongitudinalReportingConfig config, + boolean secureEncoder, byte[] userSecret) { + mConfig = config; + mIsSecure = secureEncoder; + final boolean ignoreOriginalInput = getLongTermRandomizedResult(config.getProbabilityP(), + secureEncoder, userSecret, config.getEncoderId() + PRR1_ENCODER_ID); + + if (ignoreOriginalInput) { + mFakeValue = getLongTermRandomizedResult(config.getProbabilityQ(), + secureEncoder, userSecret, config.getEncoderId() + PRR2_ENCODER_ID); + } else { + // Not using fake value, so IRR will be processed on real input value. + mFakeValue = null; + } + + final RapporConfig irrConfig = config.getIRRConfig(); + mIRREncoder = secureEncoder + ? RapporEncoder.createEncoder(irrConfig, userSecret) + : RapporEncoder.createInsecureEncoderForTest(irrConfig); + } + + @Override + public byte[] encodeString(String original) { + throw new UnsupportedOperationException(); + } + + @Override + public byte[] encodeBoolean(boolean original) { + if (mFakeValue != null) { + // Use the fake value generated in PRR. + original = mFakeValue.booleanValue(); + } + return mIRREncoder.encodeBoolean(original); + } + + @Override + public byte[] encodeBits(byte[] bits) { + throw new UnsupportedOperationException(); + } + + @Override + public LongitudinalReportingConfig getConfig() { + return mConfig; + } + + @Override + public boolean isInsecureEncoderForTest() { + return !mIsSecure; + } + + /** + * Get PRR result that with probability p is 1, probability 1-p is 0. + */ + @VisibleForTesting + public static boolean getLongTermRandomizedResult(double p, boolean secureEncoder, + byte[] userSecret, String encoderId) { + // Use Rappor to get PRR result. Rappor's P and Q are set to 0 and 1 so IRR will not be + // effective. + // As Rappor has rapporF/2 chance returns 0, rapporF/2 chance returns 1, and 1-rapporF + // chance returns original input. + // If p < 0.5, setting rapporF=2p and input=0 will make Rappor has p chance to return 1 + // P(output=1 | input=0) = rapporF/2 = 2p/2 = p. + // If p >= 0.5, setting rapporF=2(1-p) and input=1 will make Rappor has p chance + // to return 1. + // P(output=1 | input=1) = rapporF/2 + (1 - rapporF) = 2(1-p)/2 + (1 - 2(1-p)) = p. + final double effectiveF = p < 0.5f ? p * 2 : (1 - p) * 2; + final boolean prrInput = p < 0.5f ? false : true; + final RapporConfig prrConfig = new RapporConfig(encoderId, 1, effectiveF, + 0, 1, 1, 1); + final RapporEncoder encoder = secureEncoder + ? RapporEncoder.createEncoder(prrConfig, userSecret) + : RapporEncoder.createInsecureEncoderForTest(prrConfig); + return encoder.encodeBoolean(prrInput)[0] > 0; + } +} diff --git a/core/java/android/privacy/internal/rappor/RapporConfig.java b/core/java/android/privacy/internal/rappor/RapporConfig.java new file mode 100644 index 000000000000..221999bf0a95 --- /dev/null +++ b/core/java/android/privacy/internal/rappor/RapporConfig.java @@ -0,0 +1,87 @@ +/* + * Copyright 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.privacy.internal.rappor; + +import android.privacy.DifferentialPrivacyConfig; +import android.text.TextUtils; + +import com.android.internal.util.Preconditions; + +/** + * A class to store {@link RapporEncoder} config. + * + * @hide + */ +public class RapporConfig implements DifferentialPrivacyConfig { + + private static final String ALGORITHM_NAME = "Rappor"; + + final String mEncoderId; + final int mNumBits; + final double mProbabilityF; + final double mProbabilityP; + final double mProbabilityQ; + final int mNumCohorts; + final int mNumBloomHashes; + + /** + * Constructor for {@link RapporConfig}. + * + * @param encoderId Unique id for encoder. + * @param numBits Number of bits to be encoded in Rappor algorithm. + * @param probabilityF Probability F that used in Rappor algorithm. This will be + * quantized to the nearest 1/128. + * @param probabilityP Probability P that used in Rappor algorithm. + * @param probabilityQ Probability Q that used in Rappor algorithm. + * @param numCohorts Number of cohorts that used in Rappor algorithm. + * @param numBloomHashes Number of bloom hashes that used in Rappor algorithm. + */ + public RapporConfig(String encoderId, int numBits, double probabilityF, + double probabilityP, double probabilityQ, int numCohorts, int numBloomHashes) { + Preconditions.checkArgument(!TextUtils.isEmpty(encoderId), "encoderId cannot be empty"); + this.mEncoderId = encoderId; + Preconditions.checkArgument(numBits > 0, "numBits needs to be > 0"); + this.mNumBits = numBits; + Preconditions.checkArgument(probabilityF >= 0 && probabilityF <= 1, + "probabilityF must be in range [0.0, 1.0]"); + this.mProbabilityF = probabilityF; + Preconditions.checkArgument(probabilityP >= 0 && probabilityP <= 1, + "probabilityP must be in range [0.0, 1.0]"); + this.mProbabilityP = probabilityP; + Preconditions.checkArgument(probabilityQ >= 0 && probabilityQ <= 1, + "probabilityQ must be in range [0.0, 1.0]"); + this.mProbabilityQ = probabilityQ; + Preconditions.checkArgument(numCohorts > 0, "numCohorts needs to be > 0"); + this.mNumCohorts = numCohorts; + Preconditions.checkArgument(numBloomHashes > 0, "numBloomHashes needs to be > 0"); + this.mNumBloomHashes = numBloomHashes; + } + + @Override + public String getAlgorithm() { + return ALGORITHM_NAME; + } + + @Override + public String toString() { + return String.format( + "EncoderId: %s, NumBits: %d, ProbabilityF: %.3f, ProbabilityP: %.3f" + + ", ProbabilityQ: %.3f, NumCohorts: %d, NumBloomHashes: %d", + mEncoderId, mNumBits, mProbabilityF, mProbabilityP, mProbabilityQ, + mNumCohorts, mNumBloomHashes); + } +} diff --git a/core/java/android/privacy/internal/rappor/RapporEncoder.java b/core/java/android/privacy/internal/rappor/RapporEncoder.java new file mode 100644 index 000000000000..2eca4c98d235 --- /dev/null +++ b/core/java/android/privacy/internal/rappor/RapporEncoder.java @@ -0,0 +1,125 @@ +/* + * Copyright 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.privacy.internal.rappor; + +import android.privacy.DifferentialPrivacyEncoder; + +import com.google.android.rappor.Encoder; + +import java.security.SecureRandom; +import java.util.Random; + +/** + * Differential privacy encoder by using + * <a href="https://research.google.com/pubs/pub42852.html">RAPPOR</a> + * algorithm. + * + * @hide + */ +public class RapporEncoder implements DifferentialPrivacyEncoder { + + // Hard-coded seed and secret for insecure encoder + private static final long INSECURE_RANDOM_SEED = 0x12345678L; + private static final byte[] INSECURE_SECRET = new byte[]{ + (byte) 0xD7, (byte) 0x68, (byte) 0x99, (byte) 0x93, + (byte) 0x94, (byte) 0x13, (byte) 0x53, (byte) 0x54, + (byte) 0xFE, (byte) 0xD0, (byte) 0x7E, (byte) 0x54, + (byte) 0xFE, (byte) 0xD0, (byte) 0x7E, (byte) 0x54, + (byte) 0xD7, (byte) 0x68, (byte) 0x99, (byte) 0x93, + (byte) 0x94, (byte) 0x13, (byte) 0x53, (byte) 0x54, + (byte) 0xFE, (byte) 0xD0, (byte) 0x7E, (byte) 0x54, + (byte) 0xFE, (byte) 0xD0, (byte) 0x7E, (byte) 0x54, + (byte) 0xD7, (byte) 0x68, (byte) 0x99, (byte) 0x93, + (byte) 0x94, (byte) 0x13, (byte) 0x53, (byte) 0x54, + (byte) 0xFE, (byte) 0xD0, (byte) 0x7E, (byte) 0x54, + (byte) 0xFE, (byte) 0xD0, (byte) 0x7E, (byte) 0x54 + }; + private static final SecureRandom sSecureRandom = new SecureRandom(); + + private final RapporConfig mConfig; + + // Rappor encoder + private final Encoder mEncoder; + // True if encoder is secure (seed is securely randomized) + private final boolean mIsSecure; + + + private RapporEncoder(RapporConfig config, boolean secureEncoder, byte[] userSecret) { + mConfig = config; + mIsSecure = secureEncoder; + final Random random; + if (secureEncoder) { + // Use SecureRandom as random generator. + random = sSecureRandom; + } else { + // Hard-coded random generator, to have deterministic result. + random = new Random(INSECURE_RANDOM_SEED); + userSecret = INSECURE_SECRET; + } + mEncoder = new Encoder(random, null, null, + userSecret, config.mEncoderId, config.mNumBits, + config.mProbabilityF, config.mProbabilityP, config.mProbabilityQ, + config.mNumCohorts, config.mNumBloomHashes); + } + + /** + * Create {@link RapporEncoder} with {@link RapporConfig} and user secret provided. + * + * @param config Rappor parameters to encode input. + * @param userSecret Per device unique secret key. + * @return {@link RapporEncoder} instance. + */ + public static RapporEncoder createEncoder(RapporConfig config, byte[] userSecret) { + return new RapporEncoder(config, true, userSecret); + } + + /** + * Create <strong>insecure</strong> {@link RapporEncoder} with {@link RapporConfig} provided. + * Should not use it to process sensitive data. + * + * @param config Rappor parameters to encode input. + * @return {@link RapporEncoder} instance. + */ + public static RapporEncoder createInsecureEncoderForTest(RapporConfig config) { + return new RapporEncoder(config, false, null); + } + + @Override + public byte[] encodeString(String original) { + return mEncoder.encodeString(original); + } + + @Override + public byte[] encodeBoolean(boolean original) { + return mEncoder.encodeBoolean(original); + } + + @Override + public byte[] encodeBits(byte[] bits) { + return mEncoder.encodeBits(bits); + } + + @Override + public RapporConfig getConfig() { + return mConfig; + } + + @Override + public boolean isInsecureEncoderForTest() { + return !mIsSecure; + } +} diff --git a/core/java/android/provider/MediaStore.java b/core/java/android/provider/MediaStore.java index 32d68cd9f869..d9808a3d1412 100644 --- a/core/java/android/provider/MediaStore.java +++ b/core/java/android/provider/MediaStore.java @@ -63,15 +63,6 @@ public final class MediaStore { private static final String CONTENT_AUTHORITY_SLASH = "content://" + AUTHORITY + "/"; - /** - * Broadcast Action: A broadcast to indicate the end of an MTP session with the host. - * This broadcast is only sent if MTP activity has modified the media database during the - * most recent MTP session. - * - * @hide - */ - public static final String ACTION_MTP_SESSION_END = "android.provider.action.MTP_SESSION_END"; - /** * The method name used by the media scanner and mtp to tell the media provider to * rescan and reclassify that have become unhidden because of renaming folders or diff --git a/core/java/android/security/recoverablekeystore/KeyEntryRecoveryData.aidl b/core/java/android/security/recoverablekeystore/KeyEntryRecoveryData.aidl index 1058463aa561..1674e51f77e1 100644 --- a/core/java/android/security/recoverablekeystore/KeyEntryRecoveryData.aidl +++ b/core/java/android/security/recoverablekeystore/KeyEntryRecoveryData.aidl @@ -14,7 +14,7 @@ * limitations under the License. */ -package android.security.keystore.recoverablekeystore; +package android.security.recoverablekeystore; /* @hide */ parcelable KeyEntryRecoveryData; diff --git a/core/java/android/security/recoverablekeystore/RecoverableKeyStoreLoader.java b/core/java/android/security/recoverablekeystore/RecoverableKeyStoreLoader.java index 0510320d3e11..f2f225df7f93 100644 --- a/core/java/android/security/recoverablekeystore/RecoverableKeyStoreLoader.java +++ b/core/java/android/security/recoverablekeystore/RecoverableKeyStoreLoader.java @@ -19,37 +19,40 @@ package android.security.recoverablekeystore; import android.annotation.NonNull; import android.annotation.Nullable; import android.content.pm.PackageManager.NameNotFoundException; +import android.os.RemoteException; import android.os.ServiceManager; +import android.os.ServiceSpecificException; +import android.os.UserHandle; +import android.security.KeyStore; +import android.util.AndroidException; import com.android.internal.widget.ILockSettings; import java.util.List; /** - * A wrapper around KeyStore which lets key be exported to - * trusted hardware on server side and recovered later. + * A wrapper around KeyStore which lets key be exported to trusted hardware on server side and + * recovered later. * * @hide */ -public class RecoverableKeyStoreLoader { +public class RecoverableKeyStoreLoader { - private final ILockSettings mBinder; - - // Exception codes, should be in sync with {@code KeyStoreException}. - public static final int SYSTEM_ERROR = 4; + public static final String PERMISSION_RECOVER_KEYSTORE = "android.permission.RECOVER_KEYSTORE"; + public static final int NO_ERROR = KeyStore.NO_ERROR; + public static final int SYSTEM_ERROR = KeyStore.SYSTEM_ERROR; public static final int UNINITIALIZED_RECOVERY_PUBLIC_KEY = 20; - // Too many updates to recovery public key or server parameters. public static final int RATE_LIMIT_EXCEEDED = 21; + private final ILockSettings mBinder; + private RecoverableKeyStoreLoader(ILockSettings binder) { mBinder = binder; } - /** - * @hide - */ + /** @hide */ public static RecoverableKeyStoreLoader getInstance() { ILockSettings lockSettings = ILockSettings.Stub.asInterface(ServiceManager.getService("lock_settings")); @@ -57,29 +60,69 @@ public class RecoverableKeyStoreLoader { } /** + * Exceptions returned by {@link RecoverableKeyStoreLoader}. + * * @hide */ - public static class RecoverableKeyStoreLoaderException extends Exception { - private final int mErrorCode; + public static class RecoverableKeyStoreLoaderException extends AndroidException { + private int mErrorCode; + + /** + * Creates new {@link #RecoverableKeyStoreLoaderException} instance from the error code. + * + * @param errorCode + * @hide + */ + public static RecoverableKeyStoreLoaderException fromErrorCode(int errorCode) { + return new RecoverableKeyStoreLoaderException( + errorCode, getMessageFromErrorCode(errorCode)); + } + + /** + * Creates new {@link #RecoverableKeyStoreLoaderException} from {@link + * ServiceSpecificException}. + * + * @param e exception thrown on service side. + * @hide + */ + static RecoverableKeyStoreLoaderException fromServiceSpecificException( + ServiceSpecificException e) throws RecoverableKeyStoreLoaderException { + throw RecoverableKeyStoreLoaderException.fromErrorCode(e.errorCode); + } - public RecoverableKeyStoreLoaderException(int errorCode, String message) { + private RecoverableKeyStoreLoaderException(int errorCode, String message) { super(message); - mErrorCode = errorCode; } + /** Returns errorCode. */ public int getErrorCode() { return mErrorCode; } + + /** @hide */ + private static String getMessageFromErrorCode(int errorCode) { + switch (errorCode) { + case NO_ERROR: + return "OK"; + case SYSTEM_ERROR: + return "System error"; + case UNINITIALIZED_RECOVERY_PUBLIC_KEY: + return "Recovery service is not initialized"; + case RATE_LIMIT_EXCEEDED: + return "Rate limit exceeded"; + default: + return String.valueOf("Unknown error code " + errorCode); + } + } } /** * Initializes key recovery service for the calling application. RecoverableKeyStoreLoader - * randomly chooses one of the keys from the list - * and keeps it to use for future key export operations. Collection of all keys - * in the list must be signed by the provided {@code rootCertificateAlias}, which must also be - * present in the list of root certificates preinstalled on the device. The random selection - * allows RecoverableKeyStoreLoader to select which of a set of remote recovery service - * devices will be used. + * randomly chooses one of the keys from the list and keeps it to use for future key export + * operations. Collection of all keys in the list must be signed by the provided {@code + * rootCertificateAlias}, which must also be present in the list of root certificates + * preinstalled on the device. The random selection allows RecoverableKeyStoreLoader to select + * which of a set of remote recovery service devices will be used. * * <p>In addition, RecoverableKeyStoreLoader enforces a delay of three months between * consecutive initialization attempts, to limit the ability of an attacker to often switch @@ -88,127 +131,200 @@ public class RecoverableKeyStoreLoader { * @param rootCertificateAlias alias of a root certificate preinstalled on the device * @param signedPublicKeyList binary blob a list of X509 certificates and signature * @throws RecoverableKeyStoreLoaderException if signature is invalid, or key rotation was rate - * limited. + * limited. * @hide */ - public void initRecoveryService(@NonNull String rootCertificateAlias, - @NonNull byte[] signedPublicKeyList) + public void initRecoveryService( + @NonNull String rootCertificateAlias, @NonNull byte[] signedPublicKeyList) throws RecoverableKeyStoreLoaderException { - throw new RecoverableKeyStoreLoaderException(SYSTEM_ERROR, "Not implemented"); - // TODO: extend widget/ILockSettings.aidl - /* try { - mBinder.initRecoveryService(rootCertificate, publicKeyList); - } catch (RemoteException e) { + try { + mBinder.initRecoveryService( + rootCertificateAlias, signedPublicKeyList, UserHandle.getCallingUserId()); + } catch (RemoteException e) { throw e.rethrowFromSystemServer(); - } */ + } catch (ServiceSpecificException e) { + throw RecoverableKeyStoreLoaderException.fromServiceSpecificException(e); + } } /** - * Returns data necessary to store all recoverable keys for given account. - * Key material is encrypted with user secret and recovery public key. + * Returns data necessary to store all recoverable keys for given account. Key material is + * encrypted with user secret and recovery public key. + * + * @param account specific to Recovery agent. + * @return Data necessary to recover keystore. + * @hide */ public KeyStoreRecoveryData getRecoveryData(@NonNull byte[] account) throws RecoverableKeyStoreLoaderException { - throw new RecoverableKeyStoreLoaderException(SYSTEM_ERROR, "Not implemented"); + try { + KeyStoreRecoveryData recoveryData = + mBinder.getRecoveryData(account, UserHandle.getCallingUserId()); + return recoveryData; + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } catch (ServiceSpecificException e) { + throw RecoverableKeyStoreLoaderException.fromServiceSpecificException(e); + } } /** * Server parameters used to generate new recovery key blobs. This value will be included in - * {@code KeyStoreRecoveryData.getEncryptedRecoveryKeyBlob()}. - * The same value must be included in vaultParams {@link startRecoverySession} + * {@code KeyStoreRecoveryData.getEncryptedRecoveryKeyBlob()}. The same value must be included + * in vaultParams {@link startRecoverySession} * + * @param serverParameters included in recovery key blob. * @see #getRecoveryData * @throws RecoverableKeyStoreLoaderException If parameters rotation is rate limited. + * @hide */ - public void updateServerParameters(long serverParameters) + public void setServerParameters(long serverParameters) throws RecoverableKeyStoreLoaderException { - throw new RecoverableKeyStoreLoaderException(SYSTEM_ERROR, "Not implemented"); + try { + mBinder.setServerParameters(serverParameters, UserHandle.getCallingUserId()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } catch (ServiceSpecificException e) { + throw RecoverableKeyStoreLoaderException.fromServiceSpecificException(e); + } } /** - * Updates recovery status for given keys. - * It is used to notify keystore that key was successfully stored on the server or - * there were an error. Returned as a part of KeyInfo data structure. + * Updates recovery status for given keys. It is used to notify keystore that key was + * successfully stored on the server or there were an error. Returned as a part of KeyInfo data + * structure. * * @param packageName Application whose recoverable keys' statuses are to be updated. * @param aliases List of application-specific key aliases. If the array is empty, updates the - * status for all existing recoverable keys. + * status for all existing recoverable keys. * @param status Status specific to recovery agent. */ - public void setRecoveryStatus(@NonNull String packageName, @Nullable String[] aliases, - int status) throws NameNotFoundException, RecoverableKeyStoreLoaderException { - throw new RecoverableKeyStoreLoaderException(SYSTEM_ERROR, "Not implemented"); + public void setRecoveryStatus( + @NonNull String packageName, @Nullable String[] aliases, int status) + throws NameNotFoundException, RecoverableKeyStoreLoaderException { + try { + mBinder.setRecoveryStatus(packageName, aliases, status, UserHandle.getCallingUserId()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } catch (ServiceSpecificException e) { + throw RecoverableKeyStoreLoaderException.fromServiceSpecificException(e); + } } /** - * Specifies a set of secret types used for end-to-end keystore encryption. - * Knowing all of them is necessary to recover data. + * Specifies a set of secret types used for end-to-end keystore encryption. Knowing all of them + * is necessary to recover data. * - * @param secretTypes {@link KeyStoreRecoveryMetadata#TYPE_LOCKSCREEN} or - * {@link KeyStoreRecoveryMetadata#TYPE_CUSTOM_PASSWORD} + * @param secretTypes {@link KeyStoreRecoveryMetadata#TYPE_LOCKSCREEN} or {@link + * KeyStoreRecoveryMetadata#TYPE_CUSTOM_PASSWORD} */ - public void setRecoverySecretTypes(@NonNull @KeyStoreRecoveryMetadata.UserSecretType - int[] secretTypes) throws RecoverableKeyStoreLoaderException { - throw new RecoverableKeyStoreLoaderException(SYSTEM_ERROR, "Not implemented"); + public void setRecoverySecretTypes( + @NonNull @KeyStoreRecoveryMetadata.UserSecretType int[] secretTypes) + throws RecoverableKeyStoreLoaderException { + try { + mBinder.setRecoverySecretTypes(secretTypes, UserHandle.getCallingUserId()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } catch (ServiceSpecificException e) { + throw RecoverableKeyStoreLoaderException.fromServiceSpecificException(e); + } } /** - * Defines a set of secret types used for end-to-end keystore encryption. - * Knowing all of them is necessary to generate KeyStoreRecoveryData. + * Defines a set of secret types used for end-to-end keystore encryption. Knowing all of them is + * necessary to generate KeyStoreRecoveryData. + * + * @return list of recovery secret types * @see KeyStoreRecoveryData */ public @NonNull @KeyStoreRecoveryMetadata.UserSecretType int[] getRecoverySecretTypes() throws RecoverableKeyStoreLoaderException { - throw new RecoverableKeyStoreLoaderException(SYSTEM_ERROR, "Not implemented"); + try { + return mBinder.getRecoverySecretTypes(UserHandle.getCallingUserId()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } catch (ServiceSpecificException e) { + throw RecoverableKeyStoreLoaderException.fromServiceSpecificException(e); + } } /** * Returns a list of recovery secret types, necessary to create a pending recovery snapshot. - * When user enters a secret of a pending type - * {@link #recoverySecretAvailable} should be called. + * When user enters a secret of a pending type {@link #recoverySecretAvailable} should be + * called. + * + * @return list of recovery secret types */ public @NonNull @KeyStoreRecoveryMetadata.UserSecretType int[] getPendingRecoverySecretTypes() throws RecoverableKeyStoreLoaderException { - throw new RecoverableKeyStoreLoaderException(SYSTEM_ERROR, "Not implemented"); + try { + return mBinder.getPendingRecoverySecretTypes(UserHandle.getCallingUserId()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } catch (ServiceSpecificException e) { + throw RecoverableKeyStoreLoaderException.fromServiceSpecificException(e); + } } /** - * Method notifies KeyStore that a user-generated secret is available. - * This method generates a symmetric session key which a trusted remote device can use - * to return a recovery key. - * Caller should use {@link KeyStoreRecoveryMetadata#clearSecret} to override the secret value - * in memory. + * Method notifies KeyStore that a user-generated secret is available. This method generates a + * symmetric session key which a trusted remote device can use to return a recovery key. Caller + * should use {@link KeyStoreRecoveryMetadata#clearSecret} to override the secret value in + * memory. * - * @param recoverySecret user generated secret together with parameters necessary to - * regenerate it on a new device. + * @param recoverySecret user generated secret together with parameters necessary to regenerate + * it on a new device. */ public void recoverySecretAvailable(@NonNull KeyStoreRecoveryMetadata recoverySecret) throws RecoverableKeyStoreLoaderException { - throw new RecoverableKeyStoreLoaderException(SYSTEM_ERROR, "Not implemented"); + try { + mBinder.recoverySecretAvailable(recoverySecret, UserHandle.getCallingUserId()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } catch (ServiceSpecificException e) { + throw RecoverableKeyStoreLoaderException.fromServiceSpecificException(e); + } } /** * Initializes recovery session and returns a blob with proof of recovery secrets possession. - * The method generates symmetric key for a session, which trusted remote device can use - * to return recovery key. + * The method generates symmetric key for a session, which trusted remote device can use to + * return recovery key. * * @param sessionId ID for recovery session. - * @param verifierPublicKey Certificate with Public key used to create the recovery blob on - * the source device. Keystore will verify the certificate using root of trust. + * @param verifierPublicKey Certificate with Public key used to create the recovery blob on the + * source device. Keystore will verify the certificate using root of trust. * @param vaultParams Must match the parameters in the corresponding field in the recovery blob. - * Used to limit number of guesses. + * Used to limit number of guesses. * @param vaultChallenge Data passed from server for this recovery session and used to prevent - * replay attacks + * replay attacks * @param secrets Secrets provided by user, the method only uses type and secret fields. - * @return Binary blob with recovery claim. It is encrypted with verifierPublicKey and - * contains a proof of user secrets, session symmetric key and parameters necessary to identify - * the counter with the number of failed recovery attempts. + * @return Binary blob with recovery claim. It is encrypted with verifierPublicKey and contains + * a proof of user secrets, session symmetric key and parameters necessary to identify the + * counter with the number of failed recovery attempts. */ - public @NonNull byte[] startRecoverySession(@NonNull String sessionId, - @NonNull byte[] verifierPublicKey, @NonNull byte[] vaultParams, - @NonNull byte[] vaultChallenge, @NonNull List<KeyStoreRecoveryMetadata> secrets) + public @NonNull byte[] startRecoverySession( + @NonNull String sessionId, + @NonNull byte[] verifierPublicKey, + @NonNull byte[] vaultParams, + @NonNull byte[] vaultChallenge, + @NonNull List<KeyStoreRecoveryMetadata> secrets) throws RecoverableKeyStoreLoaderException { - throw new RecoverableKeyStoreLoaderException(SYSTEM_ERROR, "Not implemented"); + try { + byte[] recoveryClaim = + mBinder.startRecoverySession( + sessionId, + verifierPublicKey, + vaultParams, + vaultChallenge, + secrets, + UserHandle.getCallingUserId()); + return recoveryClaim; + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } catch (ServiceSpecificException e) { + throw RecoverableKeyStoreLoaderException.fromServiceSpecificException(e); + } } /** @@ -217,12 +333,21 @@ public class RecoverableKeyStoreLoader { * @param sessionId Id for recovery session, same as in = {@link startRecoverySession}. * @param recoveryKeyBlob Recovery blob encrypted by symmetric key generated for this session. * @param applicationKeys Application keys. Key material can be decrypted using recoveryKeyBlob - * and session. KeyStore only uses package names from the application info in - * {@link KeyEntryRecoveryData}. Caller is responsibility to perform certificates check. + * and session. KeyStore only uses package names from the application info in {@link + * KeyEntryRecoveryData}. Caller is responsibility to perform certificates check. */ - public void recoverKeys(@NonNull String sessionId, @NonNull byte[] recoveryKeyBlob, + public void recoverKeys( + @NonNull String sessionId, + @NonNull byte[] recoveryKeyBlob, @NonNull List<KeyEntryRecoveryData> applicationKeys) throws RecoverableKeyStoreLoaderException { - throw new RecoverableKeyStoreLoaderException(SYSTEM_ERROR, "Not implemented"); + try { + mBinder.recoverKeys( + sessionId, recoveryKeyBlob, applicationKeys, UserHandle.getCallingUserId()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } catch (ServiceSpecificException e) { + throw RecoverableKeyStoreLoaderException.fromServiceSpecificException(e); + } } } diff --git a/core/java/android/service/autofill/FieldClassification.java b/core/java/android/service/autofill/FieldClassification.java index b28c6f81e782..001b2917aadf 100644 --- a/core/java/android/service/autofill/FieldClassification.java +++ b/core/java/android/service/autofill/FieldClassification.java @@ -20,31 +20,39 @@ import static android.view.autofill.Helper.sDebug; import android.annotation.NonNull; import android.os.Parcel; -import android.os.Parcelable; import android.view.autofill.Helper; import com.android.internal.util.Preconditions; -import com.google.android.collect.Lists; - +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; import java.util.List; /** * Represents the <a href="AutofillService.html#FieldClassification">field classification</a> * results for a given field. */ -// TODO(b/70291841): let caller handle Parcelable... -public final class FieldClassification implements Parcelable { +public final class FieldClassification { - private final Match mMatch; + private final ArrayList<Match> mMatches; /** @hide */ - public FieldClassification(@NonNull Match match) { - mMatch = Preconditions.checkNotNull(match); + public FieldClassification(@NonNull ArrayList<Match> matches) { + mMatches = Preconditions.checkNotNull(matches); + Collections.sort(mMatches, new Comparator<Match>() { + @Override + public int compare(Match o1, Match o2) { + if (o1.mScore > o2.mScore) return -1; + if (o1.mScore < o2.mScore) return 1; + return 0; + }} + ); } /** - * Gets the {@link Match matches} with the highest {@link Match#getScore() scores}. + * Gets the {@link Match matches} with the highest {@link Match#getScore() scores} (sorted in + * descending order). * * <p><b>Note:</b> There's no guarantee of how many matches will be returned. In fact, * the Android System might return just the top match to minimize the impact of field @@ -52,43 +60,48 @@ public final class FieldClassification implements Parcelable { */ @NonNull public List<Match> getMatches() { - return Lists.newArrayList(mMatch); + return mMatches; } @Override public String toString() { if (!sDebug) return super.toString(); - return "FieldClassification: " + mMatch; + return "FieldClassification: " + mMatches; } - ///////////////////////////////////// - // Parcelable "contract" methods. // - ///////////////////////////////////// - - @Override - public int describeContents() { - return 0; + private void writeToParcel(Parcel parcel) { + parcel.writeInt(mMatches.size()); + for (int i = 0; i < mMatches.size(); i++) { + mMatches.get(i).writeToParcel(parcel); + } } - @Override - public void writeToParcel(Parcel parcel, int flags) { - mMatch.writeToParcel(parcel); - } + private static FieldClassification readFromParcel(Parcel parcel) { + final int size = parcel.readInt(); + final ArrayList<Match> matches = new ArrayList<>(); + for (int i = 0; i < size; i++) { + matches.add(i, Match.readFromParcel(parcel)); + } - public static final Parcelable.Creator<FieldClassification> CREATOR = - new Parcelable.Creator<FieldClassification>() { + return new FieldClassification(matches); + } - @Override - public FieldClassification createFromParcel(Parcel parcel) { - return new FieldClassification(Match.readFromParcel(parcel)); + static FieldClassification[] readArrayFromParcel(Parcel parcel) { + final int length = parcel.readInt(); + final FieldClassification[] fcs = new FieldClassification[length]; + for (int i = 0; i < length; i++) { + fcs[i] = readFromParcel(parcel); } + return fcs; + } - @Override - public FieldClassification[] newArray(int size) { - return new FieldClassification[size]; + static void writeArrayToParcel(@NonNull Parcel parcel, @NonNull FieldClassification[] fcs) { + parcel.writeInt(fcs.length); + for (int i = 0; i < fcs.length; i++) { + fcs[i].writeToParcel(parcel); } - }; + } /** * Represents the score of a {@link UserData} entry for the field. @@ -151,23 +164,5 @@ public final class FieldClassification implements Parcelable { private static Match readFromParcel(@NonNull Parcel parcel) { return new Match(parcel.readString(), parcel.readFloat()); } - - /** @hide */ - public static Match[] readArrayFromParcel(@NonNull Parcel parcel) { - final int length = parcel.readInt(); - final Match[] matches = new Match[length]; - for (int i = 0; i < length; i++) { - matches[i] = readFromParcel(parcel); - } - return matches; - } - - /** @hide */ - public static void writeArrayToParcel(@NonNull Parcel parcel, @NonNull Match[] matches) { - parcel.writeInt(matches.length); - for (int i = 0; i < matches.length; i++) { - matches[i].writeToParcel(parcel); - } - } } } diff --git a/core/java/android/service/autofill/FillEventHistory.java b/core/java/android/service/autofill/FillEventHistory.java index 07fab611e6be..df62446427d3 100644 --- a/core/java/android/service/autofill/FillEventHistory.java +++ b/core/java/android/service/autofill/FillEventHistory.java @@ -25,7 +25,6 @@ import android.content.IntentSender; import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; -import android.service.autofill.FieldClassification.Match; import android.util.ArrayMap; import android.util.ArraySet; import android.util.Log; @@ -157,7 +156,8 @@ public final class FillEventHistory implements Parcelable { final AutofillId[] detectedFields = event.mDetectedFieldIds; parcel.writeParcelableArray(detectedFields, flags); if (detectedFields != null) { - Match.writeArrayToParcel(parcel, event.mDetectedMatches); + FieldClassification.writeArrayToParcel(parcel, + event.mDetectedFieldClassifications); } } } @@ -251,7 +251,7 @@ public final class FillEventHistory implements Parcelable { @Nullable private final ArrayList<ArrayList<String>> mManuallyFilledDatasetIds; @Nullable private final AutofillId[] mDetectedFieldIds; - @Nullable private final Match[] mDetectedMatches; + @Nullable private final FieldClassification[] mDetectedFieldClassifications; /** * Returns the type of the event. @@ -370,11 +370,11 @@ public final class FillEventHistory implements Parcelable { final ArrayMap<AutofillId, FieldClassification> map = new ArrayMap<>(size); for (int i = 0; i < size; i++) { final AutofillId id = mDetectedFieldIds[i]; - final Match match = mDetectedMatches[i]; + final FieldClassification fc = mDetectedFieldClassifications[i]; if (sVerbose) { - Log.v(TAG, "getFieldsClassification[" + i + "]: id=" + id + ", match=" + match); + Log.v(TAG, "getFieldsClassification[" + i + "]: id=" + id + ", fc=" + fc); } - map.put(id, new FieldClassification(match)); + map.put(id, fc); } return map; } @@ -455,7 +455,7 @@ public final class FillEventHistory implements Parcelable { * and belonged to datasets. * @param manuallyFilledDatasetIds The ids of datasets that had values matching the * respective entry on {@code manuallyFilledFieldIds}. - * @param detectedMatches the field classification matches. + * @param detectedFieldClassifications the field classification matches. * * @throws IllegalArgumentException If the length of {@code changedFieldIds} and * {@code changedDatasetIds} doesn't match. @@ -471,7 +471,8 @@ public final class FillEventHistory implements Parcelable { @Nullable ArrayList<String> changedDatasetIds, @Nullable ArrayList<AutofillId> manuallyFilledFieldIds, @Nullable ArrayList<ArrayList<String>> manuallyFilledDatasetIds, - @Nullable AutofillId[] detectedFieldIds, @Nullable Match[] detectedMatches) { + @Nullable AutofillId[] detectedFieldIds, + @Nullable FieldClassification[] detectedFieldClassifications) { mEventType = Preconditions.checkArgumentInRange(eventType, 0, TYPE_CONTEXT_COMMITTED, "eventType"); mDatasetId = datasetId; @@ -496,7 +497,7 @@ public final class FillEventHistory implements Parcelable { mManuallyFilledDatasetIds = manuallyFilledDatasetIds; mDetectedFieldIds = detectedFieldIds; - mDetectedMatches = detectedMatches; + mDetectedFieldClassifications = detectedFieldClassifications; } @Override @@ -510,7 +511,8 @@ public final class FillEventHistory implements Parcelable { + ", manuallyFilledFieldIds=" + mManuallyFilledFieldIds + ", manuallyFilledDatasetIds=" + mManuallyFilledDatasetIds + ", detectedFieldIds=" + Arrays.toString(mDetectedFieldIds) - + ", detectedMaches =" + Arrays.toString(mDetectedMatches) + + ", detectedFieldClassifications =" + + Arrays.toString(mDetectedFieldClassifications) + "]"; } } @@ -548,15 +550,16 @@ public final class FillEventHistory implements Parcelable { } final AutofillId[] detectedFieldIds = parcel.readParcelableArray(null, AutofillId.class); - final Match[] detectedMatches = (detectedFieldIds != null) - ? Match.readArrayFromParcel(parcel) + final FieldClassification[] detectedFieldClassifications = + (detectedFieldIds != null) + ? FieldClassification.readArrayFromParcel(parcel) : null; selection.addEvent(new Event(eventType, datasetId, clientState, selectedDatasetIds, ignoredDatasets, changedFieldIds, changedDatasetIds, manuallyFilledFieldIds, manuallyFilledDatasetIds, - detectedFieldIds, detectedMatches)); + detectedFieldIds, detectedFieldClassifications)); } return selection; } diff --git a/core/java/android/service/wallpaper/IWallpaperEngine.aidl b/core/java/android/service/wallpaper/IWallpaperEngine.aidl index fb6f637fd927..b8f2191fccfd 100644 --- a/core/java/android/service/wallpaper/IWallpaperEngine.aidl +++ b/core/java/android/service/wallpaper/IWallpaperEngine.aidl @@ -27,6 +27,7 @@ oneway interface IWallpaperEngine { void setDesiredSize(int width, int height); void setDisplayPadding(in Rect padding); void setVisibility(boolean visible); + void setInAmbientMode(boolean inAmbientDisplay); void dispatchPointer(in MotionEvent event); void dispatchWallpaperCommand(String action, int x, int y, int z, in Bundle extras); diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java index e5ab3e1caa3d..595bfb7a8c90 100644 --- a/core/java/android/service/wallpaper/WallpaperService.java +++ b/core/java/android/service/wallpaper/WallpaperService.java @@ -102,6 +102,7 @@ public abstract class WallpaperService extends Service { private static final int DO_DETACH = 20; private static final int DO_SET_DESIRED_SIZE = 30; private static final int DO_SET_DISPLAY_PADDING = 40; + private static final int DO_IN_AMBIENT_MODE = 50; private static final int MSG_UPDATE_SURFACE = 10000; private static final int MSG_VISIBILITY_CHANGED = 10010; @@ -195,6 +196,7 @@ public abstract class WallpaperService extends Service { float mPendingYOffsetStep; boolean mPendingSync; MotionEvent mPendingMove; + boolean mIsInAmbientMode; // Needed for throttling onComputeColors. private long mLastColorInvalidation; @@ -431,6 +433,15 @@ public abstract class WallpaperService extends Service { public boolean isPreview() { return mIWallpaperEngine.mIsPreview; } + + /** + * Returns true if this engine is running in ambient mode -- that is, + * it is being shown in low power mode, in always on display. + * @hide + */ + public boolean isInAmbientMode() { + return mIsInAmbientMode; + } /** * Control whether this wallpaper will receive raw touch events @@ -549,6 +560,15 @@ public abstract class WallpaperService extends Service { } /** + * Called when the device enters or exits ambient mode. + * + * @param inAmbientMode {@code true} if in ambient mode. + * @hide + */ + public void onAmbientModeChanged(boolean inAmbientMode) { + } + + /** * Called when an application has changed the desired virtual size of * the wallpaper. */ @@ -631,6 +651,16 @@ public abstract class WallpaperService extends Service { return null; } + /** + * Sets internal engine state. Only for testing. + * @param created {@code true} or {@code false}. + * @hide + */ + @VisibleForTesting + public void setCreated(boolean created) { + mCreated = created; + } + protected void dump(String prefix, FileDescriptor fd, PrintWriter out, String[] args) { out.print(prefix); out.print("mInitializing="); out.print(mInitializing); out.print(" mDestroyed="); out.println(mDestroyed); @@ -683,7 +713,8 @@ public abstract class WallpaperService extends Service { } Message msg = mCaller.obtainMessageO(MSG_TOUCH_EVENT, event); mCaller.sendMessage(msg); - } else {event.recycle(); + } else { + event.recycle(); } } @@ -986,6 +1017,26 @@ public abstract class WallpaperService extends Service { updateSurface(false, false, false); } + /** + * Executes life cycle event and updates internal ambient mode state based on + * message sent from handler. + * + * @param inAmbientMode True if in ambient mode. + * @hide + */ + @VisibleForTesting + public void doAmbientModeChanged(boolean inAmbientMode) { + if (!mDestroyed) { + if (DEBUG) { + Log.v(TAG, "onAmbientModeChanged(" + inAmbientMode + "): " + this); + } + mIsInAmbientMode = inAmbientMode; + if (mCreated) { + onAmbientModeChanged(inAmbientMode); + } + } + } + void doDesiredSizeChanged(int desiredWidth, int desiredHeight) { if (!mDestroyed) { if (DEBUG) Log.v(TAG, "onDesiredSizeChanged(" @@ -1226,6 +1277,12 @@ public abstract class WallpaperService extends Service { mCaller.sendMessage(msg); } + @Override + public void setInAmbientMode(boolean inAmbientDisplay) throws RemoteException { + Message msg = mCaller.obtainMessageI(DO_IN_AMBIENT_MODE, inAmbientDisplay ? 1 : 0); + mCaller.sendMessage(msg); + } + public void dispatchPointer(MotionEvent event) { if (mEngine != null) { mEngine.dispatchPointer(event); @@ -1263,6 +1320,7 @@ public abstract class WallpaperService extends Service { mCaller.sendMessage(msg); } + @Override public void executeMessage(Message message) { switch (message.what) { case DO_ATTACH: { @@ -1289,6 +1347,11 @@ public abstract class WallpaperService extends Service { } case DO_SET_DISPLAY_PADDING: { mEngine.doDisplayPaddingChanged((Rect) message.obj); + return; + } + case DO_IN_AMBIENT_MODE: { + mEngine.doAmbientModeChanged(message.arg1 != 0); + return; } case MSG_UPDATE_SURFACE: mEngine.updateSurface(true, false, false); diff --git a/core/java/android/text/DynamicLayout.java b/core/java/android/text/DynamicLayout.java index fba358cf4c1b..71e039a1e0ef 100644 --- a/core/java/android/text/DynamicLayout.java +++ b/core/java/android/text/DynamicLayout.java @@ -492,7 +492,9 @@ public class DynamicLayout extends Layout } } - private void reflow(CharSequence s, int where, int before, int after) { + /** @hide */ + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) + public void reflow(CharSequence s, int where, int before, int after) { if (s != mBase) return; @@ -805,8 +807,8 @@ public class DynamicLayout extends Layout return; } - int firstBlock = -1; - int lastBlock = -1; + /*final*/ int firstBlock = -1; + /*final*/ int lastBlock = -1; for (int i = 0; i < mNumberOfBlocks; i++) { if (mBlockEndLines[i] >= startLine) { firstBlock = i; @@ -821,10 +823,10 @@ public class DynamicLayout extends Layout } final int lastBlockEndLine = mBlockEndLines[lastBlock]; - boolean createBlockBefore = startLine > (firstBlock == 0 ? 0 : + final boolean createBlockBefore = startLine > (firstBlock == 0 ? 0 : mBlockEndLines[firstBlock - 1] + 1); - boolean createBlock = newLineCount > 0; - boolean createBlockAfter = endLine < mBlockEndLines[lastBlock]; + final boolean createBlock = newLineCount > 0; + final boolean createBlockAfter = endLine < mBlockEndLines[lastBlock]; int numAddedBlocks = 0; if (createBlockBefore) numAddedBlocks++; @@ -863,12 +865,18 @@ public class DynamicLayout extends Layout if (numAddedBlocks + numRemovedBlocks != 0 && mBlocksAlwaysNeedToBeRedrawn != null) { final ArraySet<Integer> set = new ArraySet<>(); + final int changedBlockCount = numAddedBlocks - numRemovedBlocks; for (int i = 0; i < mBlocksAlwaysNeedToBeRedrawn.size(); i++) { Integer block = mBlocksAlwaysNeedToBeRedrawn.valueAt(i); - if (block > firstBlock) { - block += numAddedBlocks - numRemovedBlocks; + if (block < firstBlock) { + // block index is before firstBlock add it since it did not change + set.add(block); + } + if (block > lastBlock) { + // block index is after lastBlock, the index reduced to += changedBlockCount + block += changedBlockCount; + set.add(block); } - set.add(block); } mBlocksAlwaysNeedToBeRedrawn = set; } diff --git a/core/java/android/util/apk/ApkSignatureVerifier.java b/core/java/android/util/apk/ApkSignatureVerifier.java index 73a9478509e6..75c1000ba41d 100644 --- a/core/java/android/util/apk/ApkSignatureVerifier.java +++ b/core/java/android/util/apk/ApkSignatureVerifier.java @@ -65,18 +65,14 @@ public class ApkSignatureVerifier { * v2 stripping rollback protection, or verify integrity of the APK. * * @throws PackageParserException if the APK's signature failed to verify. - * @throws SignatureNotFoundException if a signature corresponding to minLevel or greater - * is not found, except in the case of no JAR signature. */ public static Result verify(String apkPath, int minSignatureSchemeVersion, boolean systemDir) - throws PackageParserException, SignatureNotFoundException { - boolean verified = false; - Certificate[][] signerCerts; - int level = VERSION_APK_SIGNATURE_SCHEME_V2; + throws PackageParserException { // first try v2 Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "verifyV2"); try { + Certificate[][] signerCerts; signerCerts = ApkSignatureSchemeV2Verifier.verify(apkPath); Signature[] signerSigs = convertToSignatures(signerCerts); @@ -93,12 +89,12 @@ public class ApkSignatureVerifier { } finally { closeQuietly(jarFile); } - return new Result(signerCerts, signerSigs); + return new Result(signerCerts, signerSigs, VERSION_APK_SIGNATURE_SCHEME_V2); } catch (SignatureNotFoundException e) { // not signed with v2, try older if allowed if (minSignatureSchemeVersion >= VERSION_APK_SIGNATURE_SCHEME_V2) { - throw new SignatureNotFoundException( - "No APK Signature Scheme v2 signature found for " + apkPath, e); + throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES, + "No APK Signature Scheme v2 signature in package " + apkPath, e); } } catch (PackageParserException e) { // preserve any new exceptions explicitly thrown here @@ -178,7 +174,7 @@ public class ApkSignatureVerifier { } } } - return new Result(lastCerts, lastSigs); + return new Result(lastCerts, lastSigs, VERSION_JAR_SIGNATURE_SCHEME); } catch (GeneralSecurityException e) { throw new PackageParserException(INSTALL_PARSE_FAILED_CERTIFICATE_ENCODING, "Failed to collect certificates from " + apkPath, e); @@ -254,10 +250,12 @@ public class ApkSignatureVerifier { public static class Result { public final Certificate[][] certs; public final Signature[] sigs; + public final int signatureSchemeVersion; - public Result(Certificate[][] certs, Signature[] sigs) { + public Result(Certificate[][] certs, Signature[] sigs, int signingVersion) { this.certs = certs; this.sigs = sigs; + this.signatureSchemeVersion = signingVersion; } } } diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 3f8da093487b..2c82ac49c84e 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -327,6 +327,8 @@ public final class ViewRootImpl implements ViewParent, // This is used to reduce the race between window focus changes being dispatched from // the window manager and input events coming through the input system. @GuardedBy("this") + boolean mWindowFocusChanged; + @GuardedBy("this") boolean mUpcomingWindowFocus; @GuardedBy("this") boolean mUpcomingInTouchMode; @@ -2472,14 +2474,14 @@ public final class ViewRootImpl implements ViewParent, final boolean hasWindowFocus; final boolean inTouchMode; synchronized (this) { + if (!mWindowFocusChanged) { + return; + } + mWindowFocusChanged = false; hasWindowFocus = mUpcomingWindowFocus; inTouchMode = mUpcomingInTouchMode; } - if (mAttachInfo.mHasWindowFocus == hasWindowFocus) { - return; - } - if (mAdded) { profileRendering(hasWindowFocus); @@ -7181,6 +7183,7 @@ public final class ViewRootImpl implements ViewParent, public void windowFocusChanged(boolean hasFocus, boolean inTouchMode) { synchronized (this) { + mWindowFocusChanged = true; mUpcomingWindowFocus = hasFocus; mUpcomingInTouchMode = inTouchMode; } diff --git a/core/java/android/view/textclassifier/TextClassifier.java b/core/java/android/view/textclassifier/TextClassifier.java index fdc9f92347db..ed6043038600 100644 --- a/core/java/android/view/textclassifier/TextClassifier.java +++ b/core/java/android/view/textclassifier/TextClassifier.java @@ -16,17 +16,23 @@ package android.view.textclassifier; +import android.annotation.IntDef; import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.StringDef; import android.annotation.WorkerThread; import android.os.LocaleList; +import android.util.ArraySet; import com.android.internal.util.Preconditions; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; /** * Interface for providing text classification related features. @@ -58,6 +64,20 @@ public interface TextClassifier { }) @interface EntityType {} + /** Designates that the TextClassifier should identify all entity types it can. **/ + int ENTITY_PRESET_ALL = 0; + /** Designates that the TextClassifier should identify no entities. **/ + int ENTITY_PRESET_NONE = 1; + /** Designates that the TextClassifier should identify a base set of entities determined by the + * TextClassifier. **/ + int ENTITY_PRESET_BASE = 2; + + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(prefix = { "ENTITY_CONFIG_" }, + value = {ENTITY_PRESET_ALL, ENTITY_PRESET_NONE, ENTITY_PRESET_BASE}) + @interface EntityPreset {} + /** * No-op TextClassifier. * This may be used to turn off TextClassifier features. @@ -217,6 +237,8 @@ public interface TextClassifier { * Returns a {@link TextLinks} that may be applied to the text to annotate it with links * information. * + * If no options are supplied, default values will be used, determined by the TextClassifier. + * * @param text the text to generate annotations for * @param options configuration for link generation * @@ -251,6 +273,16 @@ public interface TextClassifier { } /** + * Returns a {@link Collection} of the entity types in the specified preset. + * + * @see #ENTITIES_ALL + * @see #ENTITIES_NONE + */ + default Collection<String> getEntitiesForPreset(@EntityPreset int entityPreset) { + return Collections.EMPTY_LIST; + } + + /** * Logs a TextClassifier event. * * @param source the text classifier used to generate this event @@ -268,6 +300,62 @@ public interface TextClassifier { return TextClassifierConstants.DEFAULT; } + /** + * Configuration object for specifying what entities to identify. + * + * Configs are initially based on a predefined preset, and can be modified from there. + */ + final class EntityConfig { + private final @TextClassifier.EntityPreset int mEntityPreset; + private final Collection<String> mExcludedEntityTypes; + private final Collection<String> mIncludedEntityTypes; + + public EntityConfig(@TextClassifier.EntityPreset int mEntityPreset) { + this.mEntityPreset = mEntityPreset; + mExcludedEntityTypes = new ArraySet<>(); + mIncludedEntityTypes = new ArraySet<>(); + } + + /** + * Specifies an entity to include in addition to any specified by the enity preset. + * + * Note that if an entity has been excluded, the exclusion will take precedence. + */ + public EntityConfig includeEntities(String... entities) { + for (String entity : entities) { + mIncludedEntityTypes.add(entity); + } + return this; + } + + /** + * Specifies an entity to be excluded. + */ + public EntityConfig excludeEntities(String... entities) { + for (String entity : entities) { + mExcludedEntityTypes.add(entity); + } + return this; + } + + /** + * Returns an unmodifiable list of the final set of entities to find. + */ + public List<String> getEntities(TextClassifier textClassifier) { + ArrayList<String> entities = new ArrayList<>(); + for (String entity : textClassifier.getEntitiesForPreset(mEntityPreset)) { + if (!mExcludedEntityTypes.contains(entity)) { + entities.add(entity); + } + } + for (String entity : mIncludedEntityTypes) { + if (!mExcludedEntityTypes.contains(entity) && !entities.contains(entity)) { + entities.add(entity); + } + } + return Collections.unmodifiableList(entities); + } + } /** * Utility functions for TextClassifier methods. diff --git a/core/java/android/view/textclassifier/TextClassifierImpl.java b/core/java/android/view/textclassifier/TextClassifierImpl.java index d7aaee73f976..aea3cb0603f9 100644 --- a/core/java/android/view/textclassifier/TextClassifierImpl.java +++ b/core/java/android/view/textclassifier/TextClassifierImpl.java @@ -42,6 +42,9 @@ import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Locale; @@ -66,6 +69,18 @@ final class TextClassifierImpl implements TextClassifier { private static final String MODEL_FILE_REGEX = "textclassifier\\.smartselection\\.(.*)\\.model"; private static final String UPDATED_MODEL_FILE_PATH = "/data/misc/textclassifier/textclassifier.smartselection.model"; + private static final List<String> ENTITY_TYPES_ALL = + Collections.unmodifiableList(Arrays.asList( + TextClassifier.TYPE_ADDRESS, + TextClassifier.TYPE_EMAIL, + TextClassifier.TYPE_PHONE, + TextClassifier.TYPE_URL)); + private static final List<String> ENTITY_TYPES_BASE = + Collections.unmodifiableList(Arrays.asList( + TextClassifier.TYPE_ADDRESS, + TextClassifier.TYPE_EMAIL, + TextClassifier.TYPE_PHONE, + TextClassifier.TYPE_URL)); private final Context mContext; @@ -168,17 +183,23 @@ final class TextClassifierImpl implements TextClassifier { @Override public TextLinks generateLinks( - @NonNull CharSequence text, @NonNull TextLinks.Options options) { + @NonNull CharSequence text, @Nullable TextLinks.Options options) { Utils.validateInput(text); final String textString = text.toString(); final TextLinks.Builder builder = new TextLinks.Builder(textString); try { - LocaleList defaultLocales = options != null ? options.getDefaultLocales() : null; + final LocaleList defaultLocales = options != null ? options.getDefaultLocales() : null; + final Collection<String> entitiesToIdentify = + options != null && options.getEntityConfig() != null + ? options.getEntityConfig().getEntities(this) : ENTITY_TYPES_ALL; final SmartSelection smartSelection = getSmartSelection(defaultLocales); final SmartSelection.AnnotatedSpan[] annotations = smartSelection.annotate(textString); for (SmartSelection.AnnotatedSpan span : annotations) { - final Map<String, Float> entityScores = new HashMap<>(); final SmartSelection.ClassificationResult[] results = span.getClassification(); + if (results.length == 0 || !entitiesToIdentify.contains(results[0].mCollection)) { + continue; + } + final Map<String, Float> entityScores = new HashMap<>(); for (int i = 0; i < results.length; i++) { entityScores.put(results[i].mCollection, results[i].mScore); } @@ -193,6 +214,20 @@ final class TextClassifierImpl implements TextClassifier { } @Override + public Collection<String> getEntitiesForPreset(@TextClassifier.EntityPreset int entityPreset) { + switch (entityPreset) { + case TextClassifier.ENTITY_PRESET_NONE: + return Collections.emptyList(); + case TextClassifier.ENTITY_PRESET_BASE: + return ENTITY_TYPES_BASE; + case TextClassifier.ENTITY_PRESET_ALL: + // fall through + default: + return ENTITY_TYPES_ALL; + } + } + + @Override public void logEvent(String source, String event) { if (LOG_TAG.equals(source)) { mMetricsLogger.count(event, 1); diff --git a/core/java/android/view/textclassifier/TextLinks.java b/core/java/android/view/textclassifier/TextLinks.java index 0e039e35367e..6c587cf9d475 100644 --- a/core/java/android/view/textclassifier/TextLinks.java +++ b/core/java/android/view/textclassifier/TextLinks.java @@ -22,6 +22,8 @@ import android.annotation.Nullable; import android.os.LocaleList; import android.text.SpannableString; import android.text.style.ClickableSpan; +import android.view.View; +import android.widget.TextView; import com.android.internal.util.Preconditions; @@ -159,11 +161,12 @@ public final class TextLinks { public static final class Options { private LocaleList mDefaultLocales; + private TextClassifier.EntityConfig mEntityConfig; /** - * @param defaultLocales ordered list of locale preferences that may be used to disambiguate - * the provided text. If no locale preferences exist, set this to null or an empty - * locale list. + * @param defaultLocales ordered list of locale preferences that may be used to + * disambiguate the provided text. If no locale preferences exist, + * set this to null or an empty locale list. */ public Options setDefaultLocales(@Nullable LocaleList defaultLocales) { mDefaultLocales = defaultLocales; @@ -171,6 +174,17 @@ public final class TextLinks { } /** + * Sets the entity configuration to use. This determines what types of entities the + * TextClassifier will look for. + * + * @param entityConfig EntityConfig to use + */ + public Options setEntityConfig(@Nullable TextClassifier.EntityConfig entityConfig) { + mEntityConfig = entityConfig; + return this; + } + + /** * @return ordered list of locale preferences that can be used to disambiguate * the provided text. */ @@ -178,6 +192,15 @@ public final class TextLinks { public LocaleList getDefaultLocales() { return mDefaultLocales; } + + /** + * @return The config representing the set of entities to look for. + * @see #setEntityConfig(TextClassifier.EntityConfig) + */ + @Nullable + public TextClassifier.EntityConfig getEntityConfig() { + return mEntityConfig; + } } /** @@ -189,9 +212,14 @@ public final class TextLinks { * @hide */ public static final Function<TextLink, ClickableSpan> DEFAULT_SPAN_FACTORY = - textLink -> { - // TODO: Implement. - throw new UnsupportedOperationException("Not yet implemented"); + textLink -> new ClickableSpan() { + @Override + public void onClick(View widget) { + if (widget instanceof TextView) { + final TextView textView = (TextView) widget; + textView.requestActionMode(textLink); + } + } }; /** diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java index e9859239fdd6..de5a822da46b 100644 --- a/core/java/android/webkit/WebView.java +++ b/core/java/android/webkit/WebView.java @@ -2081,6 +2081,48 @@ public class WebView extends AbsoluteLayout } /** + * Define the directory used to store WebView data for the current process. + * The provided suffix will be used when constructing data and cache + * directory paths. If this API is not called, no suffix will be used. + * Each directory can be used by only one process in the application. If more + * than one process in an app wishes to use WebView, only one process can use + * the default directory, and other processes must call this API to define + * a unique suffix. + * <p> + * This API must be called before any instances of WebView are created in + * this process and before any other methods in the android.webkit package + * are called by this process. + * + * @param suffix The directory name suffix to be used for the current + * process. Must not contain a path separator. + * @throws IllegalStateException if WebView has already been initialized + * in the current process. + * @throws IllegalArgumentException if the suffix contains a path separator. + */ + public static void setDataDirectorySuffix(String suffix) { + WebViewFactory.setDataDirectorySuffix(suffix); + } + + /** + * Indicate that the current process does not intend to use WebView, and + * that an exception should be thrown if a WebView is created or any other + * methods in the android.webkit package are used. + * <p> + * Applications with multiple processes may wish to call this in processes + * which are not intended to use WebView to prevent potential data directory + * conflicts (see {@link #setDataDirectorySuffix}) and to avoid accidentally + * incurring the memory usage of initializing WebView in long-lived + * processes which have no need for it. + * + * @throws IllegalStateException if WebView has already been initialized + * in the current process. + */ + public static void disableWebView() { + WebViewFactory.disableWebView(); + } + + + /** * @deprecated This was used for Gears, which has been deprecated. * @hide */ diff --git a/core/java/android/webkit/WebViewDelegate.java b/core/java/android/webkit/WebViewDelegate.java index 73399313cbb5..f0670914558e 100644 --- a/core/java/android/webkit/WebViewDelegate.java +++ b/core/java/android/webkit/WebViewDelegate.java @@ -218,4 +218,11 @@ public final class WebViewDelegate { throw e.rethrowFromSystemServer(); } } + + /** + * Returns the data directory suffix to use, or null for none. + */ + public String getDataDirectorySuffix() { + return WebViewFactory.getDataDirectorySuffix(); + } } diff --git a/core/java/android/webkit/WebViewFactory.java b/core/java/android/webkit/WebViewFactory.java index 9db0e8d9a2fe..e3efad024341 100644 --- a/core/java/android/webkit/WebViewFactory.java +++ b/core/java/android/webkit/WebViewFactory.java @@ -33,6 +33,7 @@ import android.util.AndroidRuntimeException; import android.util.ArraySet; import android.util.Log; +import java.io.File; import java.lang.reflect.Method; /** @@ -63,6 +64,8 @@ public final class WebViewFactory { private static final Object sProviderLock = new Object(); private static PackageInfo sPackageInfo; private static Boolean sWebViewSupported; + private static boolean sWebViewDisabled; + private static String sDataDirectorySuffix; // stored here so it can be set without loading WV // Error codes for loadWebViewNativeLibraryFromPackage public static final int LIBLOAD_SUCCESS = 0; @@ -115,6 +118,45 @@ public final class WebViewFactory { /** * @hide */ + static void disableWebView() { + synchronized (sProviderLock) { + if (sProviderInstance != null) { + throw new IllegalStateException( + "Can't disable WebView: WebView already initialized"); + } + sWebViewDisabled = true; + } + } + + /** + * @hide + */ + static void setDataDirectorySuffix(String suffix) { + synchronized (sProviderLock) { + if (sProviderInstance != null) { + throw new IllegalStateException( + "Can't set data directory suffix: WebView already initialized"); + } + if (suffix.indexOf(File.separatorChar) >= 0) { + throw new IllegalArgumentException("Suffix " + suffix + + " contains a path separator"); + } + sDataDirectorySuffix = suffix; + } + } + + /** + * @hide + */ + static String getDataDirectorySuffix() { + synchronized (sProviderLock) { + return sDataDirectorySuffix; + } + } + + /** + * @hide + */ public static String getWebViewLibrary(ApplicationInfo ai) { if (ai.metaData != null) return ai.metaData.getString("com.android.webview.WebViewLibrary"); @@ -204,6 +246,11 @@ public final class WebViewFactory { throw new UnsupportedOperationException(); } + if (sWebViewDisabled) { + throw new IllegalStateException( + "WebView.disableWebView() was called: WebView is disabled"); + } + StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskReads(); Trace.traceBegin(Trace.TRACE_TAG_WEBVIEW, "WebViewFactory.getProvider()"); try { diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java index a4403984475f..05d18d180fbe 100644 --- a/core/java/android/widget/Editor.java +++ b/core/java/android/widget/Editor.java @@ -107,6 +107,7 @@ import android.view.inputmethod.ExtractedTextRequest; import android.view.inputmethod.InputConnection; import android.view.inputmethod.InputMethodManager; import android.view.textclassifier.TextClassification; +import android.view.textclassifier.TextLinks; import android.widget.AdapterView.OnItemClickListener; import android.widget.TextView.Drawables; import android.widget.TextView.OnEditorActionListener; @@ -174,6 +175,13 @@ public class Editor { int SELECTION_END = 2; } + @IntDef({TextActionMode.SELECTION, TextActionMode.INSERTION, TextActionMode.TEXT_LINK}) + @interface TextActionMode { + int SELECTION = 0; + int INSERTION = 1; + int TEXT_LINK = 2; + } + // Each Editor manages its own undo stack. private final UndoManager mUndoManager = new UndoManager(); private UndoOwner mUndoOwner = mUndoManager.getOwner(UNDO_OWNER_TAG, this); @@ -2053,7 +2061,7 @@ public class Editor { stopTextActionMode(); ActionMode.Callback actionModeCallback = - new TextActionModeCallback(false /* hasSelection */); + new TextActionModeCallback(TextActionMode.INSERTION); mTextActionMode = mTextView.startActionMode( actionModeCallback, ActionMode.TYPE_FLOATING); if (mTextActionMode != null && getInsertionController() != null) { @@ -2079,7 +2087,23 @@ public class Editor { * Asynchronously starts a selection action mode using the TextClassifier. */ void startSelectionActionModeAsync(boolean adjustSelection) { - getSelectionActionModeHelper().startActionModeAsync(adjustSelection); + getSelectionActionModeHelper().startSelectionActionModeAsync(adjustSelection); + } + + void startLinkActionModeAsync(TextLinks.TextLink link) { + Preconditions.checkNotNull(link); + if (!(mTextView.getText() instanceof Spannable)) { + return; + } + Spannable text = (Spannable) mTextView.getText(); + stopTextActionMode(); + if (mTextView.isTextSelectable()) { + Selection.setSelection((Spannable) text, link.getStart(), link.getEnd()); + } else { + //TODO: Nonselectable text + } + + getSelectionActionModeHelper().startLinkActionModeAsync(link); } /** @@ -2145,7 +2169,7 @@ public class Editor { return true; } - boolean startSelectionActionModeInternal() { + boolean startActionModeInternal(@TextActionMode int actionMode) { if (extractedTextModeWillBeStarted()) { return false; } @@ -2159,8 +2183,7 @@ public class Editor { return false; } - ActionMode.Callback actionModeCallback = - new TextActionModeCallback(true /* hasSelection */); + ActionMode.Callback actionModeCallback = new TextActionModeCallback(actionMode); mTextActionMode = mTextView.startActionMode(actionModeCallback, ActionMode.TYPE_FLOATING); final boolean selectionStarted = mTextActionMode != null; @@ -3828,8 +3851,9 @@ public class Editor { private final int mHandleHeight; private final Map<MenuItem, OnClickListener> mAssistClickHandlers = new HashMap<>(); - public TextActionModeCallback(boolean hasSelection) { - mHasSelection = hasSelection; + TextActionModeCallback(@TextActionMode int mode) { + mHasSelection = mode == TextActionMode.SELECTION + || (mTextIsSelectable && mode == TextActionMode.TEXT_LINK); if (mHasSelection) { SelectionModifierCursorController selectionController = getSelectionController(); if (selectionController.mStartHandle == null) { diff --git a/core/java/android/widget/SelectionActionModeHelper.java b/core/java/android/widget/SelectionActionModeHelper.java index d0ad27af0a92..2c6466cd1b9b 100644 --- a/core/java/android/widget/SelectionActionModeHelper.java +++ b/core/java/android/widget/SelectionActionModeHelper.java @@ -35,6 +35,7 @@ import android.util.Log; import android.view.ActionMode; import android.view.textclassifier.TextClassification; import android.view.textclassifier.TextClassifier; +import android.view.textclassifier.TextLinks; import android.view.textclassifier.TextSelection; import android.view.textclassifier.logging.SmartSelectionEventTracker; import android.view.textclassifier.logging.SmartSelectionEventTracker.SelectionEvent; @@ -97,7 +98,10 @@ public final class SelectionActionModeHelper { } } - public void startActionModeAsync(boolean adjustSelection) { + /** + * Starts Selection ActionMode. + */ + public void startSelectionActionModeAsync(boolean adjustSelection) { // Check if the smart selection should run for editable text. adjustSelection &= !mTextView.isTextEditable() || mTextView.getTextClassifier().getSettings() @@ -109,7 +113,7 @@ public final class SelectionActionModeHelper { mTextView.getSelectionEnd()); cancelAsyncTask(); if (skipTextClassification()) { - startActionMode(null); + startSelectionActionMode(null); } else { resetTextClassificationHelper(); mTextClassificationAsyncTask = new TextClassificationAsyncTask( @@ -119,8 +123,27 @@ public final class SelectionActionModeHelper { ? mTextClassificationHelper::suggestSelection : mTextClassificationHelper::classifyText, mSmartSelectSprite != null - ? this::startActionModeWithSmartSelectAnimation - : this::startActionMode) + ? this::startSelectionActionModeWithSmartSelectAnimation + : this::startSelectionActionMode) + .execute(); + } + } + + /** + * Starts Link ActionMode. + */ + public void startLinkActionModeAsync(TextLinks.TextLink textLink) { + //TODO: tracking/logging + cancelAsyncTask(); + if (skipTextClassification()) { + startLinkActionMode(null); + } else { + resetTextClassificationHelper(textLink.getStart(), textLink.getEnd()); + mTextClassificationAsyncTask = new TextClassificationAsyncTask( + mTextView, + mTextClassificationHelper.getTimeoutDuration(), + mTextClassificationHelper::classifyText, + this::startLinkActionMode) .execute(); } } @@ -200,9 +223,19 @@ public final class SelectionActionModeHelper { return noOpTextClassifier || noSelection || password; } - private void startActionMode(@Nullable SelectionResult result) { + private void startLinkActionMode(@Nullable SelectionResult result) { + startActionMode(Editor.TextActionMode.TEXT_LINK, result); + } + + private void startSelectionActionMode(@Nullable SelectionResult result) { + startActionMode(Editor.TextActionMode.SELECTION, result); + } + + private void startActionMode( + @Editor.TextActionMode int actionMode, @Nullable SelectionResult result) { final CharSequence text = getText(mTextView); - if (result != null && text instanceof Spannable) { + if (result != null && text instanceof Spannable + && (mTextView.isTextSelectable() || mTextView.isTextEditable())) { // Do not change the selection if TextClassifier should be dark launched. if (!mTextView.getTextClassifier().getSettings().isDarkLaunch()) { Selection.setSelection((Spannable) text, result.mStart, result.mEnd); @@ -211,12 +244,13 @@ public final class SelectionActionModeHelper { } else { mTextClassification = null; } - if (mEditor.startSelectionActionModeInternal()) { + if (mEditor.startActionModeInternal(actionMode)) { final SelectionModifierCursorController controller = mEditor.getSelectionController(); - if (controller != null) { + if (controller != null + && (mTextView.isTextSelectable() || mTextView.isTextEditable())) { controller.show(); } - if (result != null) { + if (result != null && actionMode == Editor.TextActionMode.SELECTION) { mSelectionTracker.onSmartSelection(result); } } @@ -224,10 +258,11 @@ public final class SelectionActionModeHelper { mTextClassificationAsyncTask = null; } - private void startActionModeWithSmartSelectAnimation(@Nullable SelectionResult result) { + private void startSelectionActionModeWithSmartSelectAnimation( + @Nullable SelectionResult result) { final Layout layout = mTextView.getLayout(); - final Runnable onAnimationEndCallback = () -> startActionMode(result); + final Runnable onAnimationEndCallback = () -> startSelectionActionMode(result); // TODO do not trigger the animation if the change included only non-printable characters final boolean didSelectionChange = result != null && (mTextView.getSelectionStart() != result.mStart @@ -386,15 +421,24 @@ public final class SelectionActionModeHelper { mTextClassificationAsyncTask = null; } - private void resetTextClassificationHelper() { + private void resetTextClassificationHelper(int selectionStart, int selectionEnd) { + if (selectionStart < 0 || selectionEnd < 0) { + // Use selection indices + selectionStart = mTextView.getSelectionStart(); + selectionEnd = mTextView.getSelectionEnd(); + } mTextClassificationHelper.init( mTextView.getContext(), mTextView.getTextClassifier(), getText(mTextView), - mTextView.getSelectionStart(), mTextView.getSelectionEnd(), + selectionStart, selectionEnd, mTextView.getTextLocales()); } + private void resetTextClassificationHelper() { + resetTextClassificationHelper(-1, -1); + } + private void cancelSmartSelectAnimation() { if (mSmartSelectSprite != null) { mSmartSelectSprite.cancelAnimation(); diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index 903d3cafcef8..9ac443b43701 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -160,6 +160,7 @@ import android.view.inputmethod.InputConnection; import android.view.inputmethod.InputMethodManager; import android.view.textclassifier.TextClassificationManager; import android.view.textclassifier.TextClassifier; +import android.view.textclassifier.TextLinks; import android.view.textservice.SpellCheckerSubtype; import android.view.textservice.TextServicesManager; import android.widget.RemoteViews.RemoteView; @@ -168,6 +169,7 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.internal.util.FastMath; +import com.android.internal.util.Preconditions; import com.android.internal.widget.EditableInputConnection; import libcore.util.EmptyArray; @@ -11151,6 +11153,20 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } /** + * Starts an ActionMode for the specified TextLink. + * + * @return Whether or not we're attempting to start the action mode. + * @hide + */ + public boolean requestActionMode(@NonNull TextLinks.TextLink link) { + Preconditions.checkNotNull(link); + if (mEditor != null) { + mEditor.startLinkActionModeAsync(link); + return true; + } + return false; + } + /** * @hide */ protected void stopTextActionMode() { diff --git a/core/java/com/android/internal/app/procstats/ProcessState.java b/core/java/com/android/internal/app/procstats/ProcessState.java index 6fb02b162309..3aca7981f92f 100644 --- a/core/java/com/android/internal/app/procstats/ProcessState.java +++ b/core/java/com/android/internal/app/procstats/ProcessState.java @@ -96,9 +96,9 @@ public final class ProcessState { STATE_IMPORTANT_BACKGROUND, // ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND STATE_IMPORTANT_BACKGROUND, // ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND STATE_BACKUP, // ActivityManager.PROCESS_STATE_BACKUP - STATE_HEAVY_WEIGHT, // ActivityManager.PROCESS_STATE_HEAVY_WEIGHT STATE_SERVICE, // ActivityManager.PROCESS_STATE_SERVICE STATE_RECEIVER, // ActivityManager.PROCESS_STATE_RECEIVER + STATE_HEAVY_WEIGHT, // ActivityManager.PROCESS_STATE_HEAVY_WEIGHT STATE_HOME, // ActivityManager.PROCESS_STATE_HOME STATE_LAST_ACTIVITY, // ActivityManager.PROCESS_STATE_LAST_ACTIVITY STATE_CACHED_ACTIVITY, // ActivityManager.PROCESS_STATE_CACHED_ACTIVITY diff --git a/core/java/com/android/internal/app/procstats/ProcessStats.java b/core/java/com/android/internal/app/procstats/ProcessStats.java index 2ce7936d157b..96ba2b0c5fd9 100644 --- a/core/java/com/android/internal/app/procstats/ProcessStats.java +++ b/core/java/com/android/internal/app/procstats/ProcessStats.java @@ -81,10 +81,10 @@ public final class ProcessStats implements Parcelable { public static final int STATE_IMPORTANT_FOREGROUND = 2; public static final int STATE_IMPORTANT_BACKGROUND = 3; public static final int STATE_BACKUP = 4; - public static final int STATE_HEAVY_WEIGHT = 5; - public static final int STATE_SERVICE = 6; - public static final int STATE_SERVICE_RESTARTING = 7; - public static final int STATE_RECEIVER = 8; + public static final int STATE_SERVICE = 5; + public static final int STATE_SERVICE_RESTARTING = 6; + public static final int STATE_RECEIVER = 7; + public static final int STATE_HEAVY_WEIGHT = 8; public static final int STATE_HOME = 9; public static final int STATE_LAST_ACTIVITY = 10; public static final int STATE_CACHED_ACTIVITY = 11; @@ -141,8 +141,8 @@ public final class ProcessStats implements Parcelable { public static final int[] NON_CACHED_PROC_STATES = new int[] { STATE_PERSISTENT, STATE_TOP, STATE_IMPORTANT_FOREGROUND, - STATE_IMPORTANT_BACKGROUND, STATE_BACKUP, STATE_HEAVY_WEIGHT, - STATE_SERVICE, STATE_SERVICE_RESTARTING, STATE_RECEIVER + STATE_IMPORTANT_BACKGROUND, STATE_BACKUP, + STATE_SERVICE, STATE_SERVICE_RESTARTING, STATE_RECEIVER, STATE_HEAVY_WEIGHT }; public static final int[] BACKGROUND_PROC_STATES = new int[] { @@ -152,13 +152,13 @@ public final class ProcessStats implements Parcelable { public static final int[] ALL_PROC_STATES = new int[] { STATE_PERSISTENT, STATE_TOP, STATE_IMPORTANT_FOREGROUND, STATE_IMPORTANT_BACKGROUND, STATE_BACKUP, - STATE_HEAVY_WEIGHT, STATE_SERVICE, STATE_SERVICE_RESTARTING, STATE_RECEIVER, - STATE_HOME, STATE_LAST_ACTIVITY, STATE_CACHED_ACTIVITY, + STATE_SERVICE, STATE_SERVICE_RESTARTING, STATE_RECEIVER, + STATE_HEAVY_WEIGHT, STATE_HOME, STATE_LAST_ACTIVITY, STATE_CACHED_ACTIVITY, STATE_CACHED_ACTIVITY_CLIENT, STATE_CACHED_EMPTY }; // Current version of the parcel format. - private static final int PARCEL_VERSION = 22; + private static final int PARCEL_VERSION = 23; // In-memory Parcel magic number, used to detect attempts to unmarshall bad data private static final int MAGIC = 0x50535454; diff --git a/core/java/com/android/internal/content/FileSystemProvider.java b/core/java/com/android/internal/content/FileSystemProvider.java index d49d572310d7..a075705c2e40 100644 --- a/core/java/com/android/internal/content/FileSystemProvider.java +++ b/core/java/com/android/internal/content/FileSystemProvider.java @@ -236,6 +236,7 @@ public abstract class FileSystemProvider extends DocumentsProvider { moveInMediaStore(visibleFileBefore, getFileForDocId(afterDocId, true)); if (!TextUtils.equals(docId, afterDocId)) { + scanFile(after); return afterDocId; } else { return null; diff --git a/core/java/com/android/internal/os/ByteTransferPipe.java b/core/java/com/android/internal/os/ByteTransferPipe.java new file mode 100644 index 000000000000..648989455b2b --- /dev/null +++ b/core/java/com/android/internal/os/ByteTransferPipe.java @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.os; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStream; + +/** + * Helper class to get byte data through a pipe from a client app. Also {@see TransferPipe}. + */ +public class ByteTransferPipe extends TransferPipe { + static final String TAG = "ByteTransferPipe"; + + private ByteArrayOutputStream mOutputStream; + + public ByteTransferPipe() throws IOException { + super(); + } + + public ByteTransferPipe(String bufferPrefix) throws IOException { + super(bufferPrefix, "ByteTransferPipe"); + } + + @Override + protected OutputStream getNewOutputStream() { + mOutputStream = new ByteArrayOutputStream(); + return mOutputStream; + } + + public byte[] get() throws IOException { + go(null); + return mOutputStream.toByteArray(); + } +} diff --git a/core/java/com/android/internal/os/TransferPipe.java b/core/java/com/android/internal/os/TransferPipe.java index 738ecc0bdaa0..1c09bd6a2a66 100644 --- a/core/java/com/android/internal/os/TransferPipe.java +++ b/core/java/com/android/internal/os/TransferPipe.java @@ -34,11 +34,12 @@ import java.io.FileDescriptor; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; +import java.io.OutputStream; /** * Helper for transferring data through a pipe from a client app. */ -public final class TransferPipe implements Runnable, Closeable { +public class TransferPipe implements Runnable, Closeable { static final String TAG = "TransferPipe"; static final boolean DEBUG = false; @@ -64,7 +65,11 @@ public final class TransferPipe implements Runnable, Closeable { } public TransferPipe(String bufferPrefix) throws IOException { - mThread = new Thread(this, "TransferPipe"); + this(bufferPrefix, "TransferPipe"); + } + + protected TransferPipe(String bufferPrefix, String threadName) throws IOException { + mThread = new Thread(this, threadName); mFds = ParcelFileDescriptor.createPipe(); mBufferPrefix = bufferPrefix; } @@ -234,11 +239,15 @@ public final class TransferPipe implements Runnable, Closeable { } } + protected OutputStream getNewOutputStream() { + return new FileOutputStream(mOutFd); + } + @Override public void run() { final byte[] buffer = new byte[1024]; final FileInputStream fis; - final FileOutputStream fos; + final OutputStream fos; synchronized (this) { ParcelFileDescriptor readFd = getReadFd(); @@ -247,7 +256,7 @@ public final class TransferPipe implements Runnable, Closeable { return; } fis = new FileInputStream(readFd.getFileDescriptor()); - fos = new FileOutputStream(mOutFd); + fos = getNewOutputStream(); } if (DEBUG) Slog.i(TAG, "Ready to read pipe..."); diff --git a/core/java/com/android/internal/view/menu/ListMenuItemView.java b/core/java/com/android/internal/view/menu/ListMenuItemView.java index 8f80bfe3fb50..0d2b29b69586 100644 --- a/core/java/com/android/internal/view/menu/ListMenuItemView.java +++ b/core/java/com/android/internal/view/menu/ListMenuItemView.java @@ -50,6 +50,7 @@ public class ListMenuItemView extends LinearLayout private TextView mShortcutView; private ImageView mSubMenuArrowView; private ImageView mGroupDivider; + private LinearLayout mContent; private Drawable mBackground; private int mTextAppearance; @@ -114,6 +115,8 @@ public class ListMenuItemView extends LinearLayout mSubMenuArrowView.setImageDrawable(mSubMenuArrow); } mGroupDivider = findViewById(com.android.internal.R.id.group_divider); + + mContent = findViewById(com.android.internal.R.id.content); } public void initialize(MenuItemImpl itemData, int menuType) { @@ -131,6 +134,18 @@ public class ListMenuItemView extends LinearLayout setContentDescription(itemData.getContentDescription()); } + private void addContentView(View v) { + addContentView(v, -1); + } + + private void addContentView(View v, int index) { + if (mContent != null) { + mContent.addView(v, index); + } else { + addView(v, index); + } + } + public void setForceShowIcon(boolean forceShow) { mPreserveIconSpacing = mForceShowIcon = forceShow; } @@ -270,7 +285,7 @@ public class ListMenuItemView extends LinearLayout LayoutInflater inflater = getInflater(); mIconView = (ImageView) inflater.inflate(com.android.internal.R.layout.list_menu_item_icon, this, false); - addView(mIconView, 0); + addContentView(mIconView, 0); } private void insertRadioButton() { @@ -278,7 +293,7 @@ public class ListMenuItemView extends LinearLayout mRadioButton = (RadioButton) inflater.inflate(com.android.internal.R.layout.list_menu_item_radio, this, false); - addView(mRadioButton); + addContentView(mRadioButton); } private void insertCheckBox() { @@ -286,7 +301,7 @@ public class ListMenuItemView extends LinearLayout mCheckBox = (CheckBox) inflater.inflate(com.android.internal.R.layout.list_menu_item_checkbox, this, false); - addView(mCheckBox); + addContentView(mCheckBox); } public boolean prefersCondensedTitle() { diff --git a/core/java/com/android/internal/widget/ILockSettings.aidl b/core/java/com/android/internal/widget/ILockSettings.aidl index ee16ab609a2a..164a74509ed7 100644 --- a/core/java/com/android/internal/widget/ILockSettings.aidl +++ b/core/java/com/android/internal/widget/ILockSettings.aidl @@ -17,6 +17,10 @@ package com.android.internal.widget; import android.app.trust.IStrongAuthTracker; +import android.os.Bundle; +import android.security.recoverablekeystore.KeyEntryRecoveryData; +import android.security.recoverablekeystore.KeyStoreRecoveryData; +import android.security.recoverablekeystore.KeyStoreRecoveryMetadata; import com.android.internal.widget.ICheckCredentialProgressCallback; import com.android.internal.widget.VerifyCredentialResponse; @@ -52,4 +56,22 @@ interface ILockSettings { boolean setLockCredentialWithToken(String credential, int type, long tokenHandle, in byte[] token, int requestedQuality, int userId); void unlockUserWithToken(long tokenHandle, in byte[] token, int userId); + + // RecoverableKeyStoreLoader methods. + // {@code ServiceSpecificException} may be thrown to signal an error, which caller can + // convert to {@code RecoverableKeyStoreLoader}. + void initRecoveryService(in String rootCertificateAlias, in byte[] signedPublicKeyList, + int userId); + KeyStoreRecoveryData getRecoveryData(in byte[] account, int userId); + void setServerParameters(long serverParameters, int userId); + void setRecoveryStatus(in String packageName, in String[] aliases, int status, int userId); + void setRecoverySecretTypes(in int[] secretTypes, int userId); + int[] getRecoverySecretTypes(int userId); + int[] getPendingRecoverySecretTypes(int userId); + void recoverySecretAvailable(in KeyStoreRecoveryMetadata recoverySecret, int userId); + byte[] startRecoverySession(in String sessionId, + in byte[] verifierPublicKey, in byte[] vaultParams, in byte[] vaultChallenge, + in List<KeyStoreRecoveryMetadata> secrets, int userId); + void recoverKeys(in String sessionId, in byte[] recoveryKeyBlob, + in List<KeyEntryRecoveryData> applicationKeys, int userId); } diff --git a/core/proto/android/internal/processstats.proto b/core/proto/android/internal/processstats.proto new file mode 100644 index 000000000000..5629c2d291f7 --- /dev/null +++ b/core/proto/android/internal/processstats.proto @@ -0,0 +1,28 @@ +/* + * 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. + */ + +syntax = "proto2"; + +package com.android.internal.app.procstats; + +message ProcessStatsProto { + enum MemoryFactor { + MEM_FACTOR_NORMAL = 0; + MEM_FACTOR_MODERATE = 1; + MEM_FACTOR_LOW = 2; + MEM_FACTOR_CRITICAL = 3; + } +} diff --git a/core/proto/android/os/incident.proto b/core/proto/android/os/incident.proto index 4015a7e7cea9..ac8f26d94ca2 100644 --- a/core/proto/android/os/incident.proto +++ b/core/proto/android/os/incident.proto @@ -28,13 +28,14 @@ import "frameworks/base/core/proto/android/os/system_properties.proto"; import "frameworks/base/core/proto/android/providers/settings.proto"; import "frameworks/base/core/proto/android/server/activitymanagerservice.proto"; import "frameworks/base/core/proto/android/server/alarmmanagerservice.proto"; -import "frameworks/base/core/proto/android/server/windowmanagerservice.proto"; import "frameworks/base/core/proto/android/server/fingerprint.proto"; import "frameworks/base/core/proto/android/server/powermanagerservice.proto"; +import "frameworks/base/core/proto/android/server/windowmanagerservice.proto"; import "frameworks/base/core/proto/android/service/appwidget.proto"; import "frameworks/base/core/proto/android/service/battery.proto"; import "frameworks/base/core/proto/android/service/batterystats.proto"; import "frameworks/base/core/proto/android/service/diskstats.proto"; +import "frameworks/base/core/proto/android/service/graphicsstats.proto"; import "frameworks/base/core/proto/android/service/netstats.proto"; import "frameworks/base/core/proto/android/service/notification.proto"; import "frameworks/base/core/proto/android/service/package.proto"; @@ -176,6 +177,11 @@ message IncidentProto { optional com.android.server.am.proto.MemInfoProto meminfo = 3018 [ (section).type = SECTION_DUMPSYS, - (section).args = "meminfo --proto" + (section).args = "meminfo -a --proto" + ]; + + optional android.service.GraphicsStatsServiceDumpProto graphicsstats = 3019 [ + (section).type = SECTION_DUMPSYS, + (section).args = "graphicsstats --proto" ]; } diff --git a/core/proto/android/server/activitymanagerservice.proto b/core/proto/android/server/activitymanagerservice.proto index 3af6f51fffc9..d3ca496995a9 100644 --- a/core/proto/android/server/activitymanagerservice.proto +++ b/core/proto/android/server/activitymanagerservice.proto @@ -21,6 +21,7 @@ package com.android.server.am.proto; import "frameworks/base/core/proto/android/app/notification.proto"; import "frameworks/base/core/proto/android/content/intent.proto"; import "frameworks/base/core/proto/android/graphics/rect.proto"; +import "frameworks/base/core/proto/android/internal/processstats.proto"; import "frameworks/base/core/proto/android/os/looper.proto"; import "frameworks/base/core/proto/android/server/intentresolver.proto"; import "frameworks/base/core/proto/android/server/windowmanagerservice.proto"; @@ -163,7 +164,7 @@ message MemInfoProto { optional int64 uptime_duration_ms = 1; optional int64 elapsed_realtime_ms = 2; - message NativeProcess { + message ProcessMemory { optional int32 pid = 1; optional string process_name = 2; @@ -197,7 +198,7 @@ message MemInfoProto { optional HeapInfo native_heap = 3; optional HeapInfo dalvik_heap = 4; repeated MemoryInfo other_heaps = 5; - optional HeapInfo unknown_heap = 6; + optional MemoryInfo unknown_heap = 6; // Summation of native_heap, dalvik_heap, and other_heaps. optional HeapInfo total_heap = 7; @@ -219,7 +220,113 @@ message MemInfoProto { } optional AppSummary app_summary = 9; } - repeated NativeProcess native_processes = 3; + repeated ProcessMemory native_processes = 3; + + message AppData { + optional ProcessMemory process_memory = 1; + + message ObjectStats { + optional int32 view_instance_count = 1; + optional int32 view_root_instance_count = 2; + optional int32 app_context_instance_count = 3; + optional int32 activity_instance_count = 4; + optional int32 global_asset_count = 5; + optional int32 global_asset_manager_count = 6; + optional int32 local_binder_object_count = 7; + optional int32 proxy_binder_object_count = 8; + optional int64 parcel_memory_kb = 9; + optional int32 parcel_count = 10; + optional int32 binder_object_death_count = 11; + optional int32 open_ssl_socket_count = 12; + optional int32 webview_instance_count = 13; + } + optional ObjectStats objects = 2; + + message SqlStats { + optional int32 memory_used_kb = 1; + optional int32 pagecache_overflow_kb = 2; + optional int32 malloc_size_kb = 3; + + message Database { + optional string name = 1; + optional int32 page_size = 2; + optional int32 db_size = 3; + // Number of lookaside slots: + // http://www.sqlite.org/c3ref/c_dbstatus_lookaside_used.html + optional int32 lookaside_b = 4; + // Statement cache stats: hits/misses/cachesize + optional string cache = 5; + } + repeated Database databases = 4; + } + optional SqlStats sql = 3; + + optional string asset_allocations = 4; + optional string unreachable_memory = 5; + } + repeated AppData app_processes = 4; + + message MemItem { + optional string tag = 1; + optional string label = 2; + optional int32 id = 3; + optional bool is_proc = 4; + optional bool has_activities = 5; + optional int64 pss_kb = 6; + optional int64 swap_pss_kb = 7; + repeated MemItem sub_items = 8; + } + repeated MemItem total_pss_by_process = 5; + repeated MemItem total_pss_by_oom_adjustment = 6; + repeated MemItem total_pss_by_category = 7; + + optional int64 total_ram_kb = 8; + optional .com.android.internal.app.procstats.ProcessStatsProto.MemoryFactor status = 9; + // Total free RAM = cached_pss_kb + cached_kernel_kb + free_kb. + optional int64 cached_pss_kb = 10; + optional int64 cached_kernel_kb = 11; + optional int64 free_kb = 12; + // Total used RAM = used_pss_kb + used_kernel_kb. + optional int64 used_pss_kb = 13; + optional int64 used_kernel_kb = 14; + + optional int64 lost_ram_kb = 15; + + optional int64 total_zram_kb = 16; + optional int64 zram_physical_used_in_swap_kb = 17; + optional int64 total_zram_swap_kb = 18; + + optional int64 ksm_sharing_kb = 19; + optional int64 ksm_shared_kb = 20; + optional int64 ksm_unshared_kb = 21; + optional int64 ksm_volatile_kb = 22; + + // The approximate per-application memory class of the current device. This + // gives developers an idea of how hard a memory limit you should impose on + // their application to let the overall system work best. The value is in + // megabytes; the baseline Android memory class is 16 (which happens to be the + // Java heap limit of those devices); some devices with more memory may have + // 24 or even higher numbers. + optional int32 tuning_mb = 23; + // The approximate per-application memory class of the current device when an + // application is running with a large heap. This is the space available for + // memory-intensive applications; most applications should not need this + // amount of memory, and should instead stay with the tuning_mb limit. The + // value is in megabytes. This may be the same size as tuning_mb on memory + // constrained devices, or it may be significantly larger on devices with a + // large amount of available RAM. + // This is the size of the application's Dalvik heap if it has specified + // 'android:largeHeap="true"' in its manifest. + optional int32 tuning_large_mb = 24; + + optional int64 oom_kb = 25; + + // The maximum pss size in kb that we consider a process acceptable to restore + // from its cached state for running in the background when RAM is low. + optional int64 restore_limit_kb = 26; + + optional bool is_low_ram_device = 27; + optional bool is_high_end_gfx = 28; } message StickyBroadcastProto { diff --git a/core/proto/android/service/graphicsstats.proto b/core/proto/android/service/graphicsstats.proto index 801411dbed13..f42206562fc4 100644 --- a/core/proto/android/service/graphicsstats.proto +++ b/core/proto/android/service/graphicsstats.proto @@ -19,7 +19,6 @@ package android.service; option java_multiple_files = true; option java_outer_classname = "GraphicsStatsServiceProto"; -option optimize_for = LITE_RUNTIME; message GraphicsStatsServiceDumpProto { repeated GraphicsStatsProto stats = 1; diff --git a/core/res/res/interpolator/aggressive_ease.xml b/core/res/res/interpolator/aggressive_ease.xml new file mode 100644 index 000000000000..620424fb4112 --- /dev/null +++ b/core/res/res/interpolator/aggressive_ease.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2017 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License + --> + +<pathInterpolator xmlns:android="http://schemas.android.com/apk/res/android" + android:controlX1="0.2" + android:controlY1="0" + android:controlX2="0" + android:controlY2="1"/>
\ No newline at end of file diff --git a/core/res/res/interpolator/emphasized_deceleration.xml b/core/res/res/interpolator/emphasized_deceleration.xml new file mode 100644 index 000000000000..60c315c0c4d4 --- /dev/null +++ b/core/res/res/interpolator/emphasized_deceleration.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2017 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License + --> + +<pathInterpolator xmlns:android="http://schemas.android.com/apk/res/android" + android:controlX1="0.1" + android:controlY1="0.8" + android:controlX2="0.2" + android:controlY2="1"/>
\ No newline at end of file diff --git a/core/res/res/interpolator/exaggerated_ease.xml b/core/res/res/interpolator/exaggerated_ease.xml new file mode 100644 index 000000000000..4961c1c72984 --- /dev/null +++ b/core/res/res/interpolator/exaggerated_ease.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2017 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License + --> + +<pathInterpolator xmlns:android="http://schemas.android.com/apk/res/android" + android:pathData="M 0,0 C 0.05, 0, 0.133333, 0.08, 0.166666, 0.4 C 0.225, 0.94, 0.25, 1, 1, 1"/> diff --git a/core/res/res/layout/popup_menu_item_layout.xml b/core/res/res/layout/popup_menu_item_layout.xml index 3eecb36f0ea6..3b89f0d0cde7 100644 --- a/core/res/res/layout/popup_menu_item_layout.xml +++ b/core/res/res/layout/popup_menu_item_layout.xml @@ -33,6 +33,7 @@ <!-- The title and summary have some gap between them, and this 'group' should be centered vertically. --> <LinearLayout + android:id="@+id/content" android:layout_width="wrap_content" android:layout_height="?attr/dropdownListPreferredItemHeight" android:paddingEnd="16dip" diff --git a/core/res/res/values-af/required_apps_managed_device.xml b/core/res/res/values-af/required_apps_managed_device.xml new file mode 100644 index 000000000000..9044fccd88ee --- /dev/null +++ b/core/res/res/values-af/required_apps_managed_device.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string-array name="required_apps_managed_device"> + <item msgid="1104492179978792509">"com.android.settings"</item> + <item msgid="7004798084799227194">"com.android.contacts"</item> + <item msgid="5782220690863647256">"com.android.dialer"</item> + <item msgid="5746338511138092673">"com.android.stk"</item> + <item msgid="1712599182168590664">"com.android.providers.downloads"</item> + <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item> + <item msgid="3892021562839042708">"com.android.documentsui"</item> + </string-array> +</resources> diff --git a/core/res/res/values-af/required_apps_managed_profile.xml b/core/res/res/values-af/required_apps_managed_profile.xml new file mode 100644 index 000000000000..4296b0d37dd2 --- /dev/null +++ b/core/res/res/values-af/required_apps_managed_profile.xml @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string-array name="required_apps_managed_profile"> + <item msgid="1457364287544474838">"com.android.contacts"</item> + <item msgid="4633145750237794002">"com.android.settings"</item> + <item msgid="6518205098643077579">"com.android.providers.downloads"</item> + <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item> + <item msgid="6106837921940099371">"com.android.documentsui"</item> + </string-array> +</resources> diff --git a/core/res/res/values-af/required_apps_managed_user.xml b/core/res/res/values-af/required_apps_managed_user.xml new file mode 100644 index 000000000000..1a7ade9dd5f9 --- /dev/null +++ b/core/res/res/values-af/required_apps_managed_user.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string-array name="required_apps_managed_user"> + <item msgid="4823915868435007499">"com.android.settings"</item> + <item msgid="2250259015310893915">"com.android.contacts"</item> + <item msgid="7166574999426592423">"com.android.dialer"</item> + <item msgid="7306937186458176744">"com.android.stk"</item> + <item msgid="7415441588151512455">"com.android.providers.downloads"</item> + <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item> + <item msgid="8640522622655589373">"com.android.documentsui"</item> + </string-array> +</resources> diff --git a/core/res/res/values-am/required_apps_managed_device.xml b/core/res/res/values-am/required_apps_managed_device.xml new file mode 100644 index 000000000000..9044fccd88ee --- /dev/null +++ b/core/res/res/values-am/required_apps_managed_device.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string-array name="required_apps_managed_device"> + <item msgid="1104492179978792509">"com.android.settings"</item> + <item msgid="7004798084799227194">"com.android.contacts"</item> + <item msgid="5782220690863647256">"com.android.dialer"</item> + <item msgid="5746338511138092673">"com.android.stk"</item> + <item msgid="1712599182168590664">"com.android.providers.downloads"</item> + <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item> + <item msgid="3892021562839042708">"com.android.documentsui"</item> + </string-array> +</resources> diff --git a/core/res/res/values-am/required_apps_managed_profile.xml b/core/res/res/values-am/required_apps_managed_profile.xml new file mode 100644 index 000000000000..4296b0d37dd2 --- /dev/null +++ b/core/res/res/values-am/required_apps_managed_profile.xml @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string-array name="required_apps_managed_profile"> + <item msgid="1457364287544474838">"com.android.contacts"</item> + <item msgid="4633145750237794002">"com.android.settings"</item> + <item msgid="6518205098643077579">"com.android.providers.downloads"</item> + <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item> + <item msgid="6106837921940099371">"com.android.documentsui"</item> + </string-array> +</resources> diff --git a/core/res/res/values-am/required_apps_managed_user.xml b/core/res/res/values-am/required_apps_managed_user.xml new file mode 100644 index 000000000000..1a7ade9dd5f9 --- /dev/null +++ b/core/res/res/values-am/required_apps_managed_user.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string-array name="required_apps_managed_user"> + <item msgid="4823915868435007499">"com.android.settings"</item> + <item msgid="2250259015310893915">"com.android.contacts"</item> + <item msgid="7166574999426592423">"com.android.dialer"</item> + <item msgid="7306937186458176744">"com.android.stk"</item> + <item msgid="7415441588151512455">"com.android.providers.downloads"</item> + <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item> + <item msgid="8640522622655589373">"com.android.documentsui"</item> + </string-array> +</resources> diff --git a/core/res/res/values-ar/required_apps_managed_device.xml b/core/res/res/values-ar/required_apps_managed_device.xml new file mode 100644 index 000000000000..9044fccd88ee --- /dev/null +++ b/core/res/res/values-ar/required_apps_managed_device.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string-array name="required_apps_managed_device"> + <item msgid="1104492179978792509">"com.android.settings"</item> + <item msgid="7004798084799227194">"com.android.contacts"</item> + <item msgid="5782220690863647256">"com.android.dialer"</item> + <item msgid="5746338511138092673">"com.android.stk"</item> + <item msgid="1712599182168590664">"com.android.providers.downloads"</item> + <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item> + <item msgid="3892021562839042708">"com.android.documentsui"</item> + </string-array> +</resources> diff --git a/core/res/res/values-ar/required_apps_managed_profile.xml b/core/res/res/values-ar/required_apps_managed_profile.xml new file mode 100644 index 000000000000..4296b0d37dd2 --- /dev/null +++ b/core/res/res/values-ar/required_apps_managed_profile.xml @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string-array name="required_apps_managed_profile"> + <item msgid="1457364287544474838">"com.android.contacts"</item> + <item msgid="4633145750237794002">"com.android.settings"</item> + <item msgid="6518205098643077579">"com.android.providers.downloads"</item> + <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item> + <item msgid="6106837921940099371">"com.android.documentsui"</item> + </string-array> +</resources> diff --git a/core/res/res/values-ar/required_apps_managed_user.xml b/core/res/res/values-ar/required_apps_managed_user.xml new file mode 100644 index 000000000000..1a7ade9dd5f9 --- /dev/null +++ b/core/res/res/values-ar/required_apps_managed_user.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string-array name="required_apps_managed_user"> + <item msgid="4823915868435007499">"com.android.settings"</item> + <item msgid="2250259015310893915">"com.android.contacts"</item> + <item msgid="7166574999426592423">"com.android.dialer"</item> + <item msgid="7306937186458176744">"com.android.stk"</item> + <item msgid="7415441588151512455">"com.android.providers.downloads"</item> + <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item> + <item msgid="8640522622655589373">"com.android.documentsui"</item> + </string-array> +</resources> diff --git a/core/res/res/values-ar/strings.xml b/core/res/res/values-ar/strings.xml index c9611e49da62..2f69bd58b7b1 100644 --- a/core/res/res/values-ar/strings.xml +++ b/core/res/res/values-ar/strings.xml @@ -66,15 +66,15 @@ <string name="PwdMmi" msgid="7043715687905254199">"تغيير كلمة المرور"</string> <string name="PinMmi" msgid="3113117780361190304">"تغيير رمز PIN"</string> <string name="CnipMmi" msgid="3110534680557857162">"رقم الاتصال موجود"</string> - <string name="CnirMmi" msgid="3062102121430548731">"رقم الاتصال مقيّد"</string> + <string name="CnirMmi" msgid="3062102121430548731">"رقم الاتصال محظور"</string> <string name="ThreeWCMmi" msgid="9051047170321190368">"اتصال ثلاثي"</string> <string name="RuacMmi" msgid="7827887459138308886">"رفض المكالمات المزعجة غير المرغوب فيها"</string> <string name="CndMmi" msgid="3116446237081575808">"تسليم رقم الاتصال"</string> <string name="DndMmi" msgid="1265478932418334331">"عدم الإزعاج"</string> - <string name="CLIRDefaultOnNextCallOn" msgid="429415409145781923">"الإعداد الافتراضي لمعرف المتصل هو مقيّد. الاتصال التالي: مقيّد"</string> - <string name="CLIRDefaultOnNextCallOff" msgid="3092918006077864624">"الإعداد الافتراضي لمعرف المتصل هو مقيّد. الاتصال التالي: غير مقيّد"</string> - <string name="CLIRDefaultOffNextCallOn" msgid="6179425182856418465">"الإعداد الافتراضي لمعرف المتصل هو غير مقيّد. الاتصال التالي: مقيّد"</string> - <string name="CLIRDefaultOffNextCallOff" msgid="2567998633124408552">"الإعداد الافتراضي لمعرف المتصل هو غير مقيّد. الاتصال التالي: غير مقيّد"</string> + <string name="CLIRDefaultOnNextCallOn" msgid="429415409145781923">"الإعداد الافتراضي لمعرف المتصل هو محظور . الاتصال التالي: محظور"</string> + <string name="CLIRDefaultOnNextCallOff" msgid="3092918006077864624">"الإعداد الافتراضي لمعرف المتصل هو محظور . الاتصال التالي: غير محظور"</string> + <string name="CLIRDefaultOffNextCallOn" msgid="6179425182856418465">"الإعداد الافتراضي لمعرف المتصل هو غير محظور . الاتصال التالي: محظور"</string> + <string name="CLIRDefaultOffNextCallOff" msgid="2567998633124408552">"الإعداد الافتراضي لمعرف المتصل هو غير محظور . الاتصال التالي: غير محظور"</string> <string name="serviceNotProvisioned" msgid="8614830180508686666">"الخدمة غير متوفرة."</string> <string name="CLIRPermanent" msgid="3377371145926835671">"لا يمكنك تغيير إعداد معرّف المتصل."</string> <string name="RestrictedOnDataTitle" msgid="1322504692764166532">"ليست هناك خدمة بيانات"</string> diff --git a/core/res/res/values-b+sr+Latn/required_apps_managed_device.xml b/core/res/res/values-b+sr+Latn/required_apps_managed_device.xml new file mode 100644 index 000000000000..9044fccd88ee --- /dev/null +++ b/core/res/res/values-b+sr+Latn/required_apps_managed_device.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string-array name="required_apps_managed_device"> + <item msgid="1104492179978792509">"com.android.settings"</item> + <item msgid="7004798084799227194">"com.android.contacts"</item> + <item msgid="5782220690863647256">"com.android.dialer"</item> + <item msgid="5746338511138092673">"com.android.stk"</item> + <item msgid="1712599182168590664">"com.android.providers.downloads"</item> + <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item> + <item msgid="3892021562839042708">"com.android.documentsui"</item> + </string-array> +</resources> diff --git a/core/res/res/values-b+sr+Latn/required_apps_managed_profile.xml b/core/res/res/values-b+sr+Latn/required_apps_managed_profile.xml new file mode 100644 index 000000000000..4296b0d37dd2 --- /dev/null +++ b/core/res/res/values-b+sr+Latn/required_apps_managed_profile.xml @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string-array name="required_apps_managed_profile"> + <item msgid="1457364287544474838">"com.android.contacts"</item> + <item msgid="4633145750237794002">"com.android.settings"</item> + <item msgid="6518205098643077579">"com.android.providers.downloads"</item> + <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item> + <item msgid="6106837921940099371">"com.android.documentsui"</item> + </string-array> +</resources> diff --git a/core/res/res/values-b+sr+Latn/required_apps_managed_user.xml b/core/res/res/values-b+sr+Latn/required_apps_managed_user.xml new file mode 100644 index 000000000000..1a7ade9dd5f9 --- /dev/null +++ b/core/res/res/values-b+sr+Latn/required_apps_managed_user.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string-array name="required_apps_managed_user"> + <item msgid="4823915868435007499">"com.android.settings"</item> + <item msgid="2250259015310893915">"com.android.contacts"</item> + <item msgid="7166574999426592423">"com.android.dialer"</item> + <item msgid="7306937186458176744">"com.android.stk"</item> + <item msgid="7415441588151512455">"com.android.providers.downloads"</item> + <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item> + <item msgid="8640522622655589373">"com.android.documentsui"</item> + </string-array> +</resources> diff --git a/core/res/res/values-be/required_apps_managed_device.xml b/core/res/res/values-be/required_apps_managed_device.xml new file mode 100644 index 000000000000..9044fccd88ee --- /dev/null +++ b/core/res/res/values-be/required_apps_managed_device.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string-array name="required_apps_managed_device"> + <item msgid="1104492179978792509">"com.android.settings"</item> + <item msgid="7004798084799227194">"com.android.contacts"</item> + <item msgid="5782220690863647256">"com.android.dialer"</item> + <item msgid="5746338511138092673">"com.android.stk"</item> + <item msgid="1712599182168590664">"com.android.providers.downloads"</item> + <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item> + <item msgid="3892021562839042708">"com.android.documentsui"</item> + </string-array> +</resources> diff --git a/core/res/res/values-be/required_apps_managed_profile.xml b/core/res/res/values-be/required_apps_managed_profile.xml new file mode 100644 index 000000000000..4296b0d37dd2 --- /dev/null +++ b/core/res/res/values-be/required_apps_managed_profile.xml @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string-array name="required_apps_managed_profile"> + <item msgid="1457364287544474838">"com.android.contacts"</item> + <item msgid="4633145750237794002">"com.android.settings"</item> + <item msgid="6518205098643077579">"com.android.providers.downloads"</item> + <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item> + <item msgid="6106837921940099371">"com.android.documentsui"</item> + </string-array> +</resources> diff --git a/core/res/res/values-be/required_apps_managed_user.xml b/core/res/res/values-be/required_apps_managed_user.xml new file mode 100644 index 000000000000..1a7ade9dd5f9 --- /dev/null +++ b/core/res/res/values-be/required_apps_managed_user.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string-array name="required_apps_managed_user"> + <item msgid="4823915868435007499">"com.android.settings"</item> + <item msgid="2250259015310893915">"com.android.contacts"</item> + <item msgid="7166574999426592423">"com.android.dialer"</item> + <item msgid="7306937186458176744">"com.android.stk"</item> + <item msgid="7415441588151512455">"com.android.providers.downloads"</item> + <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item> + <item msgid="8640522622655589373">"com.android.documentsui"</item> + </string-array> +</resources> diff --git a/core/res/res/values-bg/required_apps_managed_device.xml b/core/res/res/values-bg/required_apps_managed_device.xml new file mode 100644 index 000000000000..9044fccd88ee --- /dev/null +++ b/core/res/res/values-bg/required_apps_managed_device.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string-array name="required_apps_managed_device"> + <item msgid="1104492179978792509">"com.android.settings"</item> + <item msgid="7004798084799227194">"com.android.contacts"</item> + <item msgid="5782220690863647256">"com.android.dialer"</item> + <item msgid="5746338511138092673">"com.android.stk"</item> + <item msgid="1712599182168590664">"com.android.providers.downloads"</item> + <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item> + <item msgid="3892021562839042708">"com.android.documentsui"</item> + </string-array> +</resources> diff --git a/core/res/res/values-bg/required_apps_managed_profile.xml b/core/res/res/values-bg/required_apps_managed_profile.xml new file mode 100644 index 000000000000..4296b0d37dd2 --- /dev/null +++ b/core/res/res/values-bg/required_apps_managed_profile.xml @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string-array name="required_apps_managed_profile"> + <item msgid="1457364287544474838">"com.android.contacts"</item> + <item msgid="4633145750237794002">"com.android.settings"</item> + <item msgid="6518205098643077579">"com.android.providers.downloads"</item> + <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item> + <item msgid="6106837921940099371">"com.android.documentsui"</item> + </string-array> +</resources> diff --git a/core/res/res/values-bg/required_apps_managed_user.xml b/core/res/res/values-bg/required_apps_managed_user.xml new file mode 100644 index 000000000000..1a7ade9dd5f9 --- /dev/null +++ b/core/res/res/values-bg/required_apps_managed_user.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string-array name="required_apps_managed_user"> + <item msgid="4823915868435007499">"com.android.settings"</item> + <item msgid="2250259015310893915">"com.android.contacts"</item> + <item msgid="7166574999426592423">"com.android.dialer"</item> + <item msgid="7306937186458176744">"com.android.stk"</item> + <item msgid="7415441588151512455">"com.android.providers.downloads"</item> + <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item> + <item msgid="8640522622655589373">"com.android.documentsui"</item> + </string-array> +</resources> diff --git a/core/res/res/values-bn/required_apps_managed_device.xml b/core/res/res/values-bn/required_apps_managed_device.xml new file mode 100644 index 000000000000..9044fccd88ee --- /dev/null +++ b/core/res/res/values-bn/required_apps_managed_device.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string-array name="required_apps_managed_device"> + <item msgid="1104492179978792509">"com.android.settings"</item> + <item msgid="7004798084799227194">"com.android.contacts"</item> + <item msgid="5782220690863647256">"com.android.dialer"</item> + <item msgid="5746338511138092673">"com.android.stk"</item> + <item msgid="1712599182168590664">"com.android.providers.downloads"</item> + <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item> + <item msgid="3892021562839042708">"com.android.documentsui"</item> + </string-array> +</resources> diff --git a/core/res/res/values-bn/required_apps_managed_profile.xml b/core/res/res/values-bn/required_apps_managed_profile.xml new file mode 100644 index 000000000000..4296b0d37dd2 --- /dev/null +++ b/core/res/res/values-bn/required_apps_managed_profile.xml @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string-array name="required_apps_managed_profile"> + <item msgid="1457364287544474838">"com.android.contacts"</item> + <item msgid="4633145750237794002">"com.android.settings"</item> + <item msgid="6518205098643077579">"com.android.providers.downloads"</item> + <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item> + <item msgid="6106837921940099371">"com.android.documentsui"</item> + </string-array> +</resources> diff --git a/core/res/res/values-bn/required_apps_managed_user.xml b/core/res/res/values-bn/required_apps_managed_user.xml new file mode 100644 index 000000000000..1a7ade9dd5f9 --- /dev/null +++ b/core/res/res/values-bn/required_apps_managed_user.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string-array name="required_apps_managed_user"> + <item msgid="4823915868435007499">"com.android.settings"</item> + <item msgid="2250259015310893915">"com.android.contacts"</item> + <item msgid="7166574999426592423">"com.android.dialer"</item> + <item msgid="7306937186458176744">"com.android.stk"</item> + <item msgid="7415441588151512455">"com.android.providers.downloads"</item> + <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item> + <item msgid="8640522622655589373">"com.android.documentsui"</item> + </string-array> +</resources> diff --git a/core/res/res/values-bn/strings.xml b/core/res/res/values-bn/strings.xml index 073fa1218039..fa3c9b1dab6d 100644 --- a/core/res/res/values-bn/strings.xml +++ b/core/res/res/values-bn/strings.xml @@ -1695,11 +1695,9 @@ <string name="stk_cc_ussd_to_dial" msgid="5202342984749947872">"USSD অনুরোধটিকে ডায়াল অনুরোধে রুপান্তরিত করা হয়েছে৷"</string> <string name="stk_cc_ussd_to_ss" msgid="2345360594181405482">"USSD অনুরোধটিকে SS অনুরোধে রুপান্তরিত করা হয়েছে৷"</string> <string name="stk_cc_ussd_to_ussd" msgid="7466087659967191653">"USSD অনুরোধটিকে নতুন USSD অনুরোধে রুপান্তরিত করা হয়েছে৷"</string> - <!-- no translation found for stk_cc_ussd_to_dial_video (585340552561515305) --> - <skip /> + <string name="stk_cc_ussd_to_dial_video" msgid="585340552561515305">"USSD অনুরোধটিকে ভিডিও DIAL অনুরোধে পরিবর্তন করা হয়েছে।"</string> <string name="stk_cc_ss_to_dial" msgid="2151304435775557162">"SS অনুরোধটিকে ডায়াল অনুরোধে রুপান্তরিত করা হয়েছে৷"</string> - <!-- no translation found for stk_cc_ss_to_dial_video (4306210904450719045) --> - <skip /> + <string name="stk_cc_ss_to_dial_video" msgid="4306210904450719045">"SS অনুরোধটিকে ভিডিও DIAL অনুরোধে পরিবর্তন করা হয়েছে।"</string> <string name="stk_cc_ss_to_ussd" msgid="3951862188105305589">"SS অনুরোধটিকে নতুন USSD অনুরোধে রুপান্তরিত করা হয়েছে৷"</string> <string name="stk_cc_ss_to_ss" msgid="5470768854991452695">"SS অনুরোধটিকে নতুন SS অনুরোধে রুপান্তরিত করা হয়েছে৷"</string> <string name="notification_work_profile_content_description" msgid="4600554564103770764">"কর্মস্থলের প্রোফাইল"</string> diff --git a/core/res/res/values-ca/required_apps_managed_device.xml b/core/res/res/values-ca/required_apps_managed_device.xml new file mode 100644 index 000000000000..9044fccd88ee --- /dev/null +++ b/core/res/res/values-ca/required_apps_managed_device.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string-array name="required_apps_managed_device"> + <item msgid="1104492179978792509">"com.android.settings"</item> + <item msgid="7004798084799227194">"com.android.contacts"</item> + <item msgid="5782220690863647256">"com.android.dialer"</item> + <item msgid="5746338511138092673">"com.android.stk"</item> + <item msgid="1712599182168590664">"com.android.providers.downloads"</item> + <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item> + <item msgid="3892021562839042708">"com.android.documentsui"</item> + </string-array> +</resources> diff --git a/core/res/res/values-ca/required_apps_managed_profile.xml b/core/res/res/values-ca/required_apps_managed_profile.xml new file mode 100644 index 000000000000..4296b0d37dd2 --- /dev/null +++ b/core/res/res/values-ca/required_apps_managed_profile.xml @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string-array name="required_apps_managed_profile"> + <item msgid="1457364287544474838">"com.android.contacts"</item> + <item msgid="4633145750237794002">"com.android.settings"</item> + <item msgid="6518205098643077579">"com.android.providers.downloads"</item> + <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item> + <item msgid="6106837921940099371">"com.android.documentsui"</item> + </string-array> +</resources> diff --git a/core/res/res/values-ca/required_apps_managed_user.xml b/core/res/res/values-ca/required_apps_managed_user.xml new file mode 100644 index 000000000000..1a7ade9dd5f9 --- /dev/null +++ b/core/res/res/values-ca/required_apps_managed_user.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string-array name="required_apps_managed_user"> + <item msgid="4823915868435007499">"com.android.settings"</item> + <item msgid="2250259015310893915">"com.android.contacts"</item> + <item msgid="7166574999426592423">"com.android.dialer"</item> + <item msgid="7306937186458176744">"com.android.stk"</item> + <item msgid="7415441588151512455">"com.android.providers.downloads"</item> + <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item> + <item msgid="8640522622655589373">"com.android.documentsui"</item> + </string-array> +</resources> diff --git a/core/res/res/values-cs/required_apps_managed_device.xml b/core/res/res/values-cs/required_apps_managed_device.xml new file mode 100644 index 000000000000..9044fccd88ee --- /dev/null +++ b/core/res/res/values-cs/required_apps_managed_device.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string-array name="required_apps_managed_device"> + <item msgid="1104492179978792509">"com.android.settings"</item> + <item msgid="7004798084799227194">"com.android.contacts"</item> + <item msgid="5782220690863647256">"com.android.dialer"</item> + <item msgid="5746338511138092673">"com.android.stk"</item> + <item msgid="1712599182168590664">"com.android.providers.downloads"</item> + <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item> + <item msgid="3892021562839042708">"com.android.documentsui"</item> + </string-array> +</resources> diff --git a/core/res/res/values-cs/required_apps_managed_profile.xml b/core/res/res/values-cs/required_apps_managed_profile.xml new file mode 100644 index 000000000000..4296b0d37dd2 --- /dev/null +++ b/core/res/res/values-cs/required_apps_managed_profile.xml @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string-array name="required_apps_managed_profile"> + <item msgid="1457364287544474838">"com.android.contacts"</item> + <item msgid="4633145750237794002">"com.android.settings"</item> + <item msgid="6518205098643077579">"com.android.providers.downloads"</item> + <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item> + <item msgid="6106837921940099371">"com.android.documentsui"</item> + </string-array> +</resources> diff --git a/core/res/res/values-cs/required_apps_managed_user.xml b/core/res/res/values-cs/required_apps_managed_user.xml new file mode 100644 index 000000000000..1a7ade9dd5f9 --- /dev/null +++ b/core/res/res/values-cs/required_apps_managed_user.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string-array name="required_apps_managed_user"> + <item msgid="4823915868435007499">"com.android.settings"</item> + <item msgid="2250259015310893915">"com.android.contacts"</item> + <item msgid="7166574999426592423">"com.android.dialer"</item> + <item msgid="7306937186458176744">"com.android.stk"</item> + <item msgid="7415441588151512455">"com.android.providers.downloads"</item> + <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item> + <item msgid="8640522622655589373">"com.android.documentsui"</item> + </string-array> +</resources> diff --git a/core/res/res/values-da/required_apps_managed_device.xml b/core/res/res/values-da/required_apps_managed_device.xml new file mode 100644 index 000000000000..9044fccd88ee --- /dev/null +++ b/core/res/res/values-da/required_apps_managed_device.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string-array name="required_apps_managed_device"> + <item msgid="1104492179978792509">"com.android.settings"</item> + <item msgid="7004798084799227194">"com.android.contacts"</item> + <item msgid="5782220690863647256">"com.android.dialer"</item> + <item msgid="5746338511138092673">"com.android.stk"</item> + <item msgid="1712599182168590664">"com.android.providers.downloads"</item> + <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item> + <item msgid="3892021562839042708">"com.android.documentsui"</item> + </string-array> +</resources> diff --git a/core/res/res/values-da/required_apps_managed_profile.xml b/core/res/res/values-da/required_apps_managed_profile.xml new file mode 100644 index 000000000000..4296b0d37dd2 --- /dev/null +++ b/core/res/res/values-da/required_apps_managed_profile.xml @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string-array name="required_apps_managed_profile"> + <item msgid="1457364287544474838">"com.android.contacts"</item> + <item msgid="4633145750237794002">"com.android.settings"</item> + <item msgid="6518205098643077579">"com.android.providers.downloads"</item> + <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item> + <item msgid="6106837921940099371">"com.android.documentsui"</item> + </string-array> +</resources> diff --git a/core/res/res/values-da/required_apps_managed_user.xml b/core/res/res/values-da/required_apps_managed_user.xml new file mode 100644 index 000000000000..1a7ade9dd5f9 --- /dev/null +++ b/core/res/res/values-da/required_apps_managed_user.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string-array name="required_apps_managed_user"> + <item msgid="4823915868435007499">"com.android.settings"</item> + <item msgid="2250259015310893915">"com.android.contacts"</item> + <item msgid="7166574999426592423">"com.android.dialer"</item> + <item msgid="7306937186458176744">"com.android.stk"</item> + <item msgid="7415441588151512455">"com.android.providers.downloads"</item> + <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item> + <item msgid="8640522622655589373">"com.android.documentsui"</item> + </string-array> +</resources> diff --git a/core/res/res/values-de/required_apps_managed_device.xml b/core/res/res/values-de/required_apps_managed_device.xml new file mode 100644 index 000000000000..9044fccd88ee --- /dev/null +++ b/core/res/res/values-de/required_apps_managed_device.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string-array name="required_apps_managed_device"> + <item msgid="1104492179978792509">"com.android.settings"</item> + <item msgid="7004798084799227194">"com.android.contacts"</item> + <item msgid="5782220690863647256">"com.android.dialer"</item> + <item msgid="5746338511138092673">"com.android.stk"</item> + <item msgid="1712599182168590664">"com.android.providers.downloads"</item> + <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item> + <item msgid="3892021562839042708">"com.android.documentsui"</item> + </string-array> +</resources> diff --git a/core/res/res/values-de/required_apps_managed_profile.xml b/core/res/res/values-de/required_apps_managed_profile.xml new file mode 100644 index 000000000000..4296b0d37dd2 --- /dev/null +++ b/core/res/res/values-de/required_apps_managed_profile.xml @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string-array name="required_apps_managed_profile"> + <item msgid="1457364287544474838">"com.android.contacts"</item> + <item msgid="4633145750237794002">"com.android.settings"</item> + <item msgid="6518205098643077579">"com.android.providers.downloads"</item> + <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item> + <item msgid="6106837921940099371">"com.android.documentsui"</item> + </string-array> +</resources> diff --git a/core/res/res/values-de/required_apps_managed_user.xml b/core/res/res/values-de/required_apps_managed_user.xml new file mode 100644 index 000000000000..1a7ade9dd5f9 --- /dev/null +++ b/core/res/res/values-de/required_apps_managed_user.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string-array name="required_apps_managed_user"> + <item msgid="4823915868435007499">"com.android.settings"</item> + <item msgid="2250259015310893915">"com.android.contacts"</item> + <item msgid="7166574999426592423">"com.android.dialer"</item> + <item msgid="7306937186458176744">"com.android.stk"</item> + <item msgid="7415441588151512455">"com.android.providers.downloads"</item> + <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item> + <item msgid="8640522622655589373">"com.android.documentsui"</item> + </string-array> +</resources> diff --git a/core/res/res/values-el/required_apps_managed_device.xml b/core/res/res/values-el/required_apps_managed_device.xml new file mode 100644 index 000000000000..9044fccd88ee --- /dev/null +++ b/core/res/res/values-el/required_apps_managed_device.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string-array name="required_apps_managed_device"> + <item msgid="1104492179978792509">"com.android.settings"</item> + <item msgid="7004798084799227194">"com.android.contacts"</item> + <item msgid="5782220690863647256">"com.android.dialer"</item> + <item msgid="5746338511138092673">"com.android.stk"</item> + <item msgid="1712599182168590664">"com.android.providers.downloads"</item> + <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item> + <item msgid="3892021562839042708">"com.android.documentsui"</item> + </string-array> +</resources> diff --git a/core/res/res/values-el/required_apps_managed_profile.xml b/core/res/res/values-el/required_apps_managed_profile.xml new file mode 100644 index 000000000000..4296b0d37dd2 --- /dev/null +++ b/core/res/res/values-el/required_apps_managed_profile.xml @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string-array name="required_apps_managed_profile"> + <item msgid="1457364287544474838">"com.android.contacts"</item> + <item msgid="4633145750237794002">"com.android.settings"</item> + <item msgid="6518205098643077579">"com.android.providers.downloads"</item> + <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item> + <item msgid="6106837921940099371">"com.android.documentsui"</item> + </string-array> +</resources> diff --git a/core/res/res/values-el/required_apps_managed_user.xml b/core/res/res/values-el/required_apps_managed_user.xml new file mode 100644 index 000000000000..1a7ade9dd5f9 --- /dev/null +++ b/core/res/res/values-el/required_apps_managed_user.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string-array name="required_apps_managed_user"> + <item msgid="4823915868435007499">"com.android.settings"</item> + <item msgid="2250259015310893915">"com.android.contacts"</item> + <item msgid="7166574999426592423">"com.android.dialer"</item> + <item msgid="7306937186458176744">"com.android.stk"</item> + <item msgid="7415441588151512455">"com.android.providers.downloads"</item> + <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item> + <item msgid="8640522622655589373">"com.android.documentsui"</item> + </string-array> +</resources> diff --git a/core/res/res/values-es-rUS/required_apps_managed_device.xml b/core/res/res/values-es-rUS/required_apps_managed_device.xml new file mode 100644 index 000000000000..9044fccd88ee --- /dev/null +++ b/core/res/res/values-es-rUS/required_apps_managed_device.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string-array name="required_apps_managed_device"> + <item msgid="1104492179978792509">"com.android.settings"</item> + <item msgid="7004798084799227194">"com.android.contacts"</item> + <item msgid="5782220690863647256">"com.android.dialer"</item> + <item msgid="5746338511138092673">"com.android.stk"</item> + <item msgid="1712599182168590664">"com.android.providers.downloads"</item> + <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item> + <item msgid="3892021562839042708">"com.android.documentsui"</item> + </string-array> +</resources> diff --git a/core/res/res/values-es-rUS/required_apps_managed_profile.xml b/core/res/res/values-es-rUS/required_apps_managed_profile.xml new file mode 100644 index 000000000000..4296b0d37dd2 --- /dev/null +++ b/core/res/res/values-es-rUS/required_apps_managed_profile.xml @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string-array name="required_apps_managed_profile"> + <item msgid="1457364287544474838">"com.android.contacts"</item> + <item msgid="4633145750237794002">"com.android.settings"</item> + <item msgid="6518205098643077579">"com.android.providers.downloads"</item> + <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item> + <item msgid="6106837921940099371">"com.android.documentsui"</item> + </string-array> +</resources> diff --git a/core/res/res/values-es-rUS/required_apps_managed_user.xml b/core/res/res/values-es-rUS/required_apps_managed_user.xml new file mode 100644 index 000000000000..1a7ade9dd5f9 --- /dev/null +++ b/core/res/res/values-es-rUS/required_apps_managed_user.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string-array name="required_apps_managed_user"> + <item msgid="4823915868435007499">"com.android.settings"</item> + <item msgid="2250259015310893915">"com.android.contacts"</item> + <item msgid="7166574999426592423">"com.android.dialer"</item> + <item msgid="7306937186458176744">"com.android.stk"</item> + <item msgid="7415441588151512455">"com.android.providers.downloads"</item> + <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item> + <item msgid="8640522622655589373">"com.android.documentsui"</item> + </string-array> +</resources> diff --git a/core/res/res/values-et/required_apps_managed_device.xml b/core/res/res/values-et/required_apps_managed_device.xml new file mode 100644 index 000000000000..9044fccd88ee --- /dev/null +++ b/core/res/res/values-et/required_apps_managed_device.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string-array name="required_apps_managed_device"> + <item msgid="1104492179978792509">"com.android.settings"</item> + <item msgid="7004798084799227194">"com.android.contacts"</item> + <item msgid="5782220690863647256">"com.android.dialer"</item> + <item msgid="5746338511138092673">"com.android.stk"</item> + <item msgid="1712599182168590664">"com.android.providers.downloads"</item> + <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item> + <item msgid="3892021562839042708">"com.android.documentsui"</item> + </string-array> +</resources> diff --git a/core/res/res/values-et/required_apps_managed_profile.xml b/core/res/res/values-et/required_apps_managed_profile.xml new file mode 100644 index 000000000000..4296b0d37dd2 --- /dev/null +++ b/core/res/res/values-et/required_apps_managed_profile.xml @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string-array name="required_apps_managed_profile"> + <item msgid="1457364287544474838">"com.android.contacts"</item> + <item msgid="4633145750237794002">"com.android.settings"</item> + <item msgid="6518205098643077579">"com.android.providers.downloads"</item> + <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item> + <item msgid="6106837921940099371">"com.android.documentsui"</item> + </string-array> +</resources> diff --git a/core/res/res/values-et/required_apps_managed_user.xml b/core/res/res/values-et/required_apps_managed_user.xml new file mode 100644 index 000000000000..1a7ade9dd5f9 --- /dev/null +++ b/core/res/res/values-et/required_apps_managed_user.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string-array name="required_apps_managed_user"> + <item msgid="4823915868435007499">"com.android.settings"</item> + <item msgid="2250259015310893915">"com.android.contacts"</item> + <item msgid="7166574999426592423">"com.android.dialer"</item> + <item msgid="7306937186458176744">"com.android.stk"</item> + <item msgid="7415441588151512455">"com.android.providers.downloads"</item> + <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item> + <item msgid="8640522622655589373">"com.android.documentsui"</item> + </string-array> +</resources> diff --git a/core/res/res/values-eu/required_apps_managed_device.xml b/core/res/res/values-eu/required_apps_managed_device.xml new file mode 100644 index 000000000000..9044fccd88ee --- /dev/null +++ b/core/res/res/values-eu/required_apps_managed_device.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string-array name="required_apps_managed_device"> + <item msgid="1104492179978792509">"com.android.settings"</item> + <item msgid="7004798084799227194">"com.android.contacts"</item> + <item msgid="5782220690863647256">"com.android.dialer"</item> + <item msgid="5746338511138092673">"com.android.stk"</item> + <item msgid="1712599182168590664">"com.android.providers.downloads"</item> + <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item> + <item msgid="3892021562839042708">"com.android.documentsui"</item> + </string-array> +</resources> diff --git a/core/res/res/values-eu/required_apps_managed_profile.xml b/core/res/res/values-eu/required_apps_managed_profile.xml new file mode 100644 index 000000000000..4296b0d37dd2 --- /dev/null +++ b/core/res/res/values-eu/required_apps_managed_profile.xml @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string-array name="required_apps_managed_profile"> + <item msgid="1457364287544474838">"com.android.contacts"</item> + <item msgid="4633145750237794002">"com.android.settings"</item> + <item msgid="6518205098643077579">"com.android.providers.downloads"</item> + <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item> + <item msgid="6106837921940099371">"com.android.documentsui"</item> + </string-array> +</resources> diff --git a/core/res/res/values-eu/required_apps_managed_user.xml b/core/res/res/values-eu/required_apps_managed_user.xml new file mode 100644 index 000000000000..1a7ade9dd5f9 --- /dev/null +++ b/core/res/res/values-eu/required_apps_managed_user.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string-array name="required_apps_managed_user"> + <item msgid="4823915868435007499">"com.android.settings"</item> + <item msgid="2250259015310893915">"com.android.contacts"</item> + <item msgid="7166574999426592423">"com.android.dialer"</item> + <item msgid="7306937186458176744">"com.android.stk"</item> + <item msgid="7415441588151512455">"com.android.providers.downloads"</item> + <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item> + <item msgid="8640522622655589373">"com.android.documentsui"</item> + </string-array> +</resources> diff --git a/core/res/res/values-fa/required_apps_managed_device.xml b/core/res/res/values-fa/required_apps_managed_device.xml new file mode 100644 index 000000000000..9044fccd88ee --- /dev/null +++ b/core/res/res/values-fa/required_apps_managed_device.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string-array name="required_apps_managed_device"> + <item msgid="1104492179978792509">"com.android.settings"</item> + <item msgid="7004798084799227194">"com.android.contacts"</item> + <item msgid="5782220690863647256">"com.android.dialer"</item> + <item msgid="5746338511138092673">"com.android.stk"</item> + <item msgid="1712599182168590664">"com.android.providers.downloads"</item> + <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item> + <item msgid="3892021562839042708">"com.android.documentsui"</item> + </string-array> +</resources> diff --git a/core/res/res/values-fa/required_apps_managed_profile.xml b/core/res/res/values-fa/required_apps_managed_profile.xml new file mode 100644 index 000000000000..4296b0d37dd2 --- /dev/null +++ b/core/res/res/values-fa/required_apps_managed_profile.xml @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string-array name="required_apps_managed_profile"> + <item msgid="1457364287544474838">"com.android.contacts"</item> + <item msgid="4633145750237794002">"com.android.settings"</item> + <item msgid="6518205098643077579">"com.android.providers.downloads"</item> + <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item> + <item msgid="6106837921940099371">"com.android.documentsui"</item> + </string-array> +</resources> diff --git a/core/res/res/values-fa/required_apps_managed_user.xml b/core/res/res/values-fa/required_apps_managed_user.xml new file mode 100644 index 000000000000..1a7ade9dd5f9 --- /dev/null +++ b/core/res/res/values-fa/required_apps_managed_user.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string-array name="required_apps_managed_user"> + <item msgid="4823915868435007499">"com.android.settings"</item> + <item msgid="2250259015310893915">"com.android.contacts"</item> + <item msgid="7166574999426592423">"com.android.dialer"</item> + <item msgid="7306937186458176744">"com.android.stk"</item> + <item msgid="7415441588151512455">"com.android.providers.downloads"</item> + <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item> + <item msgid="8640522622655589373">"com.android.documentsui"</item> + </string-array> +</resources> diff --git a/core/res/res/values-fi/required_apps_managed_device.xml b/core/res/res/values-fi/required_apps_managed_device.xml new file mode 100644 index 000000000000..9044fccd88ee --- /dev/null +++ b/core/res/res/values-fi/required_apps_managed_device.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string-array name="required_apps_managed_device"> + <item msgid="1104492179978792509">"com.android.settings"</item> + <item msgid="7004798084799227194">"com.android.contacts"</item> + <item msgid="5782220690863647256">"com.android.dialer"</item> + <item msgid="5746338511138092673">"com.android.stk"</item> + <item msgid="1712599182168590664">"com.android.providers.downloads"</item> + <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item> + <item msgid="3892021562839042708">"com.android.documentsui"</item> + </string-array> +</resources> diff --git a/core/res/res/values-fi/required_apps_managed_profile.xml b/core/res/res/values-fi/required_apps_managed_profile.xml new file mode 100644 index 000000000000..4296b0d37dd2 --- /dev/null +++ b/core/res/res/values-fi/required_apps_managed_profile.xml @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string-array name="required_apps_managed_profile"> + <item msgid="1457364287544474838">"com.android.contacts"</item> + <item msgid="4633145750237794002">"com.android.settings"</item> + <item msgid="6518205098643077579">"com.android.providers.downloads"</item> + <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item> + <item msgid="6106837921940099371">"com.android.documentsui"</item> + </string-array> +</resources> diff --git a/core/res/res/values-fi/required_apps_managed_user.xml b/core/res/res/values-fi/required_apps_managed_user.xml new file mode 100644 index 000000000000..1a7ade9dd5f9 --- /dev/null +++ b/core/res/res/values-fi/required_apps_managed_user.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string-array name="required_apps_managed_user"> + <item msgid="4823915868435007499">"com.android.settings"</item> + <item msgid="2250259015310893915">"com.android.contacts"</item> + <item msgid="7166574999426592423">"com.android.dialer"</item> + <item msgid="7306937186458176744">"com.android.stk"</item> + <item msgid="7415441588151512455">"com.android.providers.downloads"</item> + <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item> + <item msgid="8640522622655589373">"com.android.documentsui"</item> + </string-array> +</resources> diff --git a/core/res/res/values-fr-rCA/required_apps_managed_device.xml b/core/res/res/values-fr-rCA/required_apps_managed_device.xml new file mode 100644 index 000000000000..9044fccd88ee --- /dev/null +++ b/core/res/res/values-fr-rCA/required_apps_managed_device.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string-array name="required_apps_managed_device"> + <item msgid="1104492179978792509">"com.android.settings"</item> + <item msgid="7004798084799227194">"com.android.contacts"</item> + <item msgid="5782220690863647256">"com.android.dialer"</item> + <item msgid="5746338511138092673">"com.android.stk"</item> + <item msgid="1712599182168590664">"com.android.providers.downloads"</item> + <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item> + <item msgid="3892021562839042708">"com.android.documentsui"</item> + </string-array> +</resources> diff --git a/core/res/res/values-fr-rCA/required_apps_managed_profile.xml b/core/res/res/values-fr-rCA/required_apps_managed_profile.xml new file mode 100644 index 000000000000..4296b0d37dd2 --- /dev/null +++ b/core/res/res/values-fr-rCA/required_apps_managed_profile.xml @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string-array name="required_apps_managed_profile"> + <item msgid="1457364287544474838">"com.android.contacts"</item> + <item msgid="4633145750237794002">"com.android.settings"</item> + <item msgid="6518205098643077579">"com.android.providers.downloads"</item> + <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item> + <item msgid="6106837921940099371">"com.android.documentsui"</item> + </string-array> +</resources> diff --git a/core/res/res/values-fr-rCA/required_apps_managed_user.xml b/core/res/res/values-fr-rCA/required_apps_managed_user.xml new file mode 100644 index 000000000000..1a7ade9dd5f9 --- /dev/null +++ b/core/res/res/values-fr-rCA/required_apps_managed_user.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string-array name="required_apps_managed_user"> + <item msgid="4823915868435007499">"com.android.settings"</item> + <item msgid="2250259015310893915">"com.android.contacts"</item> + <item msgid="7166574999426592423">"com.android.dialer"</item> + <item msgid="7306937186458176744">"com.android.stk"</item> + <item msgid="7415441588151512455">"com.android.providers.downloads"</item> + <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item> + <item msgid="8640522622655589373">"com.android.documentsui"</item> + </string-array> +</resources> diff --git a/core/res/res/values-gl/required_apps_managed_device.xml b/core/res/res/values-gl/required_apps_managed_device.xml new file mode 100644 index 000000000000..9044fccd88ee --- /dev/null +++ b/core/res/res/values-gl/required_apps_managed_device.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string-array name="required_apps_managed_device"> + <item msgid="1104492179978792509">"com.android.settings"</item> + <item msgid="7004798084799227194">"com.android.contacts"</item> + <item msgid="5782220690863647256">"com.android.dialer"</item> + <item msgid="5746338511138092673">"com.android.stk"</item> + <item msgid="1712599182168590664">"com.android.providers.downloads"</item> + <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item> + <item msgid="3892021562839042708">"com.android.documentsui"</item> + </string-array> +</resources> diff --git a/core/res/res/values-gl/required_apps_managed_profile.xml b/core/res/res/values-gl/required_apps_managed_profile.xml new file mode 100644 index 000000000000..4296b0d37dd2 --- /dev/null +++ b/core/res/res/values-gl/required_apps_managed_profile.xml @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string-array name="required_apps_managed_profile"> + <item msgid="1457364287544474838">"com.android.contacts"</item> + <item msgid="4633145750237794002">"com.android.settings"</item> + <item msgid="6518205098643077579">"com.android.providers.downloads"</item> + <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item> + <item msgid="6106837921940099371">"com.android.documentsui"</item> + </string-array> +</resources> diff --git a/core/res/res/values-gl/required_apps_managed_user.xml b/core/res/res/values-gl/required_apps_managed_user.xml new file mode 100644 index 000000000000..1a7ade9dd5f9 --- /dev/null +++ b/core/res/res/values-gl/required_apps_managed_user.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string-array name="required_apps_managed_user"> + <item msgid="4823915868435007499">"com.android.settings"</item> + <item msgid="2250259015310893915">"com.android.contacts"</item> + <item msgid="7166574999426592423">"com.android.dialer"</item> + <item msgid="7306937186458176744">"com.android.stk"</item> + <item msgid="7415441588151512455">"com.android.providers.downloads"</item> + <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item> + <item msgid="8640522622655589373">"com.android.documentsui"</item> + </string-array> +</resources> diff --git a/core/res/res/values-gl/strings.xml b/core/res/res/values-gl/strings.xml index b5aab394d323..b4e390473628 100644 --- a/core/res/res/values-gl/strings.xml +++ b/core/res/res/values-gl/strings.xml @@ -977,7 +977,7 @@ <string name="textSelectionCABTitle" msgid="5236850394370820357">"Selección de texto"</string> <string name="addToDictionary" msgid="4352161534510057874">"Engadir ao dicionario"</string> <string name="deleteText" msgid="6979668428458199034">"Eliminar"</string> - <string name="inputMethod" msgid="1653630062304567879">"Método de entrada"</string> + <string name="inputMethod" msgid="1653630062304567879">"Método de introdución de texto"</string> <string name="editTextMenuTitle" msgid="4909135564941815494">"Accións de texto"</string> <string name="email" msgid="4560673117055050403">"Correo electrónico"</string> <string name="dial" msgid="1253998302767701559">"Chamar"</string> @@ -1288,7 +1288,7 @@ <string name="permission_request_notification_with_subtitle" msgid="8530393139639560189">"Permiso solicitado\npara a conta <xliff:g id="ACCOUNT">%s</xliff:g>."</string> <string name="forward_intent_to_owner" msgid="1207197447013960896">"Estás usando esta aplicación fóra do teu perfil de traballo"</string> <string name="forward_intent_to_work" msgid="621480743856004612">"Estás usando esta aplicación no teu perfil de traballo"</string> - <string name="input_method_binding_label" msgid="1283557179944992649">"Método de entrada"</string> + <string name="input_method_binding_label" msgid="1283557179944992649">"Método de introdución de texto"</string> <string name="sync_binding_label" msgid="3687969138375092423">"Sincronizar"</string> <string name="accessibility_binding_label" msgid="4148120742096474641">"Accesibilidade"</string> <string name="wallpaper_binding_label" msgid="1240087844304687662">"Fondo de pantalla"</string> diff --git a/core/res/res/values-gu/required_apps_managed_device.xml b/core/res/res/values-gu/required_apps_managed_device.xml new file mode 100644 index 000000000000..9044fccd88ee --- /dev/null +++ b/core/res/res/values-gu/required_apps_managed_device.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string-array name="required_apps_managed_device"> + <item msgid="1104492179978792509">"com.android.settings"</item> + <item msgid="7004798084799227194">"com.android.contacts"</item> + <item msgid="5782220690863647256">"com.android.dialer"</item> + <item msgid="5746338511138092673">"com.android.stk"</item> + <item msgid="1712599182168590664">"com.android.providers.downloads"</item> + <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item> + <item msgid="3892021562839042708">"com.android.documentsui"</item> + </string-array> +</resources> diff --git a/core/res/res/values-gu/required_apps_managed_profile.xml b/core/res/res/values-gu/required_apps_managed_profile.xml new file mode 100644 index 000000000000..4296b0d37dd2 --- /dev/null +++ b/core/res/res/values-gu/required_apps_managed_profile.xml @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string-array name="required_apps_managed_profile"> + <item msgid="1457364287544474838">"com.android.contacts"</item> + <item msgid="4633145750237794002">"com.android.settings"</item> + <item msgid="6518205098643077579">"com.android.providers.downloads"</item> + <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item> + <item msgid="6106837921940099371">"com.android.documentsui"</item> + </string-array> +</resources> diff --git a/core/res/res/values-gu/required_apps_managed_user.xml b/core/res/res/values-gu/required_apps_managed_user.xml new file mode 100644 index 000000000000..1a7ade9dd5f9 --- /dev/null +++ b/core/res/res/values-gu/required_apps_managed_user.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string-array name="required_apps_managed_user"> + <item msgid="4823915868435007499">"com.android.settings"</item> + <item msgid="2250259015310893915">"com.android.contacts"</item> + <item msgid="7166574999426592423">"com.android.dialer"</item> + <item msgid="7306937186458176744">"com.android.stk"</item> + <item msgid="7415441588151512455">"com.android.providers.downloads"</item> + <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item> + <item msgid="8640522622655589373">"com.android.documentsui"</item> + </string-array> +</resources> diff --git a/core/res/res/values-gu/strings.xml b/core/res/res/values-gu/strings.xml index cff1a8500f21..eada63324240 100644 --- a/core/res/res/values-gu/strings.xml +++ b/core/res/res/values-gu/strings.xml @@ -1695,11 +1695,9 @@ <string name="stk_cc_ussd_to_dial" msgid="5202342984749947872">"USSD વિનંતીને DIAL વિનંતી પર સંશોધિત કરી."</string> <string name="stk_cc_ussd_to_ss" msgid="2345360594181405482">"USSD વિનંતીને SS વિનંતી પર સંશોધિત કરી."</string> <string name="stk_cc_ussd_to_ussd" msgid="7466087659967191653">"USSD વિનંતીને નવી USSD વિનંતી પર સંશોધિત કરી."</string> - <!-- no translation found for stk_cc_ussd_to_dial_video (585340552561515305) --> - <skip /> + <string name="stk_cc_ussd_to_dial_video" msgid="585340552561515305">"USSD વિનંતીને વીડિઓ DIAL વિનંતીમાં સંશોધિત કરી."</string> <string name="stk_cc_ss_to_dial" msgid="2151304435775557162">"SS વિનંતીને DIAL વિનંતી પર સંશોધિત કરી."</string> - <!-- no translation found for stk_cc_ss_to_dial_video (4306210904450719045) --> - <skip /> + <string name="stk_cc_ss_to_dial_video" msgid="4306210904450719045">"SS વિનંતીને વીડિઓ DIAL વિનંતીમાં સંશોધિત કરી."</string> <string name="stk_cc_ss_to_ussd" msgid="3951862188105305589">"SS વિનંતીને USSD વિનંતી પર સંશોધિત કરી."</string> <string name="stk_cc_ss_to_ss" msgid="5470768854991452695">"SS વિનંતીને નવી SS વિનંતી પર સંશોધિત કરી."</string> <string name="notification_work_profile_content_description" msgid="4600554564103770764">"કાર્યાલયની પ્રોફાઇલ"</string> diff --git a/core/res/res/values-hi/required_apps_managed_device.xml b/core/res/res/values-hi/required_apps_managed_device.xml new file mode 100644 index 000000000000..9044fccd88ee --- /dev/null +++ b/core/res/res/values-hi/required_apps_managed_device.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string-array name="required_apps_managed_device"> + <item msgid="1104492179978792509">"com.android.settings"</item> + <item msgid="7004798084799227194">"com.android.contacts"</item> + <item msgid="5782220690863647256">"com.android.dialer"</item> + <item msgid="5746338511138092673">"com.android.stk"</item> + <item msgid="1712599182168590664">"com.android.providers.downloads"</item> + <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item> + <item msgid="3892021562839042708">"com.android.documentsui"</item> + </string-array> +</resources> diff --git a/core/res/res/values-hi/required_apps_managed_profile.xml b/core/res/res/values-hi/required_apps_managed_profile.xml new file mode 100644 index 000000000000..4296b0d37dd2 --- /dev/null +++ b/core/res/res/values-hi/required_apps_managed_profile.xml @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string-array name="required_apps_managed_profile"> + <item msgid="1457364287544474838">"com.android.contacts"</item> + <item msgid="4633145750237794002">"com.android.settings"</item> + <item msgid="6518205098643077579">"com.android.providers.downloads"</item> + <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item> + <item msgid="6106837921940099371">"com.android.documentsui"</item> + </string-array> +</resources> diff --git a/core/res/res/values-hi/required_apps_managed_user.xml b/core/res/res/values-hi/required_apps_managed_user.xml new file mode 100644 index 000000000000..1a7ade9dd5f9 --- /dev/null +++ b/core/res/res/values-hi/required_apps_managed_user.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string-array name="required_apps_managed_user"> + <item msgid="4823915868435007499">"com.android.settings"</item> + <item msgid="2250259015310893915">"com.android.contacts"</item> + <item msgid="7166574999426592423">"com.android.dialer"</item> + <item msgid="7306937186458176744">"com.android.stk"</item> + <item msgid="7415441588151512455">"com.android.providers.downloads"</item> + <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item> + <item msgid="8640522622655589373">"com.android.documentsui"</item> + </string-array> +</resources> diff --git a/core/res/res/values-hr/required_apps_managed_device.xml b/core/res/res/values-hr/required_apps_managed_device.xml new file mode 100644 index 000000000000..9044fccd88ee --- /dev/null +++ b/core/res/res/values-hr/required_apps_managed_device.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string-array name="required_apps_managed_device"> + <item msgid="1104492179978792509">"com.android.settings"</item> + <item msgid="7004798084799227194">"com.android.contacts"</item> + <item msgid="5782220690863647256">"com.android.dialer"</item> + <item msgid="5746338511138092673">"com.android.stk"</item> + <item msgid="1712599182168590664">"com.android.providers.downloads"</item> + <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item> + <item msgid="3892021562839042708">"com.android.documentsui"</item> + </string-array> +</resources> diff --git a/core/res/res/values-hr/required_apps_managed_profile.xml b/core/res/res/values-hr/required_apps_managed_profile.xml new file mode 100644 index 000000000000..4296b0d37dd2 --- /dev/null +++ b/core/res/res/values-hr/required_apps_managed_profile.xml @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string-array name="required_apps_managed_profile"> + <item msgid="1457364287544474838">"com.android.contacts"</item> + <item msgid="4633145750237794002">"com.android.settings"</item> + <item msgid="6518205098643077579">"com.android.providers.downloads"</item> + <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item> + <item msgid="6106837921940099371">"com.android.documentsui"</item> + </string-array> +</resources> diff --git a/core/res/res/values-hr/required_apps_managed_user.xml b/core/res/res/values-hr/required_apps_managed_user.xml new file mode 100644 index 000000000000..1a7ade9dd5f9 --- /dev/null +++ b/core/res/res/values-hr/required_apps_managed_user.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string-array name="required_apps_managed_user"> + <item msgid="4823915868435007499">"com.android.settings"</item> + <item msgid="2250259015310893915">"com.android.contacts"</item> + <item msgid="7166574999426592423">"com.android.dialer"</item> + <item msgid="7306937186458176744">"com.android.stk"</item> + <item msgid="7415441588151512455">"com.android.providers.downloads"</item> + <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item> + <item msgid="8640522622655589373">"com.android.documentsui"</item> + </string-array> +</resources> diff --git a/core/res/res/values-hu/required_apps_managed_device.xml b/core/res/res/values-hu/required_apps_managed_device.xml new file mode 100644 index 000000000000..9044fccd88ee --- /dev/null +++ b/core/res/res/values-hu/required_apps_managed_device.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string-array name="required_apps_managed_device"> + <item msgid="1104492179978792509">"com.android.settings"</item> + <item msgid="7004798084799227194">"com.android.contacts"</item> + <item msgid="5782220690863647256">"com.android.dialer"</item> + <item msgid="5746338511138092673">"com.android.stk"</item> + <item msgid="1712599182168590664">"com.android.providers.downloads"</item> + <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item> + <item msgid="3892021562839042708">"com.android.documentsui"</item> + </string-array> +</resources> diff --git a/core/res/res/values-hu/required_apps_managed_profile.xml b/core/res/res/values-hu/required_apps_managed_profile.xml new file mode 100644 index 000000000000..4296b0d37dd2 --- /dev/null +++ b/core/res/res/values-hu/required_apps_managed_profile.xml @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string-array name="required_apps_managed_profile"> + <item msgid="1457364287544474838">"com.android.contacts"</item> + <item msgid="4633145750237794002">"com.android.settings"</item> + <item msgid="6518205098643077579">"com.android.providers.downloads"</item> + <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item> + <item msgid="6106837921940099371">"com.android.documentsui"</item> + </string-array> +</resources> diff --git a/core/res/res/values-hu/required_apps_managed_user.xml b/core/res/res/values-hu/required_apps_managed_user.xml new file mode 100644 index 000000000000..1a7ade9dd5f9 --- /dev/null +++ b/core/res/res/values-hu/required_apps_managed_user.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string-array name="required_apps_managed_user"> + <item msgid="4823915868435007499">"com.android.settings"</item> + <item msgid="2250259015310893915">"com.android.contacts"</item> + <item msgid="7166574999426592423">"com.android.dialer"</item> + <item msgid="7306937186458176744">"com.android.stk"</item> + <item msgid="7415441588151512455">"com.android.providers.downloads"</item> + <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item> + <item msgid="8640522622655589373">"com.android.documentsui"</item> + </string-array> +</resources> diff --git a/core/res/res/values-hy/required_apps_managed_device.xml b/core/res/res/values-hy/required_apps_managed_device.xml new file mode 100644 index 000000000000..9044fccd88ee --- /dev/null +++ b/core/res/res/values-hy/required_apps_managed_device.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string-array name="required_apps_managed_device"> + <item msgid="1104492179978792509">"com.android.settings"</item> + <item msgid="7004798084799227194">"com.android.contacts"</item> + <item msgid="5782220690863647256">"com.android.dialer"</item> + <item msgid="5746338511138092673">"com.android.stk"</item> + <item msgid="1712599182168590664">"com.android.providers.downloads"</item> + <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item> + <item msgid="3892021562839042708">"com.android.documentsui"</item> + </string-array> +</resources> diff --git a/core/res/res/values-hy/required_apps_managed_profile.xml b/core/res/res/values-hy/required_apps_managed_profile.xml new file mode 100644 index 000000000000..4296b0d37dd2 --- /dev/null +++ b/core/res/res/values-hy/required_apps_managed_profile.xml @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string-array name="required_apps_managed_profile"> + <item msgid="1457364287544474838">"com.android.contacts"</item> + <item msgid="4633145750237794002">"com.android.settings"</item> + <item msgid="6518205098643077579">"com.android.providers.downloads"</item> + <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item> + <item msgid="6106837921940099371">"com.android.documentsui"</item> + </string-array> +</resources> diff --git a/core/res/res/values-hy/required_apps_managed_user.xml b/core/res/res/values-hy/required_apps_managed_user.xml new file mode 100644 index 000000000000..1a7ade9dd5f9 --- /dev/null +++ b/core/res/res/values-hy/required_apps_managed_user.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string-array name="required_apps_managed_user"> + <item msgid="4823915868435007499">"com.android.settings"</item> + <item msgid="2250259015310893915">"com.android.contacts"</item> + <item msgid="7166574999426592423">"com.android.dialer"</item> + <item msgid="7306937186458176744">"com.android.stk"</item> + <item msgid="7415441588151512455">"com.android.providers.downloads"</item> + <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item> + <item msgid="8640522622655589373">"com.android.documentsui"</item> + </string-array> +</resources> diff --git a/core/res/res/values-is/required_apps_managed_device.xml b/core/res/res/values-is/required_apps_managed_device.xml new file mode 100644 index 000000000000..9044fccd88ee --- /dev/null +++ b/core/res/res/values-is/required_apps_managed_device.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string-array name="required_apps_managed_device"> + <item msgid="1104492179978792509">"com.android.settings"</item> + <item msgid="7004798084799227194">"com.android.contacts"</item> + <item msgid="5782220690863647256">"com.android.dialer"</item> + <item msgid="5746338511138092673">"com.android.stk"</item> + <item msgid="1712599182168590664">"com.android.providers.downloads"</item> + <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item> + <item msgid="3892021562839042708">"com.android.documentsui"</item> + </string-array> +</resources> diff --git a/core/res/res/values-is/required_apps_managed_profile.xml b/core/res/res/values-is/required_apps_managed_profile.xml new file mode 100644 index 000000000000..4296b0d37dd2 --- /dev/null +++ b/core/res/res/values-is/required_apps_managed_profile.xml @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string-array name="required_apps_managed_profile"> + <item msgid="1457364287544474838">"com.android.contacts"</item> + <item msgid="4633145750237794002">"com.android.settings"</item> + <item msgid="6518205098643077579">"com.android.providers.downloads"</item> + <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item> + <item msgid="6106837921940099371">"com.android.documentsui"</item> + </string-array> +</resources> diff --git a/core/res/res/values-is/required_apps_managed_user.xml b/core/res/res/values-is/required_apps_managed_user.xml new file mode 100644 index 000000000000..1a7ade9dd5f9 --- /dev/null +++ b/core/res/res/values-is/required_apps_managed_user.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string-array name="required_apps_managed_user"> + <item msgid="4823915868435007499">"com.android.settings"</item> + <item msgid="2250259015310893915">"com.android.contacts"</item> + <item msgid="7166574999426592423">"com.android.dialer"</item> + <item msgid="7306937186458176744">"com.android.stk"</item> + <item msgid="7415441588151512455">"com.android.providers.downloads"</item> + <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item> + <item msgid="8640522622655589373">"com.android.documentsui"</item> + </string-array> +</resources> diff --git a/core/res/res/values-iw/required_apps_managed_device.xml b/core/res/res/values-iw/required_apps_managed_device.xml new file mode 100644 index 000000000000..9044fccd88ee --- /dev/null +++ b/core/res/res/values-iw/required_apps_managed_device.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string-array name="required_apps_managed_device"> + <item msgid="1104492179978792509">"com.android.settings"</item> + <item msgid="7004798084799227194">"com.android.contacts"</item> + <item msgid="5782220690863647256">"com.android.dialer"</item> + <item msgid="5746338511138092673">"com.android.stk"</item> + <item msgid="1712599182168590664">"com.android.providers.downloads"</item> + <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item> + <item msgid="3892021562839042708">"com.android.documentsui"</item> + </string-array> +</resources> diff --git a/core/res/res/values-iw/required_apps_managed_profile.xml b/core/res/res/values-iw/required_apps_managed_profile.xml new file mode 100644 index 000000000000..4296b0d37dd2 --- /dev/null +++ b/core/res/res/values-iw/required_apps_managed_profile.xml @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string-array name="required_apps_managed_profile"> + <item msgid="1457364287544474838">"com.android.contacts"</item> + <item msgid="4633145750237794002">"com.android.settings"</item> + <item msgid="6518205098643077579">"com.android.providers.downloads"</item> + <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item> + <item msgid="6106837921940099371">"com.android.documentsui"</item> + </string-array> +</resources> diff --git a/core/res/res/values-iw/required_apps_managed_user.xml b/core/res/res/values-iw/required_apps_managed_user.xml new file mode 100644 index 000000000000..1a7ade9dd5f9 --- /dev/null +++ b/core/res/res/values-iw/required_apps_managed_user.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string-array name="required_apps_managed_user"> + <item msgid="4823915868435007499">"com.android.settings"</item> + <item msgid="2250259015310893915">"com.android.contacts"</item> + <item msgid="7166574999426592423">"com.android.dialer"</item> + <item msgid="7306937186458176744">"com.android.stk"</item> + <item msgid="7415441588151512455">"com.android.providers.downloads"</item> + <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item> + <item msgid="8640522622655589373">"com.android.documentsui"</item> + </string-array> +</resources> diff --git a/core/res/res/values-ja/required_apps_managed_device.xml b/core/res/res/values-ja/required_apps_managed_device.xml new file mode 100644 index 000000000000..9044fccd88ee --- /dev/null +++ b/core/res/res/values-ja/required_apps_managed_device.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string-array name="required_apps_managed_device"> + <item msgid="1104492179978792509">"com.android.settings"</item> + <item msgid="7004798084799227194">"com.android.contacts"</item> + <item msgid="5782220690863647256">"com.android.dialer"</item> + <item msgid="5746338511138092673">"com.android.stk"</item> + <item msgid="1712599182168590664">"com.android.providers.downloads"</item> + <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item> + <item msgid="3892021562839042708">"com.android.documentsui"</item> + </string-array> +</resources> diff --git a/core/res/res/values-ja/required_apps_managed_profile.xml b/core/res/res/values-ja/required_apps_managed_profile.xml new file mode 100644 index 000000000000..4296b0d37dd2 --- /dev/null +++ b/core/res/res/values-ja/required_apps_managed_profile.xml @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string-array name="required_apps_managed_profile"> + <item msgid="1457364287544474838">"com.android.contacts"</item> + <item msgid="4633145750237794002">"com.android.settings"</item> + <item msgid="6518205098643077579">"com.android.providers.downloads"</item> + <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item> + <item msgid="6106837921940099371">"com.android.documentsui"</item> + </string-array> +</resources> diff --git a/core/res/res/values-ja/required_apps_managed_user.xml b/core/res/res/values-ja/required_apps_managed_user.xml new file mode 100644 index 000000000000..1a7ade9dd5f9 --- /dev/null +++ b/core/res/res/values-ja/required_apps_managed_user.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string-array name="required_apps_managed_user"> + <item msgid="4823915868435007499">"com.android.settings"</item> + <item msgid="2250259015310893915">"com.android.contacts"</item> + <item msgid="7166574999426592423">"com.android.dialer"</item> + <item msgid="7306937186458176744">"com.android.stk"</item> + <item msgid="7415441588151512455">"com.android.providers.downloads"</item> + <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item> + <item msgid="8640522622655589373">"com.android.documentsui"</item> + </string-array> +</resources> diff --git a/core/res/res/values-kk/required_apps_managed_device.xml b/core/res/res/values-kk/required_apps_managed_device.xml new file mode 100644 index 000000000000..9044fccd88ee --- /dev/null +++ b/core/res/res/values-kk/required_apps_managed_device.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string-array name="required_apps_managed_device"> + <item msgid="1104492179978792509">"com.android.settings"</item> + <item msgid="7004798084799227194">"com.android.contacts"</item> + <item msgid="5782220690863647256">"com.android.dialer"</item> + <item msgid="5746338511138092673">"com.android.stk"</item> + <item msgid="1712599182168590664">"com.android.providers.downloads"</item> + <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item> + <item msgid="3892021562839042708">"com.android.documentsui"</item> + </string-array> +</resources> diff --git a/core/res/res/values-kk/required_apps_managed_profile.xml b/core/res/res/values-kk/required_apps_managed_profile.xml new file mode 100644 index 000000000000..4296b0d37dd2 --- /dev/null +++ b/core/res/res/values-kk/required_apps_managed_profile.xml @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string-array name="required_apps_managed_profile"> + <item msgid="1457364287544474838">"com.android.contacts"</item> + <item msgid="4633145750237794002">"com.android.settings"</item> + <item msgid="6518205098643077579">"com.android.providers.downloads"</item> + <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item> + <item msgid="6106837921940099371">"com.android.documentsui"</item> + </string-array> +</resources> diff --git a/core/res/res/values-kk/required_apps_managed_user.xml b/core/res/res/values-kk/required_apps_managed_user.xml new file mode 100644 index 000000000000..1a7ade9dd5f9 --- /dev/null +++ b/core/res/res/values-kk/required_apps_managed_user.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string-array name="required_apps_managed_user"> + <item msgid="4823915868435007499">"com.android.settings"</item> + <item msgid="2250259015310893915">"com.android.contacts"</item> + <item msgid="7166574999426592423">"com.android.dialer"</item> + <item msgid="7306937186458176744">"com.android.stk"</item> + <item msgid="7415441588151512455">"com.android.providers.downloads"</item> + <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item> + <item msgid="8640522622655589373">"com.android.documentsui"</item> + </string-array> +</resources> diff --git a/core/res/res/values-km/required_apps_managed_device.xml b/core/res/res/values-km/required_apps_managed_device.xml new file mode 100644 index 000000000000..9044fccd88ee --- /dev/null +++ b/core/res/res/values-km/required_apps_managed_device.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string-array name="required_apps_managed_device"> + <item msgid="1104492179978792509">"com.android.settings"</item> + <item msgid="7004798084799227194">"com.android.contacts"</item> + <item msgid="5782220690863647256">"com.android.dialer"</item> + <item msgid="5746338511138092673">"com.android.stk"</item> + <item msgid="1712599182168590664">"com.android.providers.downloads"</item> + <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item> + <item msgid="3892021562839042708">"com.android.documentsui"</item> + </string-array> +</resources> diff --git a/core/res/res/values-km/required_apps_managed_profile.xml b/core/res/res/values-km/required_apps_managed_profile.xml new file mode 100644 index 000000000000..4296b0d37dd2 --- /dev/null +++ b/core/res/res/values-km/required_apps_managed_profile.xml @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string-array name="required_apps_managed_profile"> + <item msgid="1457364287544474838">"com.android.contacts"</item> + <item msgid="4633145750237794002">"com.android.settings"</item> + <item msgid="6518205098643077579">"com.android.providers.downloads"</item> + <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item> + <item msgid="6106837921940099371">"com.android.documentsui"</item> + </string-array> +</resources> diff --git a/core/res/res/values-km/required_apps_managed_user.xml b/core/res/res/values-km/required_apps_managed_user.xml new file mode 100644 index 000000000000..1a7ade9dd5f9 --- /dev/null +++ b/core/res/res/values-km/required_apps_managed_user.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string-array name="required_apps_managed_user"> + <item msgid="4823915868435007499">"com.android.settings"</item> + <item msgid="2250259015310893915">"com.android.contacts"</item> + <item msgid="7166574999426592423">"com.android.dialer"</item> + <item msgid="7306937186458176744">"com.android.stk"</item> + <item msgid="7415441588151512455">"com.android.providers.downloads"</item> + <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item> + <item msgid="8640522622655589373">"com.android.documentsui"</item> + </string-array> +</resources> diff --git a/core/res/res/values-km/strings.xml b/core/res/res/values-km/strings.xml index b2778a1e179c..7be15f156c57 100644 --- a/core/res/res/values-km/strings.xml +++ b/core/res/res/values-km/strings.xml @@ -1696,9 +1696,9 @@ <string name="stk_cc_ussd_to_dial" msgid="5202342984749947872">"សំណើរ USSD ត្រូវបានកែសម្រួលទៅតាមសំណើរការហៅទូរស័ព្ទ។"</string> <string name="stk_cc_ussd_to_ss" msgid="2345360594181405482">"សំណើរ USSD ត្រូវបានកែសម្រួលទៅតាមសំណើរ SS។"</string> <string name="stk_cc_ussd_to_ussd" msgid="7466087659967191653">"សំណើរ USSD ត្រូវបានកែសម្រួលទៅតាមសំណើរ USSD ថ្មី។្"</string> - <string name="stk_cc_ussd_to_dial_video" msgid="585340552561515305">"សំណើ USSD ត្រូវបានកែសម្រួលទៅជាសំណើ Video DIAL ។"</string> + <string name="stk_cc_ussd_to_dial_video" msgid="585340552561515305">"សំណើ USSD ត្រូវបានកែប្រែទៅជាសំណើ DIAL ជាវីដេអូ។"</string> <string name="stk_cc_ss_to_dial" msgid="2151304435775557162">"សំណើរ SS ត្រូវបានកែសម្រួលទៅតាមសំណើរការហៅទូរស័ព្ទ។"</string> - <string name="stk_cc_ss_to_dial_video" msgid="4306210904450719045">"សំណើ SS ត្រូវបានកែសម្រួលទៅជាសំណើ Video DIAL ។"</string> + <string name="stk_cc_ss_to_dial_video" msgid="4306210904450719045">"សំណើ SS ត្រូវបានកែប្រែទៅជាសំណើ DIAL ជាវីដេអូ។"</string> <string name="stk_cc_ss_to_ussd" msgid="3951862188105305589">"សំណើរ SS ត្រូវបានកែសម្រួលទៅតាមសំណើរ USSD។"</string> <string name="stk_cc_ss_to_ss" msgid="5470768854991452695">"សំណើរ SS ត្រូវបានកែសម្រួលទៅតាមសំណើរ SS ថ្មី។"</string> <string name="notification_work_profile_content_description" msgid="4600554564103770764">"ប្រវត្តិរូបការងារ"</string> diff --git a/core/res/res/values-kn/required_apps_managed_device.xml b/core/res/res/values-kn/required_apps_managed_device.xml new file mode 100644 index 000000000000..e15b0b015f03 --- /dev/null +++ b/core/res/res/values-kn/required_apps_managed_device.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string-array name="required_apps_managed_device"> + <item msgid="1104492179978792509">"com.android.ಸೆಟ್ಟಿಂಗ್ಗಳು"</item> + <item msgid="7004798084799227194">"com.android.ಸಂಪರ್ಕಗಳು"</item> + <item msgid="5782220690863647256">"com.android.ಡಯಲರ್"</item> + <item msgid="5746338511138092673">"com.android.stk"</item> + <item msgid="1712599182168590664">"com.android.ಒದಗಿಸುವವರು.ಡೌನ್ಲೋಡ್ಗಳು"</item> + <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item> + <item msgid="3892021562839042708">"com.android.documentsui"</item> + </string-array> +</resources> diff --git a/core/res/res/values-kn/required_apps_managed_profile.xml b/core/res/res/values-kn/required_apps_managed_profile.xml new file mode 100644 index 000000000000..92ab682d47ee --- /dev/null +++ b/core/res/res/values-kn/required_apps_managed_profile.xml @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string-array name="required_apps_managed_profile"> + <item msgid="1457364287544474838">"com.android.ಸಂಪರ್ಕಗಳು"</item> + <item msgid="4633145750237794002">"com.android.ಸೆಟ್ಟಿಂಗ್ಗಳು"</item> + <item msgid="6518205098643077579">"com.android.ಒದಗಿಸುವವರು.ಡೌನ್ಲೋಡ್ಗಳು"</item> + <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item> + <item msgid="6106837921940099371">"com.android.documentsui"</item> + </string-array> +</resources> diff --git a/core/res/res/values-kn/required_apps_managed_user.xml b/core/res/res/values-kn/required_apps_managed_user.xml new file mode 100644 index 000000000000..0c59edd12681 --- /dev/null +++ b/core/res/res/values-kn/required_apps_managed_user.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string-array name="required_apps_managed_user"> + <item msgid="4823915868435007499">"com.android.ಸೆಟ್ಟಿಂಗ್ಗಳು"</item> + <item msgid="2250259015310893915">"com.android.ಸಂಪರ್ಕಗಳು"</item> + <item msgid="7166574999426592423">"com.android.ಡಯಲರ್"</item> + <item msgid="7306937186458176744">"com.android.stk"</item> + <item msgid="7415441588151512455">"com.android.ಒದಗಿಸುವವರು.ಡೌನ್ಲೋಡ್ಗಳು"</item> + <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item> + <item msgid="8640522622655589373">"com.android.documentsui"</item> + </string-array> +</resources> diff --git a/core/res/res/values-kn/strings.xml b/core/res/res/values-kn/strings.xml index 4485561754eb..99db9e6f6a69 100644 --- a/core/res/res/values-kn/strings.xml +++ b/core/res/res/values-kn/strings.xml @@ -1695,11 +1695,9 @@ <string name="stk_cc_ussd_to_dial" msgid="5202342984749947872">"USSD ವಿನಂತಿಯನ್ನು DIAL ವಿನಂತಿಗೆ ಮಾರ್ಪಡಿಸಲಾಗಿದೆ."</string> <string name="stk_cc_ussd_to_ss" msgid="2345360594181405482">"USSD ವಿನಂತಿಯನ್ನು SS ವಿನಂತಿಗೆ ಮಾರ್ಪಡಿಸಲಾಗಿದೆ."</string> <string name="stk_cc_ussd_to_ussd" msgid="7466087659967191653">"USSD ವಿನಂತಿಯನ್ನು ಹೊಸ USSD ವಿನಂತಿಗೆ ಮಾರ್ಪಡಿಸಲಾಗಿದೆ."</string> - <!-- no translation found for stk_cc_ussd_to_dial_video (585340552561515305) --> - <skip /> + <string name="stk_cc_ussd_to_dial_video" msgid="585340552561515305">"USSD ವಿನಂತಿಯನ್ನು ವೀಡಿಯೊ DIAL ವಿನಂತಿಗೆ ಮಾರ್ಪಡಿಸಲಾಗಿದೆ."</string> <string name="stk_cc_ss_to_dial" msgid="2151304435775557162">"SS ವಿನಂತಿಯನ್ನು DIAL ವಿನಂತಿಗೆ ಮಾರ್ಪಡಿಸಲಾಗಿದೆ."</string> - <!-- no translation found for stk_cc_ss_to_dial_video (4306210904450719045) --> - <skip /> + <string name="stk_cc_ss_to_dial_video" msgid="4306210904450719045">"SS ವಿನಂತಿಯನ್ನು ವೀಡಿಯೊ DIAL ವಿನಂತಿಗೆ ಮಾರ್ಪಡಿಸಲಾಗಿದೆ."</string> <string name="stk_cc_ss_to_ussd" msgid="3951862188105305589">"SS ವಿನಂತಿಯನ್ನು USSD ವಿನಂತಿಗೆ ಮಾರ್ಪಡಿಸಲಾಗಿದೆ."</string> <string name="stk_cc_ss_to_ss" msgid="5470768854991452695">"SS ವಿನಂತಿಯನ್ನು ಹೊಸ SS ವಿನಂತಿಗೆ ಮಾರ್ಪಡಿಸಲಾಗಿದೆ."</string> <string name="notification_work_profile_content_description" msgid="4600554564103770764">"ಉದ್ಯೋಗ ಪ್ರೊಫೈಲ್"</string> diff --git a/core/res/res/values-ko/required_apps_managed_device.xml b/core/res/res/values-ko/required_apps_managed_device.xml new file mode 100644 index 000000000000..9044fccd88ee --- /dev/null +++ b/core/res/res/values-ko/required_apps_managed_device.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string-array name="required_apps_managed_device"> + <item msgid="1104492179978792509">"com.android.settings"</item> + <item msgid="7004798084799227194">"com.android.contacts"</item> + <item msgid="5782220690863647256">"com.android.dialer"</item> + <item msgid="5746338511138092673">"com.android.stk"</item> + <item msgid="1712599182168590664">"com.android.providers.downloads"</item> + <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item> + <item msgid="3892021562839042708">"com.android.documentsui"</item> + </string-array> +</resources> diff --git a/core/res/res/values-ko/required_apps_managed_profile.xml b/core/res/res/values-ko/required_apps_managed_profile.xml new file mode 100644 index 000000000000..4296b0d37dd2 --- /dev/null +++ b/core/res/res/values-ko/required_apps_managed_profile.xml @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string-array name="required_apps_managed_profile"> + <item msgid="1457364287544474838">"com.android.contacts"</item> + <item msgid="4633145750237794002">"com.android.settings"</item> + <item msgid="6518205098643077579">"com.android.providers.downloads"</item> + <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item> + <item msgid="6106837921940099371">"com.android.documentsui"</item> + </string-array> +</resources> diff --git a/core/res/res/values-ko/required_apps_managed_user.xml b/core/res/res/values-ko/required_apps_managed_user.xml new file mode 100644 index 000000000000..1a7ade9dd5f9 --- /dev/null +++ b/core/res/res/values-ko/required_apps_managed_user.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string-array name="required_apps_managed_user"> + <item msgid="4823915868435007499">"com.android.settings"</item> + <item msgid="2250259015310893915">"com.android.contacts"</item> + <item msgid="7166574999426592423">"com.android.dialer"</item> + <item msgid="7306937186458176744">"com.android.stk"</item> + <item msgid="7415441588151512455">"com.android.providers.downloads"</item> + <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item> + <item msgid="8640522622655589373">"com.android.documentsui"</item> + </string-array> +</resources> diff --git a/core/res/res/values-ky/required_apps_managed_device.xml b/core/res/res/values-ky/required_apps_managed_device.xml new file mode 100644 index 000000000000..9044fccd88ee --- /dev/null +++ b/core/res/res/values-ky/required_apps_managed_device.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string-array name="required_apps_managed_device"> + <item msgid="1104492179978792509">"com.android.settings"</item> + <item msgid="7004798084799227194">"com.android.contacts"</item> + <item msgid="5782220690863647256">"com.android.dialer"</item> + <item msgid="5746338511138092673">"com.android.stk"</item> + <item msgid="1712599182168590664">"com.android.providers.downloads"</item> + <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item> + <item msgid="3892021562839042708">"com.android.documentsui"</item> + </string-array> +</resources> diff --git a/core/res/res/values-ky/required_apps_managed_profile.xml b/core/res/res/values-ky/required_apps_managed_profile.xml new file mode 100644 index 000000000000..4296b0d37dd2 --- /dev/null +++ b/core/res/res/values-ky/required_apps_managed_profile.xml @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string-array name="required_apps_managed_profile"> + <item msgid="1457364287544474838">"com.android.contacts"</item> + <item msgid="4633145750237794002">"com.android.settings"</item> + <item msgid="6518205098643077579">"com.android.providers.downloads"</item> + <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item> + <item msgid="6106837921940099371">"com.android.documentsui"</item> + </string-array> +</resources> diff --git a/core/res/res/values-ky/required_apps_managed_user.xml b/core/res/res/values-ky/required_apps_managed_user.xml new file mode 100644 index 000000000000..1a7ade9dd5f9 --- /dev/null +++ b/core/res/res/values-ky/required_apps_managed_user.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string-array name="required_apps_managed_user"> + <item msgid="4823915868435007499">"com.android.settings"</item> + <item msgid="2250259015310893915">"com.android.contacts"</item> + <item msgid="7166574999426592423">"com.android.dialer"</item> + <item msgid="7306937186458176744">"com.android.stk"</item> + <item msgid="7415441588151512455">"com.android.providers.downloads"</item> + <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item> + <item msgid="8640522622655589373">"com.android.documentsui"</item> + </string-array> +</resources> diff --git a/core/res/res/values-ky/strings.xml b/core/res/res/values-ky/strings.xml index e580a59a96b0..cfa05dedc029 100644 --- a/core/res/res/values-ky/strings.xml +++ b/core/res/res/values-ky/strings.xml @@ -212,7 +212,7 @@ <string name="global_action_power_off" msgid="4471879440839879722">"Кубатын өчүрүү"</string> <string name="global_action_emergency" msgid="7112311161137421166">"Тез жардам"</string> <string name="global_action_bug_report" msgid="7934010578922304799">"Ката тууралуу билдирүү"</string> - <string name="global_action_logout" msgid="935179188218826050">"Сессияны аяктоо"</string> + <string name="global_action_logout" msgid="935179188218826050">"Сеансты бүтүрүү"</string> <string name="bugreport_title" msgid="2667494803742548533">"Ката тууралуу билдирүү түзүү"</string> <string name="bugreport_message" msgid="398447048750350456">"Ушуну менен түзмөгүңүздүн учурдагы абалы тууралуу маалымат топтолуп, электрондук почта аркылуу жөнөтүлөт. Отчет даяр болгуча бир аз күтө туруңуз."</string> <string name="bugreport_option_interactive_title" msgid="8635056131768862479">"Интерактивдүү кабар"</string> @@ -1502,7 +1502,7 @@ <string name="disable_accessibility_shortcut" msgid="627625354248453445">"Кыска жолду өчүрүү"</string> <string name="leave_accessibility_shortcut_on" msgid="7653111894438512680">"Кыска жолду колдонуу"</string> <string name="color_inversion_feature_name" msgid="4231186527799958644">"Түстү инверсиялоо"</string> - <string name="color_correction_feature_name" msgid="6779391426096954933">"Түстү түзөтүү"</string> + <string name="color_correction_feature_name" msgid="6779391426096954933">"Түсүн тууралоо"</string> <string name="accessibility_shortcut_enabling_service" msgid="7771852911861522636">"Атайын мүмкүнчүлүктөр кыска жолу <xliff:g id="SERVICE_NAME">%1$s</xliff:g> кызматын күйгүздү"</string> <string name="accessibility_shortcut_disabling_service" msgid="2747243438223109821">"Атайын мүмкүнчүлүктөр кыска жолу <xliff:g id="SERVICE_NAME">%1$s</xliff:g> кызматын өчүрдү"</string> <string name="accessibility_button_prompt_text" msgid="4234556536456854251">"Атайын мүмкүнчүлүктөр баскычын таптаганыңызда иштетиле турган функцияны тандаңыз:"</string> diff --git a/core/res/res/values-lo/required_apps_managed_device.xml b/core/res/res/values-lo/required_apps_managed_device.xml new file mode 100644 index 000000000000..9044fccd88ee --- /dev/null +++ b/core/res/res/values-lo/required_apps_managed_device.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string-array name="required_apps_managed_device"> + <item msgid="1104492179978792509">"com.android.settings"</item> + <item msgid="7004798084799227194">"com.android.contacts"</item> + <item msgid="5782220690863647256">"com.android.dialer"</item> + <item msgid="5746338511138092673">"com.android.stk"</item> + <item msgid="1712599182168590664">"com.android.providers.downloads"</item> + <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item> + <item msgid="3892021562839042708">"com.android.documentsui"</item> + </string-array> +</resources> diff --git a/core/res/res/values-lo/required_apps_managed_profile.xml b/core/res/res/values-lo/required_apps_managed_profile.xml new file mode 100644 index 000000000000..4296b0d37dd2 --- /dev/null +++ b/core/res/res/values-lo/required_apps_managed_profile.xml @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string-array name="required_apps_managed_profile"> + <item msgid="1457364287544474838">"com.android.contacts"</item> + <item msgid="4633145750237794002">"com.android.settings"</item> + <item msgid="6518205098643077579">"com.android.providers.downloads"</item> + <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item> + <item msgid="6106837921940099371">"com.android.documentsui"</item> + </string-array> +</resources> diff --git a/core/res/res/values-lo/required_apps_managed_user.xml b/core/res/res/values-lo/required_apps_managed_user.xml new file mode 100644 index 000000000000..1a7ade9dd5f9 --- /dev/null +++ b/core/res/res/values-lo/required_apps_managed_user.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string-array name="required_apps_managed_user"> + <item msgid="4823915868435007499">"com.android.settings"</item> + <item msgid="2250259015310893915">"com.android.contacts"</item> + <item msgid="7166574999426592423">"com.android.dialer"</item> + <item msgid="7306937186458176744">"com.android.stk"</item> + <item msgid="7415441588151512455">"com.android.providers.downloads"</item> + <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item> + <item msgid="8640522622655589373">"com.android.documentsui"</item> + </string-array> +</resources> diff --git a/core/res/res/values-lt/required_apps_managed_device.xml b/core/res/res/values-lt/required_apps_managed_device.xml new file mode 100644 index 000000000000..9044fccd88ee --- /dev/null +++ b/core/res/res/values-lt/required_apps_managed_device.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string-array name="required_apps_managed_device"> + <item msgid="1104492179978792509">"com.android.settings"</item> + <item msgid="7004798084799227194">"com.android.contacts"</item> + <item msgid="5782220690863647256">"com.android.dialer"</item> + <item msgid="5746338511138092673">"com.android.stk"</item> + <item msgid="1712599182168590664">"com.android.providers.downloads"</item> + <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item> + <item msgid="3892021562839042708">"com.android.documentsui"</item> + </string-array> +</resources> diff --git a/core/res/res/values-lt/required_apps_managed_profile.xml b/core/res/res/values-lt/required_apps_managed_profile.xml new file mode 100644 index 000000000000..4296b0d37dd2 --- /dev/null +++ b/core/res/res/values-lt/required_apps_managed_profile.xml @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string-array name="required_apps_managed_profile"> + <item msgid="1457364287544474838">"com.android.contacts"</item> + <item msgid="4633145750237794002">"com.android.settings"</item> + <item msgid="6518205098643077579">"com.android.providers.downloads"</item> + <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item> + <item msgid="6106837921940099371">"com.android.documentsui"</item> + </string-array> +</resources> diff --git a/core/res/res/values-lt/required_apps_managed_user.xml b/core/res/res/values-lt/required_apps_managed_user.xml new file mode 100644 index 000000000000..1a7ade9dd5f9 --- /dev/null +++ b/core/res/res/values-lt/required_apps_managed_user.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string-array name="required_apps_managed_user"> + <item msgid="4823915868435007499">"com.android.settings"</item> + <item msgid="2250259015310893915">"com.android.contacts"</item> + <item msgid="7166574999426592423">"com.android.dialer"</item> + <item msgid="7306937186458176744">"com.android.stk"</item> + <item msgid="7415441588151512455">"com.android.providers.downloads"</item> + <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item> + <item msgid="8640522622655589373">"com.android.documentsui"</item> + </string-array> +</resources> diff --git a/core/res/res/values-lv/required_apps_managed_device.xml b/core/res/res/values-lv/required_apps_managed_device.xml new file mode 100644 index 000000000000..9044fccd88ee --- /dev/null +++ b/core/res/res/values-lv/required_apps_managed_device.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string-array name="required_apps_managed_device"> + <item msgid="1104492179978792509">"com.android.settings"</item> + <item msgid="7004798084799227194">"com.android.contacts"</item> + <item msgid="5782220690863647256">"com.android.dialer"</item> + <item msgid="5746338511138092673">"com.android.stk"</item> + <item msgid="1712599182168590664">"com.android.providers.downloads"</item> + <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item> + <item msgid="3892021562839042708">"com.android.documentsui"</item> + </string-array> +</resources> diff --git a/core/res/res/values-lv/required_apps_managed_profile.xml b/core/res/res/values-lv/required_apps_managed_profile.xml new file mode 100644 index 000000000000..4296b0d37dd2 --- /dev/null +++ b/core/res/res/values-lv/required_apps_managed_profile.xml @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string-array name="required_apps_managed_profile"> + <item msgid="1457364287544474838">"com.android.contacts"</item> + <item msgid="4633145750237794002">"com.android.settings"</item> + <item msgid="6518205098643077579">"com.android.providers.downloads"</item> + <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item> + <item msgid="6106837921940099371">"com.android.documentsui"</item> + </string-array> +</resources> diff --git a/core/res/res/values-lv/required_apps_managed_user.xml b/core/res/res/values-lv/required_apps_managed_user.xml new file mode 100644 index 000000000000..1a7ade9dd5f9 --- /dev/null +++ b/core/res/res/values-lv/required_apps_managed_user.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string-array name="required_apps_managed_user"> + <item msgid="4823915868435007499">"com.android.settings"</item> + <item msgid="2250259015310893915">"com.android.contacts"</item> + <item msgid="7166574999426592423">"com.android.dialer"</item> + <item msgid="7306937186458176744">"com.android.stk"</item> + <item msgid="7415441588151512455">"com.android.providers.downloads"</item> + <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item> + <item msgid="8640522622655589373">"com.android.documentsui"</item> + </string-array> +</resources> diff --git a/core/res/res/values-mk/required_apps_managed_device.xml b/core/res/res/values-mk/required_apps_managed_device.xml new file mode 100644 index 000000000000..9044fccd88ee --- /dev/null +++ b/core/res/res/values-mk/required_apps_managed_device.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string-array name="required_apps_managed_device"> + <item msgid="1104492179978792509">"com.android.settings"</item> + <item msgid="7004798084799227194">"com.android.contacts"</item> + <item msgid="5782220690863647256">"com.android.dialer"</item> + <item msgid="5746338511138092673">"com.android.stk"</item> + <item msgid="1712599182168590664">"com.android.providers.downloads"</item> + <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item> + <item msgid="3892021562839042708">"com.android.documentsui"</item> + </string-array> +</resources> diff --git a/core/res/res/values-mk/required_apps_managed_profile.xml b/core/res/res/values-mk/required_apps_managed_profile.xml new file mode 100644 index 000000000000..4296b0d37dd2 --- /dev/null +++ b/core/res/res/values-mk/required_apps_managed_profile.xml @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string-array name="required_apps_managed_profile"> + <item msgid="1457364287544474838">"com.android.contacts"</item> + <item msgid="4633145750237794002">"com.android.settings"</item> + <item msgid="6518205098643077579">"com.android.providers.downloads"</item> + <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item> + <item msgid="6106837921940099371">"com.android.documentsui"</item> + </string-array> +</resources> diff --git a/core/res/res/values-mk/required_apps_managed_user.xml b/core/res/res/values-mk/required_apps_managed_user.xml new file mode 100644 index 000000000000..1a7ade9dd5f9 --- /dev/null +++ b/core/res/res/values-mk/required_apps_managed_user.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string-array name="required_apps_managed_user"> + <item msgid="4823915868435007499">"com.android.settings"</item> + <item msgid="2250259015310893915">"com.android.contacts"</item> + <item msgid="7166574999426592423">"com.android.dialer"</item> + <item msgid="7306937186458176744">"com.android.stk"</item> + <item msgid="7415441588151512455">"com.android.providers.downloads"</item> + <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item> + <item msgid="8640522622655589373">"com.android.documentsui"</item> + </string-array> +</resources> diff --git a/core/res/res/values-ml/required_apps_managed_device.xml b/core/res/res/values-ml/required_apps_managed_device.xml new file mode 100644 index 000000000000..9044fccd88ee --- /dev/null +++ b/core/res/res/values-ml/required_apps_managed_device.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string-array name="required_apps_managed_device"> + <item msgid="1104492179978792509">"com.android.settings"</item> + <item msgid="7004798084799227194">"com.android.contacts"</item> + <item msgid="5782220690863647256">"com.android.dialer"</item> + <item msgid="5746338511138092673">"com.android.stk"</item> + <item msgid="1712599182168590664">"com.android.providers.downloads"</item> + <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item> + <item msgid="3892021562839042708">"com.android.documentsui"</item> + </string-array> +</resources> diff --git a/core/res/res/values-ml/required_apps_managed_profile.xml b/core/res/res/values-ml/required_apps_managed_profile.xml new file mode 100644 index 000000000000..4296b0d37dd2 --- /dev/null +++ b/core/res/res/values-ml/required_apps_managed_profile.xml @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string-array name="required_apps_managed_profile"> + <item msgid="1457364287544474838">"com.android.contacts"</item> + <item msgid="4633145750237794002">"com.android.settings"</item> + <item msgid="6518205098643077579">"com.android.providers.downloads"</item> + <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item> + <item msgid="6106837921940099371">"com.android.documentsui"</item> + </string-array> +</resources> diff --git a/core/res/res/values-ml/required_apps_managed_user.xml b/core/res/res/values-ml/required_apps_managed_user.xml new file mode 100644 index 000000000000..1a7ade9dd5f9 --- /dev/null +++ b/core/res/res/values-ml/required_apps_managed_user.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string-array name="required_apps_managed_user"> + <item msgid="4823915868435007499">"com.android.settings"</item> + <item msgid="2250259015310893915">"com.android.contacts"</item> + <item msgid="7166574999426592423">"com.android.dialer"</item> + <item msgid="7306937186458176744">"com.android.stk"</item> + <item msgid="7415441588151512455">"com.android.providers.downloads"</item> + <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item> + <item msgid="8640522622655589373">"com.android.documentsui"</item> + </string-array> +</resources> diff --git a/core/res/res/values-ml/strings.xml b/core/res/res/values-ml/strings.xml index 2595f948bf6c..fc958faf3927 100644 --- a/core/res/res/values-ml/strings.xml +++ b/core/res/res/values-ml/strings.xml @@ -1695,11 +1695,9 @@ <string name="stk_cc_ussd_to_dial" msgid="5202342984749947872">"USSD അഭ്യർത്ഥന, DIAL അഭ്യർത്ഥനയായി പരിഷ്ക്കരിച്ചു."</string> <string name="stk_cc_ussd_to_ss" msgid="2345360594181405482">"USSD അഭ്യർത്ഥന, SS അഭ്യർത്ഥനയായി പരിഷ്ക്കരിച്ചു."</string> <string name="stk_cc_ussd_to_ussd" msgid="7466087659967191653">"USSD അഭ്യർത്ഥന, പുതിയ USSD അഭ്യർത്ഥനയായി പരിഷ്ക്കരിച്ചു."</string> - <!-- no translation found for stk_cc_ussd_to_dial_video (585340552561515305) --> - <skip /> + <string name="stk_cc_ussd_to_dial_video" msgid="585340552561515305">"USSD അഭ്യർത്ഥന, വീഡിയോ DIAL അഭ്യർത്ഥനയായി പരിഷ്ക്കരിച്ചു."</string> <string name="stk_cc_ss_to_dial" msgid="2151304435775557162">"SS അഭ്യർത്ഥന, DIAL അഭ്യർത്ഥനയായി പരിഷ്ക്കരിച്ചു."</string> - <!-- no translation found for stk_cc_ss_to_dial_video (4306210904450719045) --> - <skip /> + <string name="stk_cc_ss_to_dial_video" msgid="4306210904450719045">"SS അഭ്യർത്ഥന, വീഡിയോ DIAL അഭ്യർത്ഥനയായി പരിഷ്ക്കരിച്ചു."</string> <string name="stk_cc_ss_to_ussd" msgid="3951862188105305589">"SS അഭ്യർത്ഥന, USSD അഭ്യർത്ഥനയായി പരിഷ്ക്കരിച്ചു."</string> <string name="stk_cc_ss_to_ss" msgid="5470768854991452695">"SS അഭ്യർത്ഥന, പുതിയ SS അഭ്യർത്ഥനയായി പരിഷ്ക്കരിച്ചു."</string> <string name="notification_work_profile_content_description" msgid="4600554564103770764">"ഔദ്യോഗിക പ്രൊഫൈൽ"</string> diff --git a/core/res/res/values-mn/required_apps_managed_device.xml b/core/res/res/values-mn/required_apps_managed_device.xml new file mode 100644 index 000000000000..9044fccd88ee --- /dev/null +++ b/core/res/res/values-mn/required_apps_managed_device.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string-array name="required_apps_managed_device"> + <item msgid="1104492179978792509">"com.android.settings"</item> + <item msgid="7004798084799227194">"com.android.contacts"</item> + <item msgid="5782220690863647256">"com.android.dialer"</item> + <item msgid="5746338511138092673">"com.android.stk"</item> + <item msgid="1712599182168590664">"com.android.providers.downloads"</item> + <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item> + <item msgid="3892021562839042708">"com.android.documentsui"</item> + </string-array> +</resources> diff --git a/core/res/res/values-mn/required_apps_managed_profile.xml b/core/res/res/values-mn/required_apps_managed_profile.xml new file mode 100644 index 000000000000..4296b0d37dd2 --- /dev/null +++ b/core/res/res/values-mn/required_apps_managed_profile.xml @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string-array name="required_apps_managed_profile"> + <item msgid="1457364287544474838">"com.android.contacts"</item> + <item msgid="4633145750237794002">"com.android.settings"</item> + <item msgid="6518205098643077579">"com.android.providers.downloads"</item> + <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item> + <item msgid="6106837921940099371">"com.android.documentsui"</item> + </string-array> +</resources> diff --git a/core/res/res/values-mn/required_apps_managed_user.xml b/core/res/res/values-mn/required_apps_managed_user.xml new file mode 100644 index 000000000000..1a7ade9dd5f9 --- /dev/null +++ b/core/res/res/values-mn/required_apps_managed_user.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string-array name="required_apps_managed_user"> + <item msgid="4823915868435007499">"com.android.settings"</item> + <item msgid="2250259015310893915">"com.android.contacts"</item> + <item msgid="7166574999426592423">"com.android.dialer"</item> + <item msgid="7306937186458176744">"com.android.stk"</item> + <item msgid="7415441588151512455">"com.android.providers.downloads"</item> + <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item> + <item msgid="8640522622655589373">"com.android.documentsui"</item> + </string-array> +</resources> diff --git a/core/res/res/values-mr/required_apps_managed_device.xml b/core/res/res/values-mr/required_apps_managed_device.xml new file mode 100644 index 000000000000..9044fccd88ee --- /dev/null +++ b/core/res/res/values-mr/required_apps_managed_device.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string-array name="required_apps_managed_device"> + <item msgid="1104492179978792509">"com.android.settings"</item> + <item msgid="7004798084799227194">"com.android.contacts"</item> + <item msgid="5782220690863647256">"com.android.dialer"</item> + <item msgid="5746338511138092673">"com.android.stk"</item> + <item msgid="1712599182168590664">"com.android.providers.downloads"</item> + <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item> + <item msgid="3892021562839042708">"com.android.documentsui"</item> + </string-array> +</resources> diff --git a/core/res/res/values-mr/required_apps_managed_profile.xml b/core/res/res/values-mr/required_apps_managed_profile.xml new file mode 100644 index 000000000000..4296b0d37dd2 --- /dev/null +++ b/core/res/res/values-mr/required_apps_managed_profile.xml @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string-array name="required_apps_managed_profile"> + <item msgid="1457364287544474838">"com.android.contacts"</item> + <item msgid="4633145750237794002">"com.android.settings"</item> + <item msgid="6518205098643077579">"com.android.providers.downloads"</item> + <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item> + <item msgid="6106837921940099371">"com.android.documentsui"</item> + </string-array> +</resources> diff --git a/core/res/res/values-mr/required_apps_managed_user.xml b/core/res/res/values-mr/required_apps_managed_user.xml new file mode 100644 index 000000000000..1a7ade9dd5f9 --- /dev/null +++ b/core/res/res/values-mr/required_apps_managed_user.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string-array name="required_apps_managed_user"> + <item msgid="4823915868435007499">"com.android.settings"</item> + <item msgid="2250259015310893915">"com.android.contacts"</item> + <item msgid="7166574999426592423">"com.android.dialer"</item> + <item msgid="7306937186458176744">"com.android.stk"</item> + <item msgid="7415441588151512455">"com.android.providers.downloads"</item> + <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item> + <item msgid="8640522622655589373">"com.android.documentsui"</item> + </string-array> +</resources> diff --git a/core/res/res/values-ms/required_apps_managed_device.xml b/core/res/res/values-ms/required_apps_managed_device.xml new file mode 100644 index 000000000000..9044fccd88ee --- /dev/null +++ b/core/res/res/values-ms/required_apps_managed_device.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string-array name="required_apps_managed_device"> + <item msgid="1104492179978792509">"com.android.settings"</item> + <item msgid="7004798084799227194">"com.android.contacts"</item> + <item msgid="5782220690863647256">"com.android.dialer"</item> + <item msgid="5746338511138092673">"com.android.stk"</item> + <item msgid="1712599182168590664">"com.android.providers.downloads"</item> + <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item> + <item msgid="3892021562839042708">"com.android.documentsui"</item> + </string-array> +</resources> diff --git a/core/res/res/values-ms/required_apps_managed_profile.xml b/core/res/res/values-ms/required_apps_managed_profile.xml new file mode 100644 index 000000000000..4296b0d37dd2 --- /dev/null +++ b/core/res/res/values-ms/required_apps_managed_profile.xml @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string-array name="required_apps_managed_profile"> + <item msgid="1457364287544474838">"com.android.contacts"</item> + <item msgid="4633145750237794002">"com.android.settings"</item> + <item msgid="6518205098643077579">"com.android.providers.downloads"</item> + <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item> + <item msgid="6106837921940099371">"com.android.documentsui"</item> + </string-array> +</resources> diff --git a/core/res/res/values-ms/required_apps_managed_user.xml b/core/res/res/values-ms/required_apps_managed_user.xml new file mode 100644 index 000000000000..1a7ade9dd5f9 --- /dev/null +++ b/core/res/res/values-ms/required_apps_managed_user.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string-array name="required_apps_managed_user"> + <item msgid="4823915868435007499">"com.android.settings"</item> + <item msgid="2250259015310893915">"com.android.contacts"</item> + <item msgid="7166574999426592423">"com.android.dialer"</item> + <item msgid="7306937186458176744">"com.android.stk"</item> + <item msgid="7415441588151512455">"com.android.providers.downloads"</item> + <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item> + <item msgid="8640522622655589373">"com.android.documentsui"</item> + </string-array> +</resources> diff --git a/core/res/res/values-nb/required_apps_managed_device.xml b/core/res/res/values-nb/required_apps_managed_device.xml new file mode 100644 index 000000000000..9044fccd88ee --- /dev/null +++ b/core/res/res/values-nb/required_apps_managed_device.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string-array name="required_apps_managed_device"> + <item msgid="1104492179978792509">"com.android.settings"</item> + <item msgid="7004798084799227194">"com.android.contacts"</item> + <item msgid="5782220690863647256">"com.android.dialer"</item> + <item msgid="5746338511138092673">"com.android.stk"</item> + <item msgid="1712599182168590664">"com.android.providers.downloads"</item> + <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item> + <item msgid="3892021562839042708">"com.android.documentsui"</item> + </string-array> +</resources> diff --git a/core/res/res/values-nb/required_apps_managed_profile.xml b/core/res/res/values-nb/required_apps_managed_profile.xml new file mode 100644 index 000000000000..4296b0d37dd2 --- /dev/null +++ b/core/res/res/values-nb/required_apps_managed_profile.xml @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string-array name="required_apps_managed_profile"> + <item msgid="1457364287544474838">"com.android.contacts"</item> + <item msgid="4633145750237794002">"com.android.settings"</item> + <item msgid="6518205098643077579">"com.android.providers.downloads"</item> + <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item> + <item msgid="6106837921940099371">"com.android.documentsui"</item> + </string-array> +</resources> diff --git a/core/res/res/values-nb/required_apps_managed_user.xml b/core/res/res/values-nb/required_apps_managed_user.xml new file mode 100644 index 000000000000..1a7ade9dd5f9 --- /dev/null +++ b/core/res/res/values-nb/required_apps_managed_user.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string-array name="required_apps_managed_user"> + <item msgid="4823915868435007499">"com.android.settings"</item> + <item msgid="2250259015310893915">"com.android.contacts"</item> + <item msgid="7166574999426592423">"com.android.dialer"</item> + <item msgid="7306937186458176744">"com.android.stk"</item> + <item msgid="7415441588151512455">"com.android.providers.downloads"</item> + <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item> + <item msgid="8640522622655589373">"com.android.documentsui"</item> + </string-array> +</resources> diff --git a/core/res/res/values-ne/required_apps_managed_device.xml b/core/res/res/values-ne/required_apps_managed_device.xml new file mode 100644 index 000000000000..9044fccd88ee --- /dev/null +++ b/core/res/res/values-ne/required_apps_managed_device.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string-array name="required_apps_managed_device"> + <item msgid="1104492179978792509">"com.android.settings"</item> + <item msgid="7004798084799227194">"com.android.contacts"</item> + <item msgid="5782220690863647256">"com.android.dialer"</item> + <item msgid="5746338511138092673">"com.android.stk"</item> + <item msgid="1712599182168590664">"com.android.providers.downloads"</item> + <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item> + <item msgid="3892021562839042708">"com.android.documentsui"</item> + </string-array> +</resources> diff --git a/core/res/res/values-ne/required_apps_managed_profile.xml b/core/res/res/values-ne/required_apps_managed_profile.xml new file mode 100644 index 000000000000..4296b0d37dd2 --- /dev/null +++ b/core/res/res/values-ne/required_apps_managed_profile.xml @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string-array name="required_apps_managed_profile"> + <item msgid="1457364287544474838">"com.android.contacts"</item> + <item msgid="4633145750237794002">"com.android.settings"</item> + <item msgid="6518205098643077579">"com.android.providers.downloads"</item> + <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item> + <item msgid="6106837921940099371">"com.android.documentsui"</item> + </string-array> +</resources> diff --git a/core/res/res/values-ne/required_apps_managed_user.xml b/core/res/res/values-ne/required_apps_managed_user.xml new file mode 100644 index 000000000000..1a7ade9dd5f9 --- /dev/null +++ b/core/res/res/values-ne/required_apps_managed_user.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string-array name="required_apps_managed_user"> + <item msgid="4823915868435007499">"com.android.settings"</item> + <item msgid="2250259015310893915">"com.android.contacts"</item> + <item msgid="7166574999426592423">"com.android.dialer"</item> + <item msgid="7306937186458176744">"com.android.stk"</item> + <item msgid="7415441588151512455">"com.android.providers.downloads"</item> + <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item> + <item msgid="8640522622655589373">"com.android.documentsui"</item> + </string-array> +</resources> diff --git a/core/res/res/values-ne/strings.xml b/core/res/res/values-ne/strings.xml index 9223c7686fde..f0a45a32c750 100644 --- a/core/res/res/values-ne/strings.xml +++ b/core/res/res/values-ne/strings.xml @@ -1232,8 +1232,8 @@ <string name="ext_media_unmountable_notification_message" msgid="2343202057122495773">"<xliff:g id="NAME">%s</xliff:g> त्रुटिपूर्ण छ। समाधान गर्न ट्याप गर्नुहोस्।"</string> <string name="ext_media_unmountable_notification_message" product="tv" msgid="3941179940297874950">"<xliff:g id="NAME">%s</xliff:g> बिग्रेको छ। समाधान गर्न चयन गर्नुहोस्।"</string> <string name="ext_media_unsupported_notification_title" msgid="3797642322958803257">"असमर्थित <xliff:g id="NAME">%s</xliff:g>"</string> - <string name="ext_media_unsupported_notification_message" msgid="6121601473787888589">"यस यन्त्रले यस <xliff:g id="NAME">%s</xliff:g> लाई समर्थन गर्दैन। एक समर्थित ढाँचामा सेट अप गर्न ट्याप गर्नुहोस्।"</string> - <string name="ext_media_unsupported_notification_message" product="tv" msgid="3725436899820390906">"यो यन्त्रले यस <xliff:g id="NAME">%s</xliff:g> लाई समर्थन गर्दैन। एक समर्थित ढाँचामा सेट अप गर्न चयन गर्नुहोस्।"</string> + <string name="ext_media_unsupported_notification_message" msgid="6121601473787888589">"यस यन्त्रले यस <xliff:g id="NAME">%s</xliff:g> लाई समर्थन गर्दैन। एक समर्थित ढाँचामा सेटअप गर्न ट्याप गर्नुहोस्।"</string> + <string name="ext_media_unsupported_notification_message" product="tv" msgid="3725436899820390906">"यो यन्त्रले यस <xliff:g id="NAME">%s</xliff:g> लाई समर्थन गर्दैन। एक समर्थित ढाँचामा सेटअप गर्न चयन गर्नुहोस्।"</string> <string name="ext_media_badremoval_notification_title" msgid="3206248947375505416">"<xliff:g id="NAME">%s</xliff:g> अप्रत्याशित रूपमा निकालियो"</string> <string name="ext_media_badremoval_notification_message" msgid="380176703346946313">"डेटा हराउनबाट जोगाउन निकाल्नु अघि <xliff:g id="NAME">%s</xliff:g> अनमाउन्ट गर्नुहोस्"</string> <string name="ext_media_nomedia_notification_title" msgid="1704840188641749091">"निकालियो <xliff:g id="NAME">%s</xliff:g>"</string> @@ -1318,7 +1318,7 @@ <string name="car_mode_disable_notification_title" msgid="3164768212003864316">"कार मोड सक्षम पारियो।"</string> <string name="car_mode_disable_notification_message" msgid="6301524980144350051">"कार मोडबाट बाहिर निस्कन ट्याप गर्नुहोस्।"</string> <string name="tethered_notification_title" msgid="3146694234398202601">"टेथर गर्ने वा हटस्पट सक्रिय"</string> - <string name="tethered_notification_message" msgid="2113628520792055377">"सेट अप गर्न ट्याप गर्नुहोस्।"</string> + <string name="tethered_notification_message" msgid="2113628520792055377">"सेटअप गर्न ट्याप गर्नुहोस्।"</string> <string name="disable_tether_notification_title" msgid="7526977944111313195">"टेदरिङलाई असक्षम पारिएको छ"</string> <string name="disable_tether_notification_message" msgid="2913366428516852495">"विवरणहरूका लागि आफ्ना प्रशासकलाई सम्पर्क गर्नुहोस्"</string> <string name="back_button_label" msgid="2300470004503343439">"पछाडि"</string> diff --git a/core/res/res/values-pa/required_apps_managed_device.xml b/core/res/res/values-pa/required_apps_managed_device.xml new file mode 100644 index 000000000000..faadc5009347 --- /dev/null +++ b/core/res/res/values-pa/required_apps_managed_device.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string-array name="required_apps_managed_device"> + <item msgid="1104492179978792509">"com.android.settings"</item> + <item msgid="7004798084799227194">"com.android.contacts"</item> + <item msgid="5782220690863647256">"com.android.dialer"</item> + <item msgid="5746338511138092673">"com.android.stk"</item> + <item msgid="1712599182168590664">"com.android.providers.download"</item> + <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item> + <item msgid="3892021562839042708">"com.android.documentsui"</item> + </string-array> +</resources> diff --git a/core/res/res/values-pa/required_apps_managed_profile.xml b/core/res/res/values-pa/required_apps_managed_profile.xml new file mode 100644 index 000000000000..537a80ceadd4 --- /dev/null +++ b/core/res/res/values-pa/required_apps_managed_profile.xml @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string-array name="required_apps_managed_profile"> + <item msgid="1457364287544474838">"com.android.contacts"</item> + <item msgid="4633145750237794002">"com.android.settings"</item> + <item msgid="6518205098643077579">"com.android.providers.download"</item> + <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item> + <item msgid="6106837921940099371">"com.android.documentsui"</item> + </string-array> +</resources> diff --git a/core/res/res/values-pa/required_apps_managed_user.xml b/core/res/res/values-pa/required_apps_managed_user.xml new file mode 100644 index 000000000000..e69bbbcd5eca --- /dev/null +++ b/core/res/res/values-pa/required_apps_managed_user.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string-array name="required_apps_managed_user"> + <item msgid="4823915868435007499">"com.android.settings"</item> + <item msgid="2250259015310893915">"com.android.contacts"</item> + <item msgid="7166574999426592423">"com.android.dialer"</item> + <item msgid="7306937186458176744">"com.android.stk"</item> + <item msgid="7415441588151512455">"com.android.providers.download"</item> + <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item> + <item msgid="8640522622655589373">"com.android.documentsui"</item> + </string-array> +</resources> diff --git a/core/res/res/values-pa/strings.xml b/core/res/res/values-pa/strings.xml index 283701e4ef53..b5b6a00ba920 100644 --- a/core/res/res/values-pa/strings.xml +++ b/core/res/res/values-pa/strings.xml @@ -1695,11 +1695,9 @@ <string name="stk_cc_ussd_to_dial" msgid="5202342984749947872">"USSD ਬੇਨਤੀ DIAL ਬੇਨਤੀ ਵਿੱਚ ਸੰਸ਼ੋਧਿਤ ਕੀਤੀ ਗਈ ਹੈ।"</string> <string name="stk_cc_ussd_to_ss" msgid="2345360594181405482">"USSD ਬੇਨਤੀ SS ਬੇਨਤੀ ਵਿੱਚ ਸੰਸ਼ੋਧਿਤ ਕੀਤੀ ਗਈ ਹੈ।"</string> <string name="stk_cc_ussd_to_ussd" msgid="7466087659967191653">"USSD ਬੇਨਤੀ ਨਵੀਂ USSD ਬੇਨਤੀ ਵਿੱਚ ਸੰਸ਼ੋਧਿਤ ਕੀਤੀ ਗਈ ਹੈ।"</string> - <!-- no translation found for stk_cc_ussd_to_dial_video (585340552561515305) --> - <skip /> + <string name="stk_cc_ussd_to_dial_video" msgid="585340552561515305">"USSD ਬੇਨਤੀ ਨੂੰ ਸੋਧ ਕੇ ਵੀਡੀਓ ਡਾਇਲ ਬੇਨਤੀ ਵਿੱਚ ਬਦਲਿਆ ਗਿਆ।"</string> <string name="stk_cc_ss_to_dial" msgid="2151304435775557162">"SS ਬੇਨਤੀ DIAL ਬੇਨਤੀ ਵਿੱਚ ਸੰਸ਼ੋਧਿਤ ਕੀਤੀ ਗਈ ਹੈ।"</string> - <!-- no translation found for stk_cc_ss_to_dial_video (4306210904450719045) --> - <skip /> + <string name="stk_cc_ss_to_dial_video" msgid="4306210904450719045">"SS ਬੇਨਤੀ ਨੂੰ ਸੋਧ ਕੇ ਵੀਡੀਓ ਡਾਇਲ ਬੇਨਤੀ ਵਿੱਚ ਬਦਲਿਆ ਗਿਆ।"</string> <string name="stk_cc_ss_to_ussd" msgid="3951862188105305589">"SS ਬੇਨਤੀ USSD ਬੇਨਤੀ ਵਿੱਚ ਸੰਸ਼ੋਧਿਤ ਕੀਤੀ ਗਈ ਹੈ।"</string> <string name="stk_cc_ss_to_ss" msgid="5470768854991452695">"SS ਬੇਨਤੀ ਨਵੀਂ SS ਵਿੱਚ ਸੰਸ਼ੋਧਿਤ ਕੀਤੀ ਗਈ ਹੈ।"</string> <string name="notification_work_profile_content_description" msgid="4600554564103770764">"ਕਾਰਜ ਪ੍ਰੋਫਾਈਲ"</string> diff --git a/core/res/res/values-pl/required_apps_managed_device.xml b/core/res/res/values-pl/required_apps_managed_device.xml new file mode 100644 index 000000000000..9044fccd88ee --- /dev/null +++ b/core/res/res/values-pl/required_apps_managed_device.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string-array name="required_apps_managed_device"> + <item msgid="1104492179978792509">"com.android.settings"</item> + <item msgid="7004798084799227194">"com.android.contacts"</item> + <item msgid="5782220690863647256">"com.android.dialer"</item> + <item msgid="5746338511138092673">"com.android.stk"</item> + <item msgid="1712599182168590664">"com.android.providers.downloads"</item> + <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item> + <item msgid="3892021562839042708">"com.android.documentsui"</item> + </string-array> +</resources> diff --git a/core/res/res/values-pl/required_apps_managed_profile.xml b/core/res/res/values-pl/required_apps_managed_profile.xml new file mode 100644 index 000000000000..4296b0d37dd2 --- /dev/null +++ b/core/res/res/values-pl/required_apps_managed_profile.xml @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string-array name="required_apps_managed_profile"> + <item msgid="1457364287544474838">"com.android.contacts"</item> + <item msgid="4633145750237794002">"com.android.settings"</item> + <item msgid="6518205098643077579">"com.android.providers.downloads"</item> + <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item> + <item msgid="6106837921940099371">"com.android.documentsui"</item> + </string-array> +</resources> diff --git a/core/res/res/values-pl/required_apps_managed_user.xml b/core/res/res/values-pl/required_apps_managed_user.xml new file mode 100644 index 000000000000..1a7ade9dd5f9 --- /dev/null +++ b/core/res/res/values-pl/required_apps_managed_user.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string-array name="required_apps_managed_user"> + <item msgid="4823915868435007499">"com.android.settings"</item> + <item msgid="2250259015310893915">"com.android.contacts"</item> + <item msgid="7166574999426592423">"com.android.dialer"</item> + <item msgid="7306937186458176744">"com.android.stk"</item> + <item msgid="7415441588151512455">"com.android.providers.downloads"</item> + <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item> + <item msgid="8640522622655589373">"com.android.documentsui"</item> + </string-array> +</resources> diff --git a/core/res/res/values-pt-rPT/required_apps_managed_device.xml b/core/res/res/values-pt-rPT/required_apps_managed_device.xml new file mode 100644 index 000000000000..9044fccd88ee --- /dev/null +++ b/core/res/res/values-pt-rPT/required_apps_managed_device.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string-array name="required_apps_managed_device"> + <item msgid="1104492179978792509">"com.android.settings"</item> + <item msgid="7004798084799227194">"com.android.contacts"</item> + <item msgid="5782220690863647256">"com.android.dialer"</item> + <item msgid="5746338511138092673">"com.android.stk"</item> + <item msgid="1712599182168590664">"com.android.providers.downloads"</item> + <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item> + <item msgid="3892021562839042708">"com.android.documentsui"</item> + </string-array> +</resources> diff --git a/core/res/res/values-pt-rPT/required_apps_managed_profile.xml b/core/res/res/values-pt-rPT/required_apps_managed_profile.xml new file mode 100644 index 000000000000..4296b0d37dd2 --- /dev/null +++ b/core/res/res/values-pt-rPT/required_apps_managed_profile.xml @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string-array name="required_apps_managed_profile"> + <item msgid="1457364287544474838">"com.android.contacts"</item> + <item msgid="4633145750237794002">"com.android.settings"</item> + <item msgid="6518205098643077579">"com.android.providers.downloads"</item> + <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item> + <item msgid="6106837921940099371">"com.android.documentsui"</item> + </string-array> +</resources> diff --git a/core/res/res/values-pt-rPT/required_apps_managed_user.xml b/core/res/res/values-pt-rPT/required_apps_managed_user.xml new file mode 100644 index 000000000000..1a7ade9dd5f9 --- /dev/null +++ b/core/res/res/values-pt-rPT/required_apps_managed_user.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string-array name="required_apps_managed_user"> + <item msgid="4823915868435007499">"com.android.settings"</item> + <item msgid="2250259015310893915">"com.android.contacts"</item> + <item msgid="7166574999426592423">"com.android.dialer"</item> + <item msgid="7306937186458176744">"com.android.stk"</item> + <item msgid="7415441588151512455">"com.android.providers.downloads"</item> + <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item> + <item msgid="8640522622655589373">"com.android.documentsui"</item> + </string-array> +</resources> diff --git a/core/res/res/values-pt-rPT/strings.xml b/core/res/res/values-pt-rPT/strings.xml index 434b50b99800..dc2d6e451c85 100644 --- a/core/res/res/values-pt-rPT/strings.xml +++ b/core/res/res/values-pt-rPT/strings.xml @@ -381,7 +381,7 @@ <string name="permdesc_readCalendar" product="tablet" msgid="4993979255403945892">"Esta aplicação pode ler todos os eventos do calendário armazenados no seu tablet e partilhar ou guardar os dados do calendário."</string> <string name="permdesc_readCalendar" product="tv" msgid="8837931557573064315">"Esta aplicação pode ler todos os eventos do calendário armazenados na sua TV e partilhar ou guardar os dados do calendário."</string> <string name="permdesc_readCalendar" product="default" msgid="4373978642145196715">"Esta aplicação pode ler todos os eventos do calendário armazenados no seu telemóvel e partilhar ou guardar os dados do calendário."</string> - <string name="permlab_writeCalendar" msgid="8438874755193825647">"adicionar ou modificar eventos do calendário e enviar e-mail a convidados sem o conhecimento dos proprietários"</string> + <string name="permlab_writeCalendar" msgid="8438874755193825647">"adicionar ou modificar eventos do calendário e enviar email a convidados sem o conhecimento dos proprietários"</string> <string name="permdesc_writeCalendar" product="tablet" msgid="1675270619903625982">"Esta aplicação pode adicionar, remover ou alterar eventos do calendário no seu tablet. Esta aplicação pode enviar mensagens que parecem vir de proprietários do calendário ou alterar eventos sem notificar os respetivos proprietários."</string> <string name="permdesc_writeCalendar" product="tv" msgid="9017809326268135866">"Esta aplicação pode adicionar, remover ou alterar eventos do calendário na sua TV. Esta aplicação pode enviar mensagens que parecem vir de proprietários do calendário ou alterar eventos sem notificar os respetivos proprietários."</string> <string name="permdesc_writeCalendar" product="default" msgid="7592791790516943173">"Esta aplicação pode adicionar, remover ou alterar eventos do calendário no seu telemóvel. Esta aplicação pode enviar mensagens que parecem vir de proprietários do calendário ou alterar eventos sem notificar os respetivos proprietários."</string> @@ -756,7 +756,7 @@ <string name="lockscreen_glogin_forgot_pattern" msgid="2588521501166032747">"Desbloqueio da conta"</string> <string name="lockscreen_glogin_too_many_attempts" msgid="2751368605287288808">"Demasiadas tentativas para desenhar sequência"</string> <string name="lockscreen_glogin_instructions" msgid="3931816256100707784">"Para desbloquear, inicie sessão com a Conta Google."</string> - <string name="lockscreen_glogin_username_hint" msgid="8846881424106484447">"Nome de utilizador (e-mail)"</string> + <string name="lockscreen_glogin_username_hint" msgid="8846881424106484447">"Nome de utilizador (email)"</string> <string name="lockscreen_glogin_password_hint" msgid="5958028383954738528">"Palavra-passe"</string> <string name="lockscreen_glogin_submit_button" msgid="7130893694795786300">"Iniciar sessão"</string> <string name="lockscreen_glogin_invalid_input" msgid="1364051473347485908">"Nome de utilizador ou palavra-passe inválidos."</string> diff --git a/core/res/res/values-ro/required_apps_managed_device.xml b/core/res/res/values-ro/required_apps_managed_device.xml new file mode 100644 index 000000000000..9044fccd88ee --- /dev/null +++ b/core/res/res/values-ro/required_apps_managed_device.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string-array name="required_apps_managed_device"> + <item msgid="1104492179978792509">"com.android.settings"</item> + <item msgid="7004798084799227194">"com.android.contacts"</item> + <item msgid="5782220690863647256">"com.android.dialer"</item> + <item msgid="5746338511138092673">"com.android.stk"</item> + <item msgid="1712599182168590664">"com.android.providers.downloads"</item> + <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item> + <item msgid="3892021562839042708">"com.android.documentsui"</item> + </string-array> +</resources> diff --git a/core/res/res/values-ro/required_apps_managed_profile.xml b/core/res/res/values-ro/required_apps_managed_profile.xml new file mode 100644 index 000000000000..4296b0d37dd2 --- /dev/null +++ b/core/res/res/values-ro/required_apps_managed_profile.xml @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string-array name="required_apps_managed_profile"> + <item msgid="1457364287544474838">"com.android.contacts"</item> + <item msgid="4633145750237794002">"com.android.settings"</item> + <item msgid="6518205098643077579">"com.android.providers.downloads"</item> + <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item> + <item msgid="6106837921940099371">"com.android.documentsui"</item> + </string-array> +</resources> diff --git a/core/res/res/values-ro/required_apps_managed_user.xml b/core/res/res/values-ro/required_apps_managed_user.xml new file mode 100644 index 000000000000..1a7ade9dd5f9 --- /dev/null +++ b/core/res/res/values-ro/required_apps_managed_user.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string-array name="required_apps_managed_user"> + <item msgid="4823915868435007499">"com.android.settings"</item> + <item msgid="2250259015310893915">"com.android.contacts"</item> + <item msgid="7166574999426592423">"com.android.dialer"</item> + <item msgid="7306937186458176744">"com.android.stk"</item> + <item msgid="7415441588151512455">"com.android.providers.downloads"</item> + <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item> + <item msgid="8640522622655589373">"com.android.documentsui"</item> + </string-array> +</resources> diff --git a/core/res/res/values-sk/required_apps_managed_device.xml b/core/res/res/values-sk/required_apps_managed_device.xml new file mode 100644 index 000000000000..9044fccd88ee --- /dev/null +++ b/core/res/res/values-sk/required_apps_managed_device.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string-array name="required_apps_managed_device"> + <item msgid="1104492179978792509">"com.android.settings"</item> + <item msgid="7004798084799227194">"com.android.contacts"</item> + <item msgid="5782220690863647256">"com.android.dialer"</item> + <item msgid="5746338511138092673">"com.android.stk"</item> + <item msgid="1712599182168590664">"com.android.providers.downloads"</item> + <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item> + <item msgid="3892021562839042708">"com.android.documentsui"</item> + </string-array> +</resources> diff --git a/core/res/res/values-sk/required_apps_managed_profile.xml b/core/res/res/values-sk/required_apps_managed_profile.xml new file mode 100644 index 000000000000..4296b0d37dd2 --- /dev/null +++ b/core/res/res/values-sk/required_apps_managed_profile.xml @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string-array name="required_apps_managed_profile"> + <item msgid="1457364287544474838">"com.android.contacts"</item> + <item msgid="4633145750237794002">"com.android.settings"</item> + <item msgid="6518205098643077579">"com.android.providers.downloads"</item> + <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item> + <item msgid="6106837921940099371">"com.android.documentsui"</item> + </string-array> +</resources> diff --git a/core/res/res/values-sk/required_apps_managed_user.xml b/core/res/res/values-sk/required_apps_managed_user.xml new file mode 100644 index 000000000000..1a7ade9dd5f9 --- /dev/null +++ b/core/res/res/values-sk/required_apps_managed_user.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string-array name="required_apps_managed_user"> + <item msgid="4823915868435007499">"com.android.settings"</item> + <item msgid="2250259015310893915">"com.android.contacts"</item> + <item msgid="7166574999426592423">"com.android.dialer"</item> + <item msgid="7306937186458176744">"com.android.stk"</item> + <item msgid="7415441588151512455">"com.android.providers.downloads"</item> + <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item> + <item msgid="8640522622655589373">"com.android.documentsui"</item> + </string-array> +</resources> diff --git a/core/res/res/values-sl/required_apps_managed_device.xml b/core/res/res/values-sl/required_apps_managed_device.xml new file mode 100644 index 000000000000..9044fccd88ee --- /dev/null +++ b/core/res/res/values-sl/required_apps_managed_device.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string-array name="required_apps_managed_device"> + <item msgid="1104492179978792509">"com.android.settings"</item> + <item msgid="7004798084799227194">"com.android.contacts"</item> + <item msgid="5782220690863647256">"com.android.dialer"</item> + <item msgid="5746338511138092673">"com.android.stk"</item> + <item msgid="1712599182168590664">"com.android.providers.downloads"</item> + <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item> + <item msgid="3892021562839042708">"com.android.documentsui"</item> + </string-array> +</resources> diff --git a/core/res/res/values-sl/required_apps_managed_profile.xml b/core/res/res/values-sl/required_apps_managed_profile.xml new file mode 100644 index 000000000000..4296b0d37dd2 --- /dev/null +++ b/core/res/res/values-sl/required_apps_managed_profile.xml @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string-array name="required_apps_managed_profile"> + <item msgid="1457364287544474838">"com.android.contacts"</item> + <item msgid="4633145750237794002">"com.android.settings"</item> + <item msgid="6518205098643077579">"com.android.providers.downloads"</item> + <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item> + <item msgid="6106837921940099371">"com.android.documentsui"</item> + </string-array> +</resources> diff --git a/core/res/res/values-sl/required_apps_managed_user.xml b/core/res/res/values-sl/required_apps_managed_user.xml new file mode 100644 index 000000000000..1a7ade9dd5f9 --- /dev/null +++ b/core/res/res/values-sl/required_apps_managed_user.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string-array name="required_apps_managed_user"> + <item msgid="4823915868435007499">"com.android.settings"</item> + <item msgid="2250259015310893915">"com.android.contacts"</item> + <item msgid="7166574999426592423">"com.android.dialer"</item> + <item msgid="7306937186458176744">"com.android.stk"</item> + <item msgid="7415441588151512455">"com.android.providers.downloads"</item> + <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item> + <item msgid="8640522622655589373">"com.android.documentsui"</item> + </string-array> +</resources> diff --git a/core/res/res/values-sq/required_apps_managed_device.xml b/core/res/res/values-sq/required_apps_managed_device.xml new file mode 100644 index 000000000000..9044fccd88ee --- /dev/null +++ b/core/res/res/values-sq/required_apps_managed_device.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string-array name="required_apps_managed_device"> + <item msgid="1104492179978792509">"com.android.settings"</item> + <item msgid="7004798084799227194">"com.android.contacts"</item> + <item msgid="5782220690863647256">"com.android.dialer"</item> + <item msgid="5746338511138092673">"com.android.stk"</item> + <item msgid="1712599182168590664">"com.android.providers.downloads"</item> + <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item> + <item msgid="3892021562839042708">"com.android.documentsui"</item> + </string-array> +</resources> diff --git a/core/res/res/values-sq/required_apps_managed_profile.xml b/core/res/res/values-sq/required_apps_managed_profile.xml new file mode 100644 index 000000000000..4296b0d37dd2 --- /dev/null +++ b/core/res/res/values-sq/required_apps_managed_profile.xml @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string-array name="required_apps_managed_profile"> + <item msgid="1457364287544474838">"com.android.contacts"</item> + <item msgid="4633145750237794002">"com.android.settings"</item> + <item msgid="6518205098643077579">"com.android.providers.downloads"</item> + <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item> + <item msgid="6106837921940099371">"com.android.documentsui"</item> + </string-array> +</resources> diff --git a/core/res/res/values-sq/required_apps_managed_user.xml b/core/res/res/values-sq/required_apps_managed_user.xml new file mode 100644 index 000000000000..1a7ade9dd5f9 --- /dev/null +++ b/core/res/res/values-sq/required_apps_managed_user.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string-array name="required_apps_managed_user"> + <item msgid="4823915868435007499">"com.android.settings"</item> + <item msgid="2250259015310893915">"com.android.contacts"</item> + <item msgid="7166574999426592423">"com.android.dialer"</item> + <item msgid="7306937186458176744">"com.android.stk"</item> + <item msgid="7415441588151512455">"com.android.providers.downloads"</item> + <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item> + <item msgid="8640522622655589373">"com.android.documentsui"</item> + </string-array> +</resources> diff --git a/core/res/res/values-sr/required_apps_managed_device.xml b/core/res/res/values-sr/required_apps_managed_device.xml new file mode 100644 index 000000000000..9044fccd88ee --- /dev/null +++ b/core/res/res/values-sr/required_apps_managed_device.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string-array name="required_apps_managed_device"> + <item msgid="1104492179978792509">"com.android.settings"</item> + <item msgid="7004798084799227194">"com.android.contacts"</item> + <item msgid="5782220690863647256">"com.android.dialer"</item> + <item msgid="5746338511138092673">"com.android.stk"</item> + <item msgid="1712599182168590664">"com.android.providers.downloads"</item> + <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item> + <item msgid="3892021562839042708">"com.android.documentsui"</item> + </string-array> +</resources> diff --git a/core/res/res/values-sr/required_apps_managed_profile.xml b/core/res/res/values-sr/required_apps_managed_profile.xml new file mode 100644 index 000000000000..4296b0d37dd2 --- /dev/null +++ b/core/res/res/values-sr/required_apps_managed_profile.xml @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string-array name="required_apps_managed_profile"> + <item msgid="1457364287544474838">"com.android.contacts"</item> + <item msgid="4633145750237794002">"com.android.settings"</item> + <item msgid="6518205098643077579">"com.android.providers.downloads"</item> + <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item> + <item msgid="6106837921940099371">"com.android.documentsui"</item> + </string-array> +</resources> diff --git a/core/res/res/values-sr/required_apps_managed_user.xml b/core/res/res/values-sr/required_apps_managed_user.xml new file mode 100644 index 000000000000..1a7ade9dd5f9 --- /dev/null +++ b/core/res/res/values-sr/required_apps_managed_user.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string-array name="required_apps_managed_user"> + <item msgid="4823915868435007499">"com.android.settings"</item> + <item msgid="2250259015310893915">"com.android.contacts"</item> + <item msgid="7166574999426592423">"com.android.dialer"</item> + <item msgid="7306937186458176744">"com.android.stk"</item> + <item msgid="7415441588151512455">"com.android.providers.downloads"</item> + <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item> + <item msgid="8640522622655589373">"com.android.documentsui"</item> + </string-array> +</resources> diff --git a/core/res/res/values-sv/required_apps_managed_device.xml b/core/res/res/values-sv/required_apps_managed_device.xml new file mode 100644 index 000000000000..9044fccd88ee --- /dev/null +++ b/core/res/res/values-sv/required_apps_managed_device.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string-array name="required_apps_managed_device"> + <item msgid="1104492179978792509">"com.android.settings"</item> + <item msgid="7004798084799227194">"com.android.contacts"</item> + <item msgid="5782220690863647256">"com.android.dialer"</item> + <item msgid="5746338511138092673">"com.android.stk"</item> + <item msgid="1712599182168590664">"com.android.providers.downloads"</item> + <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item> + <item msgid="3892021562839042708">"com.android.documentsui"</item> + </string-array> +</resources> diff --git a/core/res/res/values-sv/required_apps_managed_profile.xml b/core/res/res/values-sv/required_apps_managed_profile.xml new file mode 100644 index 000000000000..4296b0d37dd2 --- /dev/null +++ b/core/res/res/values-sv/required_apps_managed_profile.xml @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string-array name="required_apps_managed_profile"> + <item msgid="1457364287544474838">"com.android.contacts"</item> + <item msgid="4633145750237794002">"com.android.settings"</item> + <item msgid="6518205098643077579">"com.android.providers.downloads"</item> + <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item> + <item msgid="6106837921940099371">"com.android.documentsui"</item> + </string-array> +</resources> diff --git a/core/res/res/values-sv/required_apps_managed_user.xml b/core/res/res/values-sv/required_apps_managed_user.xml new file mode 100644 index 000000000000..1a7ade9dd5f9 --- /dev/null +++ b/core/res/res/values-sv/required_apps_managed_user.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string-array name="required_apps_managed_user"> + <item msgid="4823915868435007499">"com.android.settings"</item> + <item msgid="2250259015310893915">"com.android.contacts"</item> + <item msgid="7166574999426592423">"com.android.dialer"</item> + <item msgid="7306937186458176744">"com.android.stk"</item> + <item msgid="7415441588151512455">"com.android.providers.downloads"</item> + <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item> + <item msgid="8640522622655589373">"com.android.documentsui"</item> + </string-array> +</resources> diff --git a/core/res/res/values-sw/required_apps_managed_device.xml b/core/res/res/values-sw/required_apps_managed_device.xml new file mode 100644 index 000000000000..9044fccd88ee --- /dev/null +++ b/core/res/res/values-sw/required_apps_managed_device.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string-array name="required_apps_managed_device"> + <item msgid="1104492179978792509">"com.android.settings"</item> + <item msgid="7004798084799227194">"com.android.contacts"</item> + <item msgid="5782220690863647256">"com.android.dialer"</item> + <item msgid="5746338511138092673">"com.android.stk"</item> + <item msgid="1712599182168590664">"com.android.providers.downloads"</item> + <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item> + <item msgid="3892021562839042708">"com.android.documentsui"</item> + </string-array> +</resources> diff --git a/core/res/res/values-sw/required_apps_managed_profile.xml b/core/res/res/values-sw/required_apps_managed_profile.xml new file mode 100644 index 000000000000..4296b0d37dd2 --- /dev/null +++ b/core/res/res/values-sw/required_apps_managed_profile.xml @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string-array name="required_apps_managed_profile"> + <item msgid="1457364287544474838">"com.android.contacts"</item> + <item msgid="4633145750237794002">"com.android.settings"</item> + <item msgid="6518205098643077579">"com.android.providers.downloads"</item> + <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item> + <item msgid="6106837921940099371">"com.android.documentsui"</item> + </string-array> +</resources> diff --git a/core/res/res/values-sw/required_apps_managed_user.xml b/core/res/res/values-sw/required_apps_managed_user.xml new file mode 100644 index 000000000000..1a7ade9dd5f9 --- /dev/null +++ b/core/res/res/values-sw/required_apps_managed_user.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string-array name="required_apps_managed_user"> + <item msgid="4823915868435007499">"com.android.settings"</item> + <item msgid="2250259015310893915">"com.android.contacts"</item> + <item msgid="7166574999426592423">"com.android.dialer"</item> + <item msgid="7306937186458176744">"com.android.stk"</item> + <item msgid="7415441588151512455">"com.android.providers.downloads"</item> + <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item> + <item msgid="8640522622655589373">"com.android.documentsui"</item> + </string-array> +</resources> diff --git a/core/res/res/values-ta/required_apps_managed_device.xml b/core/res/res/values-ta/required_apps_managed_device.xml new file mode 100644 index 000000000000..9044fccd88ee --- /dev/null +++ b/core/res/res/values-ta/required_apps_managed_device.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string-array name="required_apps_managed_device"> + <item msgid="1104492179978792509">"com.android.settings"</item> + <item msgid="7004798084799227194">"com.android.contacts"</item> + <item msgid="5782220690863647256">"com.android.dialer"</item> + <item msgid="5746338511138092673">"com.android.stk"</item> + <item msgid="1712599182168590664">"com.android.providers.downloads"</item> + <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item> + <item msgid="3892021562839042708">"com.android.documentsui"</item> + </string-array> +</resources> diff --git a/core/res/res/values-ta/required_apps_managed_profile.xml b/core/res/res/values-ta/required_apps_managed_profile.xml new file mode 100644 index 000000000000..4296b0d37dd2 --- /dev/null +++ b/core/res/res/values-ta/required_apps_managed_profile.xml @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string-array name="required_apps_managed_profile"> + <item msgid="1457364287544474838">"com.android.contacts"</item> + <item msgid="4633145750237794002">"com.android.settings"</item> + <item msgid="6518205098643077579">"com.android.providers.downloads"</item> + <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item> + <item msgid="6106837921940099371">"com.android.documentsui"</item> + </string-array> +</resources> diff --git a/core/res/res/values-ta/required_apps_managed_user.xml b/core/res/res/values-ta/required_apps_managed_user.xml new file mode 100644 index 000000000000..1a7ade9dd5f9 --- /dev/null +++ b/core/res/res/values-ta/required_apps_managed_user.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string-array name="required_apps_managed_user"> + <item msgid="4823915868435007499">"com.android.settings"</item> + <item msgid="2250259015310893915">"com.android.contacts"</item> + <item msgid="7166574999426592423">"com.android.dialer"</item> + <item msgid="7306937186458176744">"com.android.stk"</item> + <item msgid="7415441588151512455">"com.android.providers.downloads"</item> + <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item> + <item msgid="8640522622655589373">"com.android.documentsui"</item> + </string-array> +</resources> diff --git a/core/res/res/values-ta/strings.xml b/core/res/res/values-ta/strings.xml index c1526f3fef53..c517223fcea6 100644 --- a/core/res/res/values-ta/strings.xml +++ b/core/res/res/values-ta/strings.xml @@ -1695,11 +1695,9 @@ <string name="stk_cc_ussd_to_dial" msgid="5202342984749947872">"USSD கோரிக்கையானது DIAL கோரிக்கைக்கு மாற்றப்பட்டது."</string> <string name="stk_cc_ussd_to_ss" msgid="2345360594181405482">"USSD கோரிக்கையானது SS கோரிக்கைக்கு மாற்றப்பட்டது."</string> <string name="stk_cc_ussd_to_ussd" msgid="7466087659967191653">"USSD கோரிக்கையானது புதிய USSD கோரிக்கைக்கு மாற்றப்பட்டது."</string> - <!-- no translation found for stk_cc_ussd_to_dial_video (585340552561515305) --> - <skip /> + <string name="stk_cc_ussd_to_dial_video" msgid="585340552561515305">"USSD கோரிக்கையானது வீடியோ DIAL கோரிக்கைக்கு மாற்றப்பட்டது."</string> <string name="stk_cc_ss_to_dial" msgid="2151304435775557162">"SS கோரிக்கையானது DIAL கோரிக்கைக்கு மாற்றப்பட்டது."</string> - <!-- no translation found for stk_cc_ss_to_dial_video (4306210904450719045) --> - <skip /> + <string name="stk_cc_ss_to_dial_video" msgid="4306210904450719045">"SS கோரிக்கையானது வீடியோ DIAL கோரிக்கைக்கு மாற்றப்பட்டது."</string> <string name="stk_cc_ss_to_ussd" msgid="3951862188105305589">"SS கோரிக்கையானது USSD கோரிக்கைக்கு மாற்றப்பட்டது."</string> <string name="stk_cc_ss_to_ss" msgid="5470768854991452695">"SS கோரிக்கையானது புதிய SS கோரிக்கைக்கு மாற்றப்பட்டது."</string> <string name="notification_work_profile_content_description" msgid="4600554564103770764">"பணி சுயவிவரம்"</string> diff --git a/core/res/res/values-te/required_apps_managed_device.xml b/core/res/res/values-te/required_apps_managed_device.xml new file mode 100644 index 000000000000..9044fccd88ee --- /dev/null +++ b/core/res/res/values-te/required_apps_managed_device.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string-array name="required_apps_managed_device"> + <item msgid="1104492179978792509">"com.android.settings"</item> + <item msgid="7004798084799227194">"com.android.contacts"</item> + <item msgid="5782220690863647256">"com.android.dialer"</item> + <item msgid="5746338511138092673">"com.android.stk"</item> + <item msgid="1712599182168590664">"com.android.providers.downloads"</item> + <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item> + <item msgid="3892021562839042708">"com.android.documentsui"</item> + </string-array> +</resources> diff --git a/core/res/res/values-te/required_apps_managed_profile.xml b/core/res/res/values-te/required_apps_managed_profile.xml new file mode 100644 index 000000000000..4296b0d37dd2 --- /dev/null +++ b/core/res/res/values-te/required_apps_managed_profile.xml @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string-array name="required_apps_managed_profile"> + <item msgid="1457364287544474838">"com.android.contacts"</item> + <item msgid="4633145750237794002">"com.android.settings"</item> + <item msgid="6518205098643077579">"com.android.providers.downloads"</item> + <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item> + <item msgid="6106837921940099371">"com.android.documentsui"</item> + </string-array> +</resources> diff --git a/core/res/res/values-te/required_apps_managed_user.xml b/core/res/res/values-te/required_apps_managed_user.xml new file mode 100644 index 000000000000..1a7ade9dd5f9 --- /dev/null +++ b/core/res/res/values-te/required_apps_managed_user.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string-array name="required_apps_managed_user"> + <item msgid="4823915868435007499">"com.android.settings"</item> + <item msgid="2250259015310893915">"com.android.contacts"</item> + <item msgid="7166574999426592423">"com.android.dialer"</item> + <item msgid="7306937186458176744">"com.android.stk"</item> + <item msgid="7415441588151512455">"com.android.providers.downloads"</item> + <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item> + <item msgid="8640522622655589373">"com.android.documentsui"</item> + </string-array> +</resources> diff --git a/core/res/res/values-te/strings.xml b/core/res/res/values-te/strings.xml index a96bcaf1aeec..fd103bc9f8f4 100644 --- a/core/res/res/values-te/strings.xml +++ b/core/res/res/values-te/strings.xml @@ -1695,11 +1695,9 @@ <string name="stk_cc_ussd_to_dial" msgid="5202342984749947872">"USSD అభ్యర్థన డయల్ అభ్యర్థనగా సవరించబడింది."</string> <string name="stk_cc_ussd_to_ss" msgid="2345360594181405482">"USSD అభ్యర్థన SS అభ్యర్థనగా సవరించబడింది."</string> <string name="stk_cc_ussd_to_ussd" msgid="7466087659967191653">"USSD అభ్యర్థన కొత్త USSD అభ్యర్థనగా సవరించబడింది."</string> - <!-- no translation found for stk_cc_ussd_to_dial_video (585340552561515305) --> - <skip /> + <string name="stk_cc_ussd_to_dial_video" msgid="585340552561515305">"USSD అభ్యర్థన వీడియో డయల్ అభ్యర్థనగా సవరించబడింది."</string> <string name="stk_cc_ss_to_dial" msgid="2151304435775557162">"SS అభ్యర్థన డయల్ అభ్యర్థనగా సవరించబడింది."</string> - <!-- no translation found for stk_cc_ss_to_dial_video (4306210904450719045) --> - <skip /> + <string name="stk_cc_ss_to_dial_video" msgid="4306210904450719045">"SS అభ్యర్థన వీడియో డయల్ అభ్యర్థనగా సవరించబడింది."</string> <string name="stk_cc_ss_to_ussd" msgid="3951862188105305589">"SS అభ్యర్థన USSD అభ్యర్థనగా సవరించబడింది."</string> <string name="stk_cc_ss_to_ss" msgid="5470768854991452695">"SS అభ్యర్థన కొత్త SS అభ్యర్థనగా సవరించబడింది."</string> <string name="notification_work_profile_content_description" msgid="4600554564103770764">"కార్యాలయ ప్రొఫైల్"</string> diff --git a/core/res/res/values-th/required_apps_managed_device.xml b/core/res/res/values-th/required_apps_managed_device.xml new file mode 100644 index 000000000000..9044fccd88ee --- /dev/null +++ b/core/res/res/values-th/required_apps_managed_device.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string-array name="required_apps_managed_device"> + <item msgid="1104492179978792509">"com.android.settings"</item> + <item msgid="7004798084799227194">"com.android.contacts"</item> + <item msgid="5782220690863647256">"com.android.dialer"</item> + <item msgid="5746338511138092673">"com.android.stk"</item> + <item msgid="1712599182168590664">"com.android.providers.downloads"</item> + <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item> + <item msgid="3892021562839042708">"com.android.documentsui"</item> + </string-array> +</resources> diff --git a/core/res/res/values-th/required_apps_managed_profile.xml b/core/res/res/values-th/required_apps_managed_profile.xml new file mode 100644 index 000000000000..4296b0d37dd2 --- /dev/null +++ b/core/res/res/values-th/required_apps_managed_profile.xml @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string-array name="required_apps_managed_profile"> + <item msgid="1457364287544474838">"com.android.contacts"</item> + <item msgid="4633145750237794002">"com.android.settings"</item> + <item msgid="6518205098643077579">"com.android.providers.downloads"</item> + <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item> + <item msgid="6106837921940099371">"com.android.documentsui"</item> + </string-array> +</resources> diff --git a/core/res/res/values-th/required_apps_managed_user.xml b/core/res/res/values-th/required_apps_managed_user.xml new file mode 100644 index 000000000000..1a7ade9dd5f9 --- /dev/null +++ b/core/res/res/values-th/required_apps_managed_user.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string-array name="required_apps_managed_user"> + <item msgid="4823915868435007499">"com.android.settings"</item> + <item msgid="2250259015310893915">"com.android.contacts"</item> + <item msgid="7166574999426592423">"com.android.dialer"</item> + <item msgid="7306937186458176744">"com.android.stk"</item> + <item msgid="7415441588151512455">"com.android.providers.downloads"</item> + <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item> + <item msgid="8640522622655589373">"com.android.documentsui"</item> + </string-array> +</resources> diff --git a/core/res/res/values-tl/required_apps_managed_device.xml b/core/res/res/values-tl/required_apps_managed_device.xml new file mode 100644 index 000000000000..9044fccd88ee --- /dev/null +++ b/core/res/res/values-tl/required_apps_managed_device.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string-array name="required_apps_managed_device"> + <item msgid="1104492179978792509">"com.android.settings"</item> + <item msgid="7004798084799227194">"com.android.contacts"</item> + <item msgid="5782220690863647256">"com.android.dialer"</item> + <item msgid="5746338511138092673">"com.android.stk"</item> + <item msgid="1712599182168590664">"com.android.providers.downloads"</item> + <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item> + <item msgid="3892021562839042708">"com.android.documentsui"</item> + </string-array> +</resources> diff --git a/core/res/res/values-tl/required_apps_managed_profile.xml b/core/res/res/values-tl/required_apps_managed_profile.xml new file mode 100644 index 000000000000..4296b0d37dd2 --- /dev/null +++ b/core/res/res/values-tl/required_apps_managed_profile.xml @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string-array name="required_apps_managed_profile"> + <item msgid="1457364287544474838">"com.android.contacts"</item> + <item msgid="4633145750237794002">"com.android.settings"</item> + <item msgid="6518205098643077579">"com.android.providers.downloads"</item> + <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item> + <item msgid="6106837921940099371">"com.android.documentsui"</item> + </string-array> +</resources> diff --git a/core/res/res/values-tl/required_apps_managed_user.xml b/core/res/res/values-tl/required_apps_managed_user.xml new file mode 100644 index 000000000000..1a7ade9dd5f9 --- /dev/null +++ b/core/res/res/values-tl/required_apps_managed_user.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string-array name="required_apps_managed_user"> + <item msgid="4823915868435007499">"com.android.settings"</item> + <item msgid="2250259015310893915">"com.android.contacts"</item> + <item msgid="7166574999426592423">"com.android.dialer"</item> + <item msgid="7306937186458176744">"com.android.stk"</item> + <item msgid="7415441588151512455">"com.android.providers.downloads"</item> + <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item> + <item msgid="8640522622655589373">"com.android.documentsui"</item> + </string-array> +</resources> diff --git a/core/res/res/values-tr/required_apps_managed_device.xml b/core/res/res/values-tr/required_apps_managed_device.xml new file mode 100644 index 000000000000..9044fccd88ee --- /dev/null +++ b/core/res/res/values-tr/required_apps_managed_device.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string-array name="required_apps_managed_device"> + <item msgid="1104492179978792509">"com.android.settings"</item> + <item msgid="7004798084799227194">"com.android.contacts"</item> + <item msgid="5782220690863647256">"com.android.dialer"</item> + <item msgid="5746338511138092673">"com.android.stk"</item> + <item msgid="1712599182168590664">"com.android.providers.downloads"</item> + <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item> + <item msgid="3892021562839042708">"com.android.documentsui"</item> + </string-array> +</resources> diff --git a/core/res/res/values-tr/required_apps_managed_profile.xml b/core/res/res/values-tr/required_apps_managed_profile.xml new file mode 100644 index 000000000000..4296b0d37dd2 --- /dev/null +++ b/core/res/res/values-tr/required_apps_managed_profile.xml @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string-array name="required_apps_managed_profile"> + <item msgid="1457364287544474838">"com.android.contacts"</item> + <item msgid="4633145750237794002">"com.android.settings"</item> + <item msgid="6518205098643077579">"com.android.providers.downloads"</item> + <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item> + <item msgid="6106837921940099371">"com.android.documentsui"</item> + </string-array> +</resources> diff --git a/core/res/res/values-tr/required_apps_managed_user.xml b/core/res/res/values-tr/required_apps_managed_user.xml new file mode 100644 index 000000000000..1a7ade9dd5f9 --- /dev/null +++ b/core/res/res/values-tr/required_apps_managed_user.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string-array name="required_apps_managed_user"> + <item msgid="4823915868435007499">"com.android.settings"</item> + <item msgid="2250259015310893915">"com.android.contacts"</item> + <item msgid="7166574999426592423">"com.android.dialer"</item> + <item msgid="7306937186458176744">"com.android.stk"</item> + <item msgid="7415441588151512455">"com.android.providers.downloads"</item> + <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item> + <item msgid="8640522622655589373">"com.android.documentsui"</item> + </string-array> +</resources> diff --git a/core/res/res/values-ur/required_apps_managed_device.xml b/core/res/res/values-ur/required_apps_managed_device.xml new file mode 100644 index 000000000000..9044fccd88ee --- /dev/null +++ b/core/res/res/values-ur/required_apps_managed_device.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string-array name="required_apps_managed_device"> + <item msgid="1104492179978792509">"com.android.settings"</item> + <item msgid="7004798084799227194">"com.android.contacts"</item> + <item msgid="5782220690863647256">"com.android.dialer"</item> + <item msgid="5746338511138092673">"com.android.stk"</item> + <item msgid="1712599182168590664">"com.android.providers.downloads"</item> + <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item> + <item msgid="3892021562839042708">"com.android.documentsui"</item> + </string-array> +</resources> diff --git a/core/res/res/values-ur/required_apps_managed_profile.xml b/core/res/res/values-ur/required_apps_managed_profile.xml new file mode 100644 index 000000000000..4296b0d37dd2 --- /dev/null +++ b/core/res/res/values-ur/required_apps_managed_profile.xml @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string-array name="required_apps_managed_profile"> + <item msgid="1457364287544474838">"com.android.contacts"</item> + <item msgid="4633145750237794002">"com.android.settings"</item> + <item msgid="6518205098643077579">"com.android.providers.downloads"</item> + <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item> + <item msgid="6106837921940099371">"com.android.documentsui"</item> + </string-array> +</resources> diff --git a/core/res/res/values-ur/required_apps_managed_user.xml b/core/res/res/values-ur/required_apps_managed_user.xml new file mode 100644 index 000000000000..1a7ade9dd5f9 --- /dev/null +++ b/core/res/res/values-ur/required_apps_managed_user.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string-array name="required_apps_managed_user"> + <item msgid="4823915868435007499">"com.android.settings"</item> + <item msgid="2250259015310893915">"com.android.contacts"</item> + <item msgid="7166574999426592423">"com.android.dialer"</item> + <item msgid="7306937186458176744">"com.android.stk"</item> + <item msgid="7415441588151512455">"com.android.providers.downloads"</item> + <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item> + <item msgid="8640522622655589373">"com.android.documentsui"</item> + </string-array> +</resources> diff --git a/core/res/res/values-ur/strings.xml b/core/res/res/values-ur/strings.xml index 2155b34482c6..2771432dd56f 100644 --- a/core/res/res/values-ur/strings.xml +++ b/core/res/res/values-ur/strings.xml @@ -1695,11 +1695,9 @@ <string name="stk_cc_ussd_to_dial" msgid="5202342984749947872">"USSD درخواست میں ترمیم کر کے DIAL درخواست بنا دی گئی ہے۔"</string> <string name="stk_cc_ussd_to_ss" msgid="2345360594181405482">"USSD درخواست میں ترمیم کر کے SS درخواست بنا دی گئی ہے۔"</string> <string name="stk_cc_ussd_to_ussd" msgid="7466087659967191653">"USSD درخواست میں ترمیم کر کے نئی USSD درخواست بنا دی گئی ہے۔"</string> - <!-- no translation found for stk_cc_ussd_to_dial_video (585340552561515305) --> - <skip /> + <string name="stk_cc_ussd_to_dial_video" msgid="585340552561515305">"USSD درخواست کو ویڈیو DIAL درخواست میں تبدیل کر دیا گیا ہے۔"</string> <string name="stk_cc_ss_to_dial" msgid="2151304435775557162">"SS درخواست میں ترمیم کر کے DIAL درخواست بنا دی گئی ہے۔"</string> - <!-- no translation found for stk_cc_ss_to_dial_video (4306210904450719045) --> - <skip /> + <string name="stk_cc_ss_to_dial_video" msgid="4306210904450719045">"SS درخواست کو ویڈیو DIAL درخواست میں تبدیل کر دیا گیا ہے۔"</string> <string name="stk_cc_ss_to_ussd" msgid="3951862188105305589">"SS درخواست میں ترمیم کر کے USSD درخواست بنا دی گئی ہے۔"</string> <string name="stk_cc_ss_to_ss" msgid="5470768854991452695">"SS درخواست میں ترمیم کر کے نئی SS درخواست بنا دی گئی ہے۔"</string> <string name="notification_work_profile_content_description" msgid="4600554564103770764">"دفتری پروفائل"</string> diff --git a/core/res/res/values-uz/required_apps_managed_device.xml b/core/res/res/values-uz/required_apps_managed_device.xml new file mode 100644 index 000000000000..9044fccd88ee --- /dev/null +++ b/core/res/res/values-uz/required_apps_managed_device.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string-array name="required_apps_managed_device"> + <item msgid="1104492179978792509">"com.android.settings"</item> + <item msgid="7004798084799227194">"com.android.contacts"</item> + <item msgid="5782220690863647256">"com.android.dialer"</item> + <item msgid="5746338511138092673">"com.android.stk"</item> + <item msgid="1712599182168590664">"com.android.providers.downloads"</item> + <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item> + <item msgid="3892021562839042708">"com.android.documentsui"</item> + </string-array> +</resources> diff --git a/core/res/res/values-uz/required_apps_managed_profile.xml b/core/res/res/values-uz/required_apps_managed_profile.xml new file mode 100644 index 000000000000..4296b0d37dd2 --- /dev/null +++ b/core/res/res/values-uz/required_apps_managed_profile.xml @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string-array name="required_apps_managed_profile"> + <item msgid="1457364287544474838">"com.android.contacts"</item> + <item msgid="4633145750237794002">"com.android.settings"</item> + <item msgid="6518205098643077579">"com.android.providers.downloads"</item> + <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item> + <item msgid="6106837921940099371">"com.android.documentsui"</item> + </string-array> +</resources> diff --git a/core/res/res/values-uz/required_apps_managed_user.xml b/core/res/res/values-uz/required_apps_managed_user.xml new file mode 100644 index 000000000000..1a7ade9dd5f9 --- /dev/null +++ b/core/res/res/values-uz/required_apps_managed_user.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string-array name="required_apps_managed_user"> + <item msgid="4823915868435007499">"com.android.settings"</item> + <item msgid="2250259015310893915">"com.android.contacts"</item> + <item msgid="7166574999426592423">"com.android.dialer"</item> + <item msgid="7306937186458176744">"com.android.stk"</item> + <item msgid="7415441588151512455">"com.android.providers.downloads"</item> + <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item> + <item msgid="8640522622655589373">"com.android.documentsui"</item> + </string-array> +</resources> diff --git a/core/res/res/values-vi/required_apps_managed_device.xml b/core/res/res/values-vi/required_apps_managed_device.xml new file mode 100644 index 000000000000..9044fccd88ee --- /dev/null +++ b/core/res/res/values-vi/required_apps_managed_device.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string-array name="required_apps_managed_device"> + <item msgid="1104492179978792509">"com.android.settings"</item> + <item msgid="7004798084799227194">"com.android.contacts"</item> + <item msgid="5782220690863647256">"com.android.dialer"</item> + <item msgid="5746338511138092673">"com.android.stk"</item> + <item msgid="1712599182168590664">"com.android.providers.downloads"</item> + <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item> + <item msgid="3892021562839042708">"com.android.documentsui"</item> + </string-array> +</resources> diff --git a/core/res/res/values-vi/required_apps_managed_profile.xml b/core/res/res/values-vi/required_apps_managed_profile.xml new file mode 100644 index 000000000000..4296b0d37dd2 --- /dev/null +++ b/core/res/res/values-vi/required_apps_managed_profile.xml @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string-array name="required_apps_managed_profile"> + <item msgid="1457364287544474838">"com.android.contacts"</item> + <item msgid="4633145750237794002">"com.android.settings"</item> + <item msgid="6518205098643077579">"com.android.providers.downloads"</item> + <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item> + <item msgid="6106837921940099371">"com.android.documentsui"</item> + </string-array> +</resources> diff --git a/core/res/res/values-vi/required_apps_managed_user.xml b/core/res/res/values-vi/required_apps_managed_user.xml new file mode 100644 index 000000000000..1a7ade9dd5f9 --- /dev/null +++ b/core/res/res/values-vi/required_apps_managed_user.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string-array name="required_apps_managed_user"> + <item msgid="4823915868435007499">"com.android.settings"</item> + <item msgid="2250259015310893915">"com.android.contacts"</item> + <item msgid="7166574999426592423">"com.android.dialer"</item> + <item msgid="7306937186458176744">"com.android.stk"</item> + <item msgid="7415441588151512455">"com.android.providers.downloads"</item> + <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item> + <item msgid="8640522622655589373">"com.android.documentsui"</item> + </string-array> +</resources> diff --git a/core/res/res/values-zh-rCN/required_apps_managed_device.xml b/core/res/res/values-zh-rCN/required_apps_managed_device.xml new file mode 100644 index 000000000000..9044fccd88ee --- /dev/null +++ b/core/res/res/values-zh-rCN/required_apps_managed_device.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string-array name="required_apps_managed_device"> + <item msgid="1104492179978792509">"com.android.settings"</item> + <item msgid="7004798084799227194">"com.android.contacts"</item> + <item msgid="5782220690863647256">"com.android.dialer"</item> + <item msgid="5746338511138092673">"com.android.stk"</item> + <item msgid="1712599182168590664">"com.android.providers.downloads"</item> + <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item> + <item msgid="3892021562839042708">"com.android.documentsui"</item> + </string-array> +</resources> diff --git a/core/res/res/values-zh-rCN/required_apps_managed_profile.xml b/core/res/res/values-zh-rCN/required_apps_managed_profile.xml new file mode 100644 index 000000000000..4296b0d37dd2 --- /dev/null +++ b/core/res/res/values-zh-rCN/required_apps_managed_profile.xml @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string-array name="required_apps_managed_profile"> + <item msgid="1457364287544474838">"com.android.contacts"</item> + <item msgid="4633145750237794002">"com.android.settings"</item> + <item msgid="6518205098643077579">"com.android.providers.downloads"</item> + <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item> + <item msgid="6106837921940099371">"com.android.documentsui"</item> + </string-array> +</resources> diff --git a/core/res/res/values-zh-rCN/required_apps_managed_user.xml b/core/res/res/values-zh-rCN/required_apps_managed_user.xml new file mode 100644 index 000000000000..1a7ade9dd5f9 --- /dev/null +++ b/core/res/res/values-zh-rCN/required_apps_managed_user.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string-array name="required_apps_managed_user"> + <item msgid="4823915868435007499">"com.android.settings"</item> + <item msgid="2250259015310893915">"com.android.contacts"</item> + <item msgid="7166574999426592423">"com.android.dialer"</item> + <item msgid="7306937186458176744">"com.android.stk"</item> + <item msgid="7415441588151512455">"com.android.providers.downloads"</item> + <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item> + <item msgid="8640522622655589373">"com.android.documentsui"</item> + </string-array> +</resources> diff --git a/core/res/res/values-zh-rHK/required_apps_managed_device.xml b/core/res/res/values-zh-rHK/required_apps_managed_device.xml new file mode 100644 index 000000000000..9044fccd88ee --- /dev/null +++ b/core/res/res/values-zh-rHK/required_apps_managed_device.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string-array name="required_apps_managed_device"> + <item msgid="1104492179978792509">"com.android.settings"</item> + <item msgid="7004798084799227194">"com.android.contacts"</item> + <item msgid="5782220690863647256">"com.android.dialer"</item> + <item msgid="5746338511138092673">"com.android.stk"</item> + <item msgid="1712599182168590664">"com.android.providers.downloads"</item> + <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item> + <item msgid="3892021562839042708">"com.android.documentsui"</item> + </string-array> +</resources> diff --git a/core/res/res/values-zh-rHK/required_apps_managed_profile.xml b/core/res/res/values-zh-rHK/required_apps_managed_profile.xml new file mode 100644 index 000000000000..4296b0d37dd2 --- /dev/null +++ b/core/res/res/values-zh-rHK/required_apps_managed_profile.xml @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string-array name="required_apps_managed_profile"> + <item msgid="1457364287544474838">"com.android.contacts"</item> + <item msgid="4633145750237794002">"com.android.settings"</item> + <item msgid="6518205098643077579">"com.android.providers.downloads"</item> + <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item> + <item msgid="6106837921940099371">"com.android.documentsui"</item> + </string-array> +</resources> diff --git a/core/res/res/values-zh-rHK/required_apps_managed_user.xml b/core/res/res/values-zh-rHK/required_apps_managed_user.xml new file mode 100644 index 000000000000..1a7ade9dd5f9 --- /dev/null +++ b/core/res/res/values-zh-rHK/required_apps_managed_user.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string-array name="required_apps_managed_user"> + <item msgid="4823915868435007499">"com.android.settings"</item> + <item msgid="2250259015310893915">"com.android.contacts"</item> + <item msgid="7166574999426592423">"com.android.dialer"</item> + <item msgid="7306937186458176744">"com.android.stk"</item> + <item msgid="7415441588151512455">"com.android.providers.downloads"</item> + <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item> + <item msgid="8640522622655589373">"com.android.documentsui"</item> + </string-array> +</resources> diff --git a/core/res/res/values-zh-rTW/required_apps_managed_device.xml b/core/res/res/values-zh-rTW/required_apps_managed_device.xml new file mode 100644 index 000000000000..9044fccd88ee --- /dev/null +++ b/core/res/res/values-zh-rTW/required_apps_managed_device.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string-array name="required_apps_managed_device"> + <item msgid="1104492179978792509">"com.android.settings"</item> + <item msgid="7004798084799227194">"com.android.contacts"</item> + <item msgid="5782220690863647256">"com.android.dialer"</item> + <item msgid="5746338511138092673">"com.android.stk"</item> + <item msgid="1712599182168590664">"com.android.providers.downloads"</item> + <item msgid="2858239953396384085">"com.android.providers.downloads.ui"</item> + <item msgid="3892021562839042708">"com.android.documentsui"</item> + </string-array> +</resources> diff --git a/core/res/res/values-zh-rTW/required_apps_managed_profile.xml b/core/res/res/values-zh-rTW/required_apps_managed_profile.xml new file mode 100644 index 000000000000..4296b0d37dd2 --- /dev/null +++ b/core/res/res/values-zh-rTW/required_apps_managed_profile.xml @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string-array name="required_apps_managed_profile"> + <item msgid="1457364287544474838">"com.android.contacts"</item> + <item msgid="4633145750237794002">"com.android.settings"</item> + <item msgid="6518205098643077579">"com.android.providers.downloads"</item> + <item msgid="9003577256117829525">"com.android.providers.downloads.ui"</item> + <item msgid="6106837921940099371">"com.android.documentsui"</item> + </string-array> +</resources> diff --git a/core/res/res/values-zh-rTW/required_apps_managed_user.xml b/core/res/res/values-zh-rTW/required_apps_managed_user.xml new file mode 100644 index 000000000000..1a7ade9dd5f9 --- /dev/null +++ b/core/res/res/values-zh-rTW/required_apps_managed_user.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string-array name="required_apps_managed_user"> + <item msgid="4823915868435007499">"com.android.settings"</item> + <item msgid="2250259015310893915">"com.android.contacts"</item> + <item msgid="7166574999426592423">"com.android.dialer"</item> + <item msgid="7306937186458176744">"com.android.stk"</item> + <item msgid="7415441588151512455">"com.android.providers.downloads"</item> + <item msgid="2277950048461066377">"com.android.providers.downloads.ui"</item> + <item msgid="8640522622655589373">"com.android.documentsui"</item> + </string-array> +</resources> diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml index 51ffa6007ae0..64febf1f7ee4 100644 --- a/core/res/res/values/attrs.xml +++ b/core/res/res/values/attrs.xml @@ -4442,7 +4442,7 @@ <attr name="textColor" /> <!-- Size of the text. Recommended dimension type for text is "sp" for scaled-pixels (example: 15sp). --> <attr name="textSize" /> - <!-- Style (bold, italic, bolditalic) for the text. --> + <!-- Style (normal, bold, italic, bold|italic) for the text. --> <attr name="textStyle" /> <!-- Typeface (normal, sans, serif, monospace) for the text. --> <attr name="typeface" /> @@ -4527,7 +4527,7 @@ <attr name="textScaleX" format="float" /> <!-- Typeface (normal, sans, serif, monospace) for the text. --> <attr name="typeface" /> - <!-- Style (bold, italic, bolditalic) for the text. --> + <!-- Style (normal, bold, italic, bold|italic) for the text. --> <attr name="textStyle" /> <!-- Font family (named by string or as a font resource reference) for the text. --> <attr name="fontFamily" /> @@ -7729,6 +7729,10 @@ wallpaper. --> <attr name="showMetadataInPreview" format="boolean" /> + <!-- Wallpapers optimized and capable of drawing in ambient mode will return true. + @hide --> + <attr name="supportsAmbientMode" format="boolean" /> + </declare-styleable> <!-- Use <code>dream</code> as the root tag of the XML resource that diff --git a/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java b/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java index 4b1f2dab61b9..e4de5263bb7c 100644 --- a/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java +++ b/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java @@ -513,6 +513,12 @@ public class TransactionParcelTests { } @Override + public void dumpMemInfoProto(ParcelFileDescriptor parcelFileDescriptor, + Debug.MemoryInfo memoryInfo, boolean b, boolean b1, boolean b2, + boolean b3, String[] strings) throws RemoteException { + } + + @Override public void dumpGfxInfo(ParcelFileDescriptor parcelFileDescriptor, String[] strings) throws RemoteException { } diff --git a/core/tests/coretests/src/android/text/DynamicLayoutTest.java b/core/tests/coretests/src/android/text/DynamicLayoutTest.java index aa9aed8e2fcc..ea954f65476d 100644 --- a/core/tests/coretests/src/android/text/DynamicLayoutTest.java +++ b/core/tests/coretests/src/android/text/DynamicLayoutTest.java @@ -30,6 +30,7 @@ import android.platform.test.annotations.Presubmit; import android.support.test.filters.SmallTest; import android.support.test.runner.AndroidJUnit4; import android.text.style.ReplacementSpan; +import android.util.ArraySet; import org.junit.Test; import org.junit.runner.RunWith; @@ -190,6 +191,27 @@ public class DynamicLayoutTest { } @Test + public void testReflow_afterSpannableEdit() { + final String text = "a\nb:\uD83C\uDF1A c \n\uD83C\uDF1A"; + final int length = text.length(); + final SpannableStringBuilder spannable = new SpannableStringBuilder(text); + spannable.setSpan(new MockReplacementSpan(), 4, 6, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + spannable.setSpan(new MockReplacementSpan(), 10, length, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + + final DynamicLayout layout = new DynamicLayout(spannable, new TextPaint(), WIDTH, + ALIGN_NORMAL, 1.0f /*spacingMultiplier*/, 0f /*spacingAdd*/, false /*includepad*/); + + spannable.delete(8, 9); + spannable.replace(7, 8, "ch"); + + layout.reflow(spannable, 0, length, length); + final ArraySet<Integer> blocks = layout.getBlocksAlwaysNeedToBeRedrawn(); + for (Integer value : blocks) { + assertTrue("Block index should not be negative", value >= 0); + } + } + + @Test public void testFallbackLineSpacing() { // All glyphs in the fonts are 1em wide. final String[] testFontFiles = { diff --git a/core/tests/coretests/src/android/text/SpannableStringBuilderTest.java b/core/tests/coretests/src/android/text/SpannableStringBuilderTest.java index f1a730a7a420..d0f2d46f9c2b 100644 --- a/core/tests/coretests/src/android/text/SpannableStringBuilderTest.java +++ b/core/tests/coretests/src/android/text/SpannableStringBuilderTest.java @@ -20,6 +20,7 @@ package android.text; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; +import android.platform.test.annotations.Presubmit; import android.support.test.filters.SmallTest; import android.support.test.runner.AndroidJUnit4; import android.text.style.BulletSpan; @@ -30,6 +31,7 @@ import android.text.style.UnderlineSpan; import org.junit.Test; import org.junit.runner.RunWith; +@Presubmit @SmallTest @RunWith(AndroidJUnit4.class) public class SpannableStringBuilderTest extends SpannableTest { diff --git a/core/tests/coretests/src/android/text/format/FormatterTest.java b/core/tests/coretests/src/android/text/format/FormatterTest.java index 2d9401686e81..9c544f47717c 100644 --- a/core/tests/coretests/src/android/text/format/FormatterTest.java +++ b/core/tests/coretests/src/android/text/format/FormatterTest.java @@ -23,6 +23,7 @@ import android.content.Context; import android.content.res.Configuration; import android.content.res.Resources; import android.icu.util.MeasureUnit; +import android.platform.test.annotations.Presubmit; import android.support.test.InstrumentationRegistry; import android.support.test.filters.SmallTest; import android.support.test.runner.AndroidJUnit4; @@ -36,7 +37,7 @@ import org.junit.runner.RunWith; import java.util.Locale; import java.util.Set; - +@Presubmit @SmallTest @RunWith(AndroidJUnit4.class) public class FormatterTest { diff --git a/core/tests/coretests/src/android/view/textclassifier/TextClassificationManagerTest.java b/core/tests/coretests/src/android/view/textclassifier/TextClassificationManagerTest.java index 9092c852db47..a8de374cf30b 100644 --- a/core/tests/coretests/src/android/view/textclassifier/TextClassificationManagerTest.java +++ b/core/tests/coretests/src/android/view/textclassifier/TextClassificationManagerTest.java @@ -16,10 +16,9 @@ package android.view.textclassifier; +import static org.hamcrest.CoreMatchers.not; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertThat; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; import static org.mockito.Mockito.mock; import android.os.LocaleList; @@ -34,8 +33,6 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; -import java.util.Collection; - @SmallTest @RunWith(AndroidJUnit4.class) public class TextClassificationManagerTest { @@ -166,20 +163,50 @@ public class TextClassificationManagerTest { } @Test - public void testGenerateLinks() { + public void testGenerateLinks_phone() { if (isTextClassifierDisabled()) return; + String text = "The number is +12122537077. See you tonight!"; + assertThat(mClassifier.generateLinks(text, null), + isTextLinksContaining(text, "+12122537077", TextClassifier.TYPE_PHONE)); + } - checkGenerateLinksFindsLink( - "The number is +12122537077. See you tonight!", - "+12122537077", - TextClassifier.TYPE_PHONE); + @Test + public void testGenerateLinks_exclude() { + if (isTextClassifierDisabled()) return; + String text = "The number is +12122537077. See you tonight!"; + assertThat(mClassifier.generateLinks(text, mLinksOptions.setEntityConfig( + new TextClassifier.EntityConfig(TextClassifier.ENTITY_PRESET_ALL) + .excludeEntities(TextClassifier.TYPE_PHONE))), + not(isTextLinksContaining(text, "+12122537077", TextClassifier.TYPE_PHONE))); + } + + @Test + public void testGenerateLinks_none_config() { + if (isTextClassifierDisabled()) return; + String text = "The number is +12122537077. See you tonight!"; + assertThat(mClassifier.generateLinks(text, mLinksOptions.setEntityConfig( + new TextClassifier.EntityConfig(TextClassifier.ENTITY_PRESET_NONE))), + not(isTextLinksContaining(text, "+12122537077", TextClassifier.TYPE_PHONE))); + } - checkGenerateLinksFindsLink( - "The address is 1600 Amphitheater Parkway, Mountain View, CA. See you tonight!", - "1600 Amphitheater Parkway, Mountain View, CA", - TextClassifier.TYPE_ADDRESS); + @Test + public void testGenerateLinks_address() { + if (isTextClassifierDisabled()) return; + String text = "The address is 1600 Amphitheater Parkway, Mountain View, CA. See you!"; + assertThat(mClassifier.generateLinks(text, null), + isTextLinksContaining(text, "1600 Amphitheater Parkway, Mountain View, CA", + TextClassifier.TYPE_ADDRESS)); + } - // TODO: Add more entity types when the model supports them. + @Test + public void testGenerateLinks_include() { + if (isTextClassifierDisabled()) return; + String text = "The address is 1600 Amphitheater Parkway, Mountain View, CA. See you!"; + assertThat(mClassifier.generateLinks(text, mLinksOptions.setEntityConfig( + new TextClassifier.EntityConfig(TextClassifier.ENTITY_PRESET_NONE) + .includeEntities(TextClassifier.TYPE_ADDRESS))), + isTextLinksContaining(text, "1600 Amphitheater Parkway, Mountain View, CA", + TextClassifier.TYPE_ADDRESS)); } @Test @@ -193,25 +220,6 @@ public class TextClassificationManagerTest { return mClassifier == TextClassifier.NO_OP; } - private void checkGenerateLinksFindsLink(String text, String classifiedText, String type) { - assertTrue(text.contains(classifiedText)); - int startIndex = text.indexOf(classifiedText); - int endIndex = startIndex + classifiedText.length(); - - Collection<TextLinks.TextLink> links = mClassifier.generateLinks(text, mLinksOptions) - .getLinks(); - for (TextLinks.TextLink link : links) { - if (text.subSequence(link.getStart(), link.getEnd()).equals(classifiedText)) { - assertEquals(type, link.getEntity(0)); - assertEquals(startIndex, link.getStart()); - assertEquals(endIndex, link.getEnd()); - assertTrue(link.getConfidenceScore(type) > 0); - return; - } - } - fail(); // Subsequence was not identified. - } - private static Matcher<TextSelection> isTextSelection( final int startIndex, final int endIndex, final String type) { return new BaseMatcher<TextSelection>() { @@ -240,6 +248,31 @@ public class TextClassificationManagerTest { }; } + private static Matcher<TextLinks> isTextLinksContaining( + final String text, final String substring, final String type) { + return new BaseMatcher<TextLinks>() { + + @Override + public void describeTo(Description description) { + description.appendText("text=").appendValue(text) + .appendText(", substring=").appendValue(substring) + .appendText(", type=").appendValue(type); + } + + @Override + public boolean matches(Object o) { + if (o instanceof TextLinks) { + for (TextLinks.TextLink link : ((TextLinks) o).getLinks()) { + if (text.subSequence(link.getStart(), link.getEnd()).equals(substring)) { + return type.equals(link.getEntity(0)); + } + } + } + return false; + } + }; + } + private static Matcher<TextClassification> isTextClassification( final String text, final String type, final String intentUri) { return new BaseMatcher<TextClassification>() { diff --git a/core/tests/coretests/src/android/widget/TextViewActivityTest.java b/core/tests/coretests/src/android/widget/TextViewActivityTest.java index 0e460b9ed2b2..1a654f4b9ebb 100644 --- a/core/tests/coretests/src/android/widget/TextViewActivityTest.java +++ b/core/tests/coretests/src/android/widget/TextViewActivityTest.java @@ -27,8 +27,10 @@ import static android.support.test.espresso.matcher.ViewMatchers.withId; import static android.support.test.espresso.matcher.ViewMatchers.withText; import static android.widget.espresso.CustomViewActions.longPressAtRelativeCoordinates; import static android.widget.espresso.DragHandleUtils.onHandleView; -import static android.widget.espresso.FloatingToolbarEspressoUtils.assertFloatingToolbarContainsItem; -import static android.widget.espresso.FloatingToolbarEspressoUtils.assertFloatingToolbarDoesNotContainItem; +import static android.widget.espresso.FloatingToolbarEspressoUtils + .assertFloatingToolbarContainsItem; +import static android.widget.espresso.FloatingToolbarEspressoUtils + .assertFloatingToolbarDoesNotContainItem; import static android.widget.espresso.FloatingToolbarEspressoUtils.assertFloatingToolbarIsDisplayed; import static android.widget.espresso.FloatingToolbarEspressoUtils.assertFloatingToolbarItemIndex; import static android.widget.espresso.FloatingToolbarEspressoUtils.clickFloatingToolbarItem; @@ -68,12 +70,15 @@ import android.test.suitebuilder.annotation.Suppress; import android.text.InputType; import android.text.Selection; import android.text.Spannable; +import android.text.SpannableString; +import android.text.method.LinkMovementMethod; import android.view.ActionMode; import android.view.KeyEvent; import android.view.Menu; import android.view.MenuItem; import android.view.textclassifier.TextClassificationManager; import android.view.textclassifier.TextClassifier; +import android.view.textclassifier.TextLinks; import android.widget.espresso.CustomViewActions.RelativeCoordinatesProvider; import com.android.frameworks.coretests.R; @@ -305,6 +310,33 @@ public class TextViewActivityTest { } @Test + public void testToolbarAppearsAfterLinkClicked() throws Throwable { + useSystemDefaultTextClassifier(); + TextClassificationManager textClassificationManager = + mActivity.getSystemService(TextClassificationManager.class); + TextClassifier textClassifier = textClassificationManager.getTextClassifier(); + final TextView textView = mActivity.findViewById(R.id.textview); + SpannableString content = new SpannableString("Call me at +19148277737"); + TextLinks links = textClassifier.generateLinks(content); + links.apply(content, null); + + mActivityRule.runOnUiThread(() -> { + textView.setText(content); + textView.setMovementMethod(LinkMovementMethod.getInstance()); + }); + mInstrumentation.waitForIdleSync(); + + // Wait for the UI thread to refresh + Thread.sleep(1000); + + TextLinks.TextLink textLink = links.getLinks().iterator().next(); + int position = (textLink.getStart() + textLink.getEnd()) / 2; + onView(withId(R.id.textview)).perform(clickOnTextAtIndex(position)); + sleepForFloatingToolbarPopup(); + assertFloatingToolbarIsDisplayed(); + } + + @Test public void testToolbarAndInsertionHandle() { final String text = "text"; onView(withId(R.id.textview)).perform(replaceText(text)); diff --git a/core/tests/privacytests/Android.mk b/core/tests/privacytests/Android.mk new file mode 100644 index 000000000000..7b112259c9d7 --- /dev/null +++ b/core/tests/privacytests/Android.mk @@ -0,0 +1,16 @@ +LOCAL_PATH:= $(call my-dir) +include $(CLEAR_VARS) + +# We only want this apk build for tests. +LOCAL_MODULE_TAGS := tests + +# Include all test java files. +LOCAL_SRC_FILES := \ + $(call all-java-files-under, src) + +LOCAL_STATIC_JAVA_LIBRARIES := junit rappor-tests android-support-test + +LOCAL_JAVA_LIBRARIES := android.test.runner +LOCAL_PACKAGE_NAME := FrameworksPrivacyLibraryTests + +include $(BUILD_PACKAGE) diff --git a/core/tests/privacytests/AndroidManifest.xml b/core/tests/privacytests/AndroidManifest.xml new file mode 100644 index 000000000000..a0e52814bdcd --- /dev/null +++ b/core/tests/privacytests/AndroidManifest.xml @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2017 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.frameworks.coretests.privacy"> + + <application> + <uses-library android:name="android.test.runner" /> + </application> + + <instrumentation + android:name="android.support.test.runner.AndroidJUnitRunner" + android:targetPackage="com.android.frameworks.coretests.privacy" + android:label="Frameworks Privacy Library Tests" /> + +</manifest> diff --git a/core/tests/privacytests/src/android/privacy/LongitudinalReportingEncoderTest.java b/core/tests/privacytests/src/android/privacy/LongitudinalReportingEncoderTest.java new file mode 100644 index 000000000000..91664381efe0 --- /dev/null +++ b/core/tests/privacytests/src/android/privacy/LongitudinalReportingEncoderTest.java @@ -0,0 +1,392 @@ +/* + * 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.privacy; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import android.privacy.internal.longitudinalreporting.LongitudinalReportingConfig; +import android.privacy.internal.longitudinalreporting.LongitudinalReportingEncoder; + +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; + +/** + * Unit test for the {@link LongitudinalReportingEncoder}. + * + * As {@link LongitudinalReportingEncoder} is based on Rappor, + * most cases are covered by Rappor tests already. + */ +@RunWith(AndroidJUnit4.class) +@SmallTest +public class LongitudinalReportingEncoderTest { + + @Test + public void testLongitudinalReportingEncoder_config() throws Exception { + final LongitudinalReportingConfig config = new LongitudinalReportingConfig( + "Foo", // encoderId + 0.4, // probabilityF + 0.25, // probabilityP + 1); // probabilityQ + final LongitudinalReportingEncoder encoder = + LongitudinalReportingEncoder.createInsecureEncoderForTest( + config); + assertEquals("LongitudinalReporting", encoder.getConfig().getAlgorithm()); + assertEquals( + "EncoderId: Foo, ProbabilityF: 0.400, ProbabilityP: 0.250, ProbabilityQ: 1.000", + encoder.getConfig().toString()); + } + + @Test + public void testLongitudinalReportingEncoder_basicIRRTest() throws Exception { + // Test if IRR can generate expected result when seed is fixed (insecure encoder) + final LongitudinalReportingConfig config = new LongitudinalReportingConfig( + "Foo", // encoderId + 0.4, // probabilityF + 0, // probabilityP + 0); // probabilityQ + // Use insecure encoder here to make sure seed is set. + final LongitudinalReportingEncoder encoder = + LongitudinalReportingEncoder.createInsecureEncoderForTest( + config); + assertEquals(1, encoder.encodeBoolean(true)[0]); + assertEquals(0, encoder.encodeBoolean(true)[0]); + assertEquals(1, encoder.encodeBoolean(true)[0]); + assertEquals(1, encoder.encodeBoolean(true)[0]); + assertEquals(1, encoder.encodeBoolean(true)[0]); + assertEquals(1, encoder.encodeBoolean(true)[0]); + assertEquals(0, encoder.encodeBoolean(true)[0]); + assertEquals(1, encoder.encodeBoolean(true)[0]); + assertEquals(1, encoder.encodeBoolean(true)[0]); + assertEquals(1, encoder.encodeBoolean(true)[0]); + + assertEquals(0, encoder.encodeBoolean(false)[0]); + assertEquals(1, encoder.encodeBoolean(false)[0]); + assertEquals(1, encoder.encodeBoolean(false)[0]); + assertEquals(0, encoder.encodeBoolean(false)[0]); + assertEquals(0, encoder.encodeBoolean(false)[0]); + assertEquals(0, encoder.encodeBoolean(false)[0]); + assertEquals(1, encoder.encodeBoolean(false)[0]); + assertEquals(0, encoder.encodeBoolean(false)[0]); + assertEquals(0, encoder.encodeBoolean(false)[0]); + assertEquals(1, encoder.encodeBoolean(false)[0]); + + // Test if IRR returns original result when f = 0 + final LongitudinalReportingConfig config2 = new LongitudinalReportingConfig( + "Foo", // encoderId + 0, // probabilityF + 0, // probabilityP + 0); // probabilityQ + final LongitudinalReportingEncoder encoder2 + = LongitudinalReportingEncoder.createEncoder( + config2, makeTestingUserSecret("secret2")); + for (int i = 0; i < 10; i++) { + assertEquals(1, encoder2.encodeBoolean(true)[0]); + } + for (int i = 0; i < 10; i++) { + assertEquals(0, encoder2.encodeBoolean(false)[0]); + } + + // Test if IRR returns opposite result when f = 1 + final LongitudinalReportingConfig config3 = new LongitudinalReportingConfig( + "Foo", // encoderId + 1, // probabilityF + 0, // probabilityP + 0); // probabilityQ + final LongitudinalReportingEncoder encoder3 + = LongitudinalReportingEncoder.createEncoder( + config3, makeTestingUserSecret("secret3")); + for (int i = 0; i < 10; i++) { + assertEquals(1, encoder3.encodeBoolean(false)[0]); + } + for (int i = 0; i < 10; i++) { + assertEquals(0, encoder3.encodeBoolean(true)[0]); + } + } + + @Test + public void testLongitudinalReportingEncoder_basicPRRTest() throws Exception { + // Should always return original value when p = 0 + for (int i = 0; i < 10; i++) { + for (int j = 0; j < 10; j++) { + final LongitudinalReportingConfig config1 = new LongitudinalReportingConfig( + "Foo" + i, // encoderId + 0, // probabilityF + 0, // probabilityP + 0); // probabilityQ + final LongitudinalReportingEncoder encoder1 + = LongitudinalReportingEncoder.createEncoder( + config1, makeTestingUserSecret("encoder" + j)); + assertEquals(0, encoder1.encodeBoolean(false)[0]); + assertEquals(0, encoder1.encodeBoolean(false)[0]); + assertEquals(0, encoder1.encodeBoolean(false)[0]); + assertEquals(0, encoder1.encodeBoolean(false)[0]); + assertEquals(0, encoder1.encodeBoolean(false)[0]); + assertEquals(1, encoder1.encodeBoolean(true)[0]); + assertEquals(1, encoder1.encodeBoolean(true)[0]); + assertEquals(1, encoder1.encodeBoolean(true)[0]); + assertEquals(1, encoder1.encodeBoolean(true)[0]); + assertEquals(1, encoder1.encodeBoolean(true)[0]); + } + } + + // Should always return false when p = 1, q = 0 + for (int i = 0; i < 10; i++) { + for (int j = 0; j < 10; j++) { + final LongitudinalReportingConfig config2 = new LongitudinalReportingConfig( + "Foo" + i, // encoderId + 0, // probabilityF + 1, // probabilityP + 0); // probabilityQ + final LongitudinalReportingEncoder encoder2 + = LongitudinalReportingEncoder.createEncoder( + config2, makeTestingUserSecret("encoder" + j)); + assertEquals(0, encoder2.encodeBoolean(false)[0]); + assertEquals(0, encoder2.encodeBoolean(false)[0]); + assertEquals(0, encoder2.encodeBoolean(false)[0]); + assertEquals(0, encoder2.encodeBoolean(false)[0]); + assertEquals(0, encoder2.encodeBoolean(false)[0]); + assertEquals(0, encoder2.encodeBoolean(true)[0]); + assertEquals(0, encoder2.encodeBoolean(true)[0]); + assertEquals(0, encoder2.encodeBoolean(true)[0]); + assertEquals(0, encoder2.encodeBoolean(true)[0]); + assertEquals(0, encoder2.encodeBoolean(true)[0]); + } + } + + // Should always return true when p = 1, q = 1 + for (int i = 0; i < 10; i++) { + for (int j = 0; j < 10; j++) { + final LongitudinalReportingConfig config3 = new LongitudinalReportingConfig( + "Foo" + i, // encoderId + 0, // probabilityF + 1, // probabilityP + 1); // probabilityQ + final LongitudinalReportingEncoder encoder3 + = LongitudinalReportingEncoder.createEncoder( + config3, makeTestingUserSecret("encoder" + j)); + assertEquals(1, encoder3.encodeBoolean(false)[0]); + assertEquals(1, encoder3.encodeBoolean(false)[0]); + assertEquals(1, encoder3.encodeBoolean(false)[0]); + assertEquals(1, encoder3.encodeBoolean(false)[0]); + assertEquals(1, encoder3.encodeBoolean(false)[0]); + assertEquals(1, encoder3.encodeBoolean(true)[0]); + assertEquals(1, encoder3.encodeBoolean(true)[0]); + assertEquals(1, encoder3.encodeBoolean(true)[0]); + assertEquals(1, encoder3.encodeBoolean(true)[0]); + assertEquals(1, encoder3.encodeBoolean(true)[0]); + } + } + + // PRR should return different value when encoder id is changed + boolean hasFalseResult1 = false; + boolean hasTrueResult1 = false; + for (int i = 0; i < 50; i++) { + boolean firstResult = false; + for (int j = 0; j < 10; j++) { + final LongitudinalReportingConfig config4 = new LongitudinalReportingConfig( + "Foo" + i, // encoderId + 0, // probabilityF + 1, // probabilityP + 0.5); // probabilityQ + final LongitudinalReportingEncoder encoder4 + = LongitudinalReportingEncoder.createEncoder( + config4, makeTestingUserSecret("encoder4")); + boolean encodedFalse = encoder4.encodeBoolean(false)[0] > 0; + boolean encodedTrue = encoder4.encodeBoolean(true)[0] > 0; + // PRR should always give the same value when all parameters are the same + assertEquals(encodedTrue, encodedFalse); + if (j == 0) { + firstResult = encodedTrue; + } else { + assertEquals(firstResult, encodedTrue); + } + if (encodedTrue) { + hasTrueResult1 = true; + } else { + hasFalseResult1 = true; + } + } + } + // Ensure it has both true and false results when encoder id is different + assertTrue(hasTrueResult1); + assertTrue(hasFalseResult1); + + // PRR should give different value when secret is changed + boolean hasFalseResult2 = false; + boolean hasTrueResult2 = false; + for (int i = 0; i < 50; i++) { + boolean firstResult = false; + for (int j = 0; j < 10; j++) { + final LongitudinalReportingConfig config5 = new LongitudinalReportingConfig( + "Foo", // encoderId + 0, // probabilityF + 1, // probabilityP + 0.5); // probabilityQ + final LongitudinalReportingEncoder encoder5 + = LongitudinalReportingEncoder.createEncoder( + config5, makeTestingUserSecret("encoder" + i)); + boolean encodedFalse = encoder5.encodeBoolean(false)[0] > 0; + boolean encodedTrue = encoder5.encodeBoolean(true)[0] > 0; + // PRR should always give the same value when parameters are the same + assertEquals(encodedTrue, encodedFalse); + if (j == 0) { + firstResult = encodedTrue; + } else { + assertEquals(firstResult, encodedTrue); + } + if (encodedTrue) { + hasTrueResult2 = true; + } else { + hasFalseResult2 = true; + } + } + } + // Ensure it has both true and false results when encoder id is different + assertTrue(hasTrueResult2); + assertTrue(hasFalseResult2); + + // Confirm if PRR randomizer is working correctly + final int n1 = 1000; + final double p1 = 0.8; + final double expectedTrueSum1 = n1 * p1; + final double valueRange1 = 5 * Math.sqrt(n1 * p1 * (1 - p1)); + int trueSum1 = 0; + for (int i = 0; i < n1; i++) { + final LongitudinalReportingConfig config6 = new LongitudinalReportingConfig( + "Foo", // encoderId + 0, // probabilityF + p1, // probabilityP + 1); // probabilityQ + final LongitudinalReportingEncoder encoder6 + = LongitudinalReportingEncoder.createEncoder( + config6, makeTestingUserSecret("encoder" + i)); + boolean encodedFalse = encoder6.encodeBoolean(false)[0] > 0; + if (encodedFalse) { + trueSum1 += 1; + } + } + // Total number of true(s) should be around the mean (1000 * 0.8) + assertTrue(trueSum1 < expectedTrueSum1 + valueRange1); + assertTrue(trueSum1 > expectedTrueSum1 - valueRange1); + + // Confirm if PRR randomizer is working correctly + final int n2 = 1000; + final double p2 = 0.2; + final double expectedTrueSum2 = n2 * p2; + final double valueRange2 = 5 * Math.sqrt(n2 * p2 * (1 - p2)); + int trueSum2 = 0; + for (int i = 0; i < n2; i++) { + final LongitudinalReportingConfig config7 = new LongitudinalReportingConfig( + "Foo", // encoderId + 0, // probabilityF + p2, // probabilityP + 1); // probabilityQ + final LongitudinalReportingEncoder encoder7 + = LongitudinalReportingEncoder.createEncoder( + config7, makeTestingUserSecret("encoder" + i)); + boolean encodedFalse = encoder7.encodeBoolean(false)[0] > 0; + if (encodedFalse) { + trueSum2 += 1; + } + } + // Total number of true(s) should be around the mean (1000 * 0.2) + assertTrue(trueSum2 < expectedTrueSum2 + valueRange2); + assertTrue(trueSum2 > expectedTrueSum2 - valueRange2); + } + + @Test + public void testLongitudinalReportingEncoder_basicIRRwithPRRTest() throws Exception { + // Verify PRR result will run IRR + boolean hasFalseResult1 = false; + boolean hasTrueResult1 = false; + for (int i = 0; i < 50; i++) { + final LongitudinalReportingConfig config1 = new LongitudinalReportingConfig( + "Foo", // encoderId + 0.5, // probabilityF + 1, // probabilityP + 1); // probabilityQ + final LongitudinalReportingEncoder encoder1 + = LongitudinalReportingEncoder.createEncoder( + config1, makeTestingUserSecret("encoder1")); + if (encoder1.encodeBoolean(false)[0] > 0) { + hasTrueResult1 = true; + } else { + hasFalseResult1 = true; + } + } + assertTrue(hasTrueResult1); + assertTrue(hasFalseResult1); + + // When secret is different, some device should use PRR result, some should use IRR result + boolean hasFalseResult2 = false; + boolean hasTrueResult2 = false; + for (int i = 0; i < 50; i++) { + final LongitudinalReportingConfig config2 = new LongitudinalReportingConfig( + "Foo", // encoderId + 1, // probabilityF + 0.5, // probabilityP + 1); // probabilityQ + final LongitudinalReportingEncoder encoder2 + = LongitudinalReportingEncoder.createEncoder( + config2, makeTestingUserSecret("encoder" + i)); + if (encoder2.encodeBoolean(false)[0] > 0) { + hasTrueResult2 = true; + } else { + hasFalseResult2 = true; + } + } + assertTrue(hasTrueResult2); + assertTrue(hasFalseResult2); + } + + @Test + public void testLongTermRandomizedResult() throws Exception { + // Verify getLongTermRandomizedResult can return expected result when parameters are fixed. + final boolean[] expectedResult = + new boolean[]{true, false, true, true, true, + false, false, false, true, false, + false, false, false, true, true, + true, true, false, true, true, + true, true, false, true, true}; + for (int i = 0; i < 5; i++) { + for (int j = 0; j < 5; j++) { + boolean result = LongitudinalReportingEncoder.getLongTermRandomizedResult(0.5, + true, makeTestingUserSecret("secret" + i), "encoder" + j); + assertEquals(expectedResult[i * 5 + j], result); + } + } + } + + private static byte[] makeTestingUserSecret(String testingSecret) throws Exception { + // We generate the fake user secret by concatenating three copies of the + // 16 byte MD5 hash of the testingSecret string encoded in UTF 8. + MessageDigest md5 = MessageDigest.getInstance("MD5"); + byte[] digest = md5.digest(testingSecret.getBytes(StandardCharsets.UTF_8)); + assertEquals(16, digest.length); + return ByteBuffer.allocate(48).put(digest).put(digest).put(digest).array(); + } +} diff --git a/core/tests/privacytests/src/android/privacy/RapporEncoderTest.java b/core/tests/privacytests/src/android/privacy/RapporEncoderTest.java new file mode 100644 index 000000000000..dad98b8e4a35 --- /dev/null +++ b/core/tests/privacytests/src/android/privacy/RapporEncoderTest.java @@ -0,0 +1,204 @@ +/* + * 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.privacy; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import android.privacy.internal.rappor.RapporConfig; +import android.privacy.internal.rappor.RapporEncoder; +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; + +/** + * Unit test for the {@link RapporEncoder}. + * Most of the tests are done in external/rappor/client/javatest/ already. + * Tests here are just make sure the {@link RapporEncoder} wrap Rappor correctly. + */ +@RunWith(AndroidJUnit4.class) +@SmallTest +public class RapporEncoderTest { + + @Test + public void testRapporEncoder_config() throws Exception { + final RapporConfig config = new RapporConfig( + "Foo", // encoderId + 8, // numBits, + 13.0 / 128.0, // probabilityF + 0.25, // probabilityP + 0.75, // probabilityQ + 1, // numCohorts + 2); // numBloomHashes) + final RapporEncoder encoder = RapporEncoder.createEncoder(config, + makeTestingUserSecret("encoder1")); + assertEquals("Rappor", encoder.getConfig().getAlgorithm()); + assertEquals("EncoderId: Foo, NumBits: 8, ProbabilityF: 0.102, " + + "ProbabilityP: 0.250, ProbabilityQ: 0.750, NumCohorts: 1, " + + "NumBloomHashes: 2", encoder.getConfig().toString()); + } + + @Test + public void testRapporEncoder_basicIRRTest() throws Exception { + final RapporConfig config = new RapporConfig( + "Foo", // encoderId + 12, // numBits, + 0, // probabilityF + 0, // probabilityP + 1, // probabilityQ + 1, // numCohorts (so must be cohort 0) + 2); // numBloomHashes + // Use insecure encoder here as we want to get the exact output. + final RapporEncoder encoder = RapporEncoder.createInsecureEncoderForTest(config); + assertEquals(768, toLong(encoder.encodeString("Testing"))); + } + + @Test + public void testRapporEncoder_IRRWithPRR() throws Exception { + int numBits = 8; + final long inputValue = 254L; + final long prrValue = 250L; + final long prrAndIrrValue = 184L; + + final RapporConfig config1 = new RapporConfig( + "Foo", // encoderId + numBits, // numBits, + 0.25, // probabilityF + 0, // probabilityP + 1, // probabilityQ + 1, // numCohorts + 2); // numBloomHashes + // Use insecure encoder here as we want to get the exact output. + final RapporEncoder encoder1 = RapporEncoder.createInsecureEncoderForTest(config1); + // Verify that PRR is working as expected. + assertEquals(prrValue, toLong(encoder1.encodeBits(toBytes(inputValue)))); + assertTrue(encoder1.isInsecureEncoderForTest()); + + // Verify that IRR is working as expected. + final RapporConfig config2 = new RapporConfig( + "Foo", // encoderId + numBits, // numBits, + 0, // probabilityF + 0.3, // probabilityP + 0.7, // probabilityQ + 1, // numCohorts + 2); // numBloomHashes + // Use insecure encoder here as we want to get the exact output. + final RapporEncoder encoder2 = RapporEncoder.createInsecureEncoderForTest(config2); + assertEquals(prrAndIrrValue, toLong(encoder2.encodeBits(toBytes(prrValue)))); + + // Test that end-to-end is the result of PRR + IRR. + final RapporConfig config3 = new RapporConfig( + "Foo", // encoderId + numBits, // numBits, + 0.25, // probabilityF + 0.3, // probabilityP + 0.7, // probabilityQ + 1, // numCohorts + 2); // numBloomHashes + final RapporEncoder encoder3 = RapporEncoder.createInsecureEncoderForTest(config3); + // Verify that PRR is working as expected. + assertEquals(prrAndIrrValue, toLong(encoder3.encodeBits(toBytes(inputValue)))); + } + + @Test + public void testRapporEncoder_ensureSecureEncoderIsSecure() throws Exception { + int numBits = 8; + final long inputValue = 254L; + final long prrValue = 250L; + final long prrAndIrrValue = 184L; + + final RapporConfig config1 = new RapporConfig( + "Foo", // encoderId + numBits, // numBits, + 0.25, // probabilityF + 0, // probabilityP + 1, // probabilityQ + 1, // numCohorts + 2); // numBloomHashes + final RapporEncoder encoder1 = RapporEncoder.createEncoder(config1, + makeTestingUserSecret("secret1")); + // Verify that PRR is working as expected, not affected by random seed. + assertEquals(prrValue, toLong(encoder1.encodeBits(toBytes(inputValue)))); + assertFalse(encoder1.isInsecureEncoderForTest()); + + boolean hasDifferentResult2 = false; + for (int i = 0; i < 5; i++) { + final RapporConfig config2 = new RapporConfig( + "Foo", // encoderId + numBits, // numBits, + 0, // probabilityF + 0.3, // probabilityP + 0.7, // probabilityQ + 1, // numCohorts + 2); // numBloomHashes + final RapporEncoder encoder2 = RapporEncoder.createEncoder(config2, + makeTestingUserSecret("secret1")); + hasDifferentResult2 |= (prrAndIrrValue != toLong( + encoder2.encodeBits(toBytes(prrValue)))); + } + // Ensure it's not getting same result as it has random seed while encoder id and secret + // is the same. + assertTrue(hasDifferentResult2); + + boolean hasDifferentResults3 = false; + for (int i = 0; i < 5; i++) { + final RapporConfig config3 = new RapporConfig( + "Foo", // encoderId + numBits, // numBits, + 0.25, // probabilityF + 0.3, // probabilityP + 0.7, // probabilityQ + 1, // numCohorts + 2); // numBloomHashes + final RapporEncoder encoder3 = RapporEncoder.createEncoder(config3, + makeTestingUserSecret("secret1")); + hasDifferentResults3 |= (prrAndIrrValue != toLong( + encoder3.encodeBits(toBytes(inputValue)))); + } + // Ensure it's not getting same result as it has random seed while encoder id and secret + // is the same. + assertTrue(hasDifferentResults3); + } + + private static byte[] toBytes(long value) { + return ByteBuffer.allocate(8).order(ByteOrder.LITTLE_ENDIAN).putLong(value).array(); + } + + private static long toLong(byte[] bytes) { + ByteBuffer buffer = ByteBuffer.allocate(8).order(ByteOrder.LITTLE_ENDIAN).put(bytes); + buffer.rewind(); + return buffer.getLong(); + } + + private static byte[] makeTestingUserSecret(String testingSecret) throws Exception { + // We generate the fake user secret by concatenating three copies of the + // 16 byte MD5 hash of the testingSecret string encoded in UTF 8. + MessageDigest md5 = MessageDigest.getInstance("MD5"); + byte[] digest = md5.digest(testingSecret.getBytes(StandardCharsets.UTF_8)); + assertEquals(16, digest.length); + return ByteBuffer.allocate(48).put(digest).put(digest).put(digest).array(); + } +} diff --git a/libs/hwui/tests/macrobench/main.cpp b/libs/hwui/tests/macrobench/main.cpp index 8c0ca5c12d1e..0a9a74eccfc9 100644 --- a/libs/hwui/tests/macrobench/main.cpp +++ b/libs/hwui/tests/macrobench/main.cpp @@ -21,7 +21,6 @@ #include "hwui/Typeface.h" #include "protos/hwui.pb.h" -#include <../src/sysinfo.h> #include <benchmark/benchmark.h> #include <getopt.h> #include <pthread.h> @@ -345,9 +344,6 @@ int main(int argc, char* argv[]) { name_field_width += 5; benchmark::BenchmarkReporter::Context context; - context.num_cpus = benchmark::NumCPUs(); - context.mhz_per_cpu = benchmark::CyclesPerSecond() / 1000000.0f; - context.cpu_scaling_enabled = benchmark::CpuScalingEnabled(); context.name_field_width = name_field_width; gBenchmarkReporter->ReportContext(context); } diff --git a/libs/incident/Android.mk b/libs/incident/Android.mk index 8aa4b107c25e..5f3e4071afef 100644 --- a/libs/incident/Android.mk +++ b/libs/incident/Android.mk @@ -31,7 +31,6 @@ LOCAL_C_INCLUDES := \ LOCAL_SRC_FILES := \ ../../core/java/android/os/IIncidentManager.aidl \ - ../../core/java/android/os/IIncidentReportCompletedListener.aidl \ ../../core/java/android/os/IIncidentReportStatusListener.aidl \ src/IncidentReportArgs.cpp diff --git a/media/java/android/mtp/MtpDatabase.java b/media/java/android/mtp/MtpDatabase.java index ba29d2daaa0e..a647dcc2d4b9 100755 --- a/media/java/android/mtp/MtpDatabase.java +++ b/media/java/android/mtp/MtpDatabase.java @@ -30,6 +30,7 @@ import android.net.Uri; import android.os.BatteryManager; import android.os.RemoteException; import android.os.SystemProperties; +import android.os.storage.StorageVolume; import android.provider.MediaStore; import android.provider.MediaStore.Audio; import android.provider.MediaStore.Files; @@ -40,21 +41,31 @@ import android.view.WindowManager; import dalvik.system.CloseGuard; +import com.google.android.collect.Sets; + import java.io.File; -import java.io.IOException; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.Arrays; import java.util.HashMap; +import java.util.Iterator; import java.util.Locale; import java.util.concurrent.atomic.AtomicBoolean; +import java.util.stream.IntStream; +import java.util.stream.Stream; /** + * MtpDatabase provides an interface for MTP operations that MtpServer can use. To do this, it uses + * MtpStorageManager for filesystem operations and MediaProvider to get media metadata. File + * operations are also reflected in MediaProvider if possible. + * operations * {@hide} */ public class MtpDatabase implements AutoCloseable { - private static final String TAG = "MtpDatabase"; + private static final String TAG = MtpDatabase.class.getSimpleName(); - private final Context mUserContext; private final Context mContext; - private final String mPackageName; private final ContentProviderClient mMediaProvider; private final String mVolumeName; private final Uri mObjectsUri; @@ -63,86 +74,158 @@ public class MtpDatabase implements AutoCloseable { private final AtomicBoolean mClosed = new AtomicBoolean(); private final CloseGuard mCloseGuard = CloseGuard.get(); - // path to primary storage - private final String mMediaStoragePath; - // if not null, restrict all queries to these subdirectories - private final String[] mSubDirectories; - // where clause for restricting queries to files in mSubDirectories - private String mSubDirectoriesWhere; - // where arguments for restricting queries to files in mSubDirectories - private String[] mSubDirectoriesWhereArgs; - - private final HashMap<String, MtpStorage> mStorageMap = new HashMap<String, MtpStorage>(); + private final HashMap<String, MtpStorage> mStorageMap = new HashMap<>(); // cached property groups for single properties - private final HashMap<Integer, MtpPropertyGroup> mPropertyGroupsByProperty - = new HashMap<Integer, MtpPropertyGroup>(); + private final HashMap<Integer, MtpPropertyGroup> mPropertyGroupsByProperty = new HashMap<>(); // cached property groups for all properties for a given format - private final HashMap<Integer, MtpPropertyGroup> mPropertyGroupsByFormat - = new HashMap<Integer, MtpPropertyGroup>(); - - // true if the database has been modified in the current MTP session - private boolean mDatabaseModified; + private final HashMap<Integer, MtpPropertyGroup> mPropertyGroupsByFormat = new HashMap<>(); // SharedPreferences for writable MTP device properties private SharedPreferences mDeviceProperties; - private static final int DEVICE_PROPERTIES_DATABASE_VERSION = 1; - private static final String[] ID_PROJECTION = new String[] { - Files.FileColumns._ID, // 0 + // Cached device properties + private int mBatteryLevel; + private int mBatteryScale; + private int mDeviceType; + + private MtpServer mServer; + private MtpStorageManager mManager; + + private static final String PATH_WHERE = Files.FileColumns.DATA + "=?"; + private static final String[] ID_PROJECTION = new String[] {Files.FileColumns._ID}; + private static final String[] PATH_PROJECTION = new String[] {Files.FileColumns.DATA}; + private static final String NO_MEDIA = ".nomedia"; + + static { + System.loadLibrary("media_jni"); + } + + private static final int[] PLAYBACK_FORMATS = { + // allow transferring arbitrary files + MtpConstants.FORMAT_UNDEFINED, + + MtpConstants.FORMAT_ASSOCIATION, + MtpConstants.FORMAT_TEXT, + MtpConstants.FORMAT_HTML, + MtpConstants.FORMAT_WAV, + MtpConstants.FORMAT_MP3, + MtpConstants.FORMAT_MPEG, + MtpConstants.FORMAT_EXIF_JPEG, + MtpConstants.FORMAT_TIFF_EP, + MtpConstants.FORMAT_BMP, + MtpConstants.FORMAT_GIF, + MtpConstants.FORMAT_JFIF, + MtpConstants.FORMAT_PNG, + MtpConstants.FORMAT_TIFF, + MtpConstants.FORMAT_WMA, + MtpConstants.FORMAT_OGG, + MtpConstants.FORMAT_AAC, + MtpConstants.FORMAT_MP4_CONTAINER, + MtpConstants.FORMAT_MP2, + MtpConstants.FORMAT_3GP_CONTAINER, + MtpConstants.FORMAT_ABSTRACT_AV_PLAYLIST, + MtpConstants.FORMAT_WPL_PLAYLIST, + MtpConstants.FORMAT_M3U_PLAYLIST, + MtpConstants.FORMAT_PLS_PLAYLIST, + MtpConstants.FORMAT_XML_DOCUMENT, + MtpConstants.FORMAT_FLAC, + MtpConstants.FORMAT_DNG, + MtpConstants.FORMAT_HEIF, }; - private static final String[] PATH_PROJECTION = new String[] { - Files.FileColumns._ID, // 0 - Files.FileColumns.DATA, // 1 + + private static final int[] FILE_PROPERTIES = { + MtpConstants.PROPERTY_STORAGE_ID, + MtpConstants.PROPERTY_OBJECT_FORMAT, + MtpConstants.PROPERTY_PROTECTION_STATUS, + MtpConstants.PROPERTY_OBJECT_SIZE, + MtpConstants.PROPERTY_OBJECT_FILE_NAME, + MtpConstants.PROPERTY_DATE_MODIFIED, + MtpConstants.PROPERTY_PERSISTENT_UID, + MtpConstants.PROPERTY_PARENT_OBJECT, + MtpConstants.PROPERTY_NAME, + MtpConstants.PROPERTY_DISPLAY_NAME, + MtpConstants.PROPERTY_DATE_ADDED, }; - private static final String[] FORMAT_PROJECTION = new String[] { - Files.FileColumns._ID, // 0 - Files.FileColumns.FORMAT, // 1 + + private static final int[] AUDIO_PROPERTIES = { + MtpConstants.PROPERTY_ARTIST, + MtpConstants.PROPERTY_ALBUM_NAME, + MtpConstants.PROPERTY_ALBUM_ARTIST, + MtpConstants.PROPERTY_TRACK, + MtpConstants.PROPERTY_ORIGINAL_RELEASE_DATE, + MtpConstants.PROPERTY_DURATION, + MtpConstants.PROPERTY_GENRE, + MtpConstants.PROPERTY_COMPOSER, + MtpConstants.PROPERTY_AUDIO_WAVE_CODEC, + MtpConstants.PROPERTY_BITRATE_TYPE, + MtpConstants.PROPERTY_AUDIO_BITRATE, + MtpConstants.PROPERTY_NUMBER_OF_CHANNELS, + MtpConstants.PROPERTY_SAMPLE_RATE, }; - private static final String[] PATH_FORMAT_PROJECTION = new String[] { - Files.FileColumns._ID, // 0 - Files.FileColumns.DATA, // 1 - Files.FileColumns.FORMAT, // 2 + + private static final int[] VIDEO_PROPERTIES = { + MtpConstants.PROPERTY_ARTIST, + MtpConstants.PROPERTY_ALBUM_NAME, + MtpConstants.PROPERTY_DURATION, + MtpConstants.PROPERTY_DESCRIPTION, }; - private static final String[] OBJECT_INFO_PROJECTION = new String[] { - Files.FileColumns._ID, // 0 - Files.FileColumns.STORAGE_ID, // 1 - Files.FileColumns.FORMAT, // 2 - Files.FileColumns.PARENT, // 3 - Files.FileColumns.DATA, // 4 - Files.FileColumns.DATE_ADDED, // 5 - Files.FileColumns.DATE_MODIFIED, // 6 + + private static final int[] IMAGE_PROPERTIES = { + MtpConstants.PROPERTY_DESCRIPTION, }; - private static final String ID_WHERE = Files.FileColumns._ID + "=?"; - private static final String PATH_WHERE = Files.FileColumns.DATA + "=?"; - private static final String STORAGE_WHERE = Files.FileColumns.STORAGE_ID + "=?"; - private static final String FORMAT_WHERE = Files.FileColumns.FORMAT + "=?"; - private static final String PARENT_WHERE = Files.FileColumns.PARENT + "=?"; - private static final String STORAGE_FORMAT_WHERE = STORAGE_WHERE + " AND " - + Files.FileColumns.FORMAT + "=?"; - private static final String STORAGE_PARENT_WHERE = STORAGE_WHERE + " AND " - + Files.FileColumns.PARENT + "=?"; - private static final String FORMAT_PARENT_WHERE = FORMAT_WHERE + " AND " - + Files.FileColumns.PARENT + "=?"; - private static final String STORAGE_FORMAT_PARENT_WHERE = STORAGE_FORMAT_WHERE + " AND " - + Files.FileColumns.PARENT + "=?"; + private static final int[] DEVICE_PROPERTIES = { + MtpConstants.DEVICE_PROPERTY_SYNCHRONIZATION_PARTNER, + MtpConstants.DEVICE_PROPERTY_DEVICE_FRIENDLY_NAME, + MtpConstants.DEVICE_PROPERTY_IMAGE_SIZE, + MtpConstants.DEVICE_PROPERTY_BATTERY_LEVEL, + MtpConstants.DEVICE_PROPERTY_PERCEIVED_DEVICE_TYPE, + }; - private MtpServer mServer; + private int[] getSupportedObjectProperties(int format) { + switch (format) { + case MtpConstants.FORMAT_MP3: + case MtpConstants.FORMAT_WAV: + case MtpConstants.FORMAT_WMA: + case MtpConstants.FORMAT_OGG: + case MtpConstants.FORMAT_AAC: + return IntStream.concat(Arrays.stream(FILE_PROPERTIES), + Arrays.stream(AUDIO_PROPERTIES)).toArray(); + case MtpConstants.FORMAT_MPEG: + case MtpConstants.FORMAT_3GP_CONTAINER: + case MtpConstants.FORMAT_WMV: + return IntStream.concat(Arrays.stream(FILE_PROPERTIES), + Arrays.stream(VIDEO_PROPERTIES)).toArray(); + case MtpConstants.FORMAT_EXIF_JPEG: + case MtpConstants.FORMAT_GIF: + case MtpConstants.FORMAT_PNG: + case MtpConstants.FORMAT_BMP: + case MtpConstants.FORMAT_DNG: + case MtpConstants.FORMAT_HEIF: + return IntStream.concat(Arrays.stream(FILE_PROPERTIES), + Arrays.stream(IMAGE_PROPERTIES)).toArray(); + default: + return FILE_PROPERTIES; + } + } - // read from native code - private int mBatteryLevel; - private int mBatteryScale; + private int[] getSupportedDeviceProperties() { + return DEVICE_PROPERTIES; + } - private int mDeviceType; + private int[] getSupportedPlaybackFormats() { + return PLAYBACK_FORMATS; + } - static { - System.loadLibrary("media_jni"); + private int[] getSupportedCaptureFormats() { + // no capture formats yet + return null; } private BroadcastReceiver mBatteryReceiver = new BroadcastReceiver() { - @Override + @Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); if (action.equals(Intent.ACTION_BATTERY_CHANGED)) { @@ -160,61 +243,42 @@ public class MtpDatabase implements AutoCloseable { } }; - public MtpDatabase(Context context, Context userContext, String volumeName, String storagePath, + public MtpDatabase(Context context, Context userContext, String volumeName, String[] subDirectories) { native_setup(); - mContext = context; - mUserContext = userContext; - mPackageName = context.getPackageName(); mMediaProvider = userContext.getContentResolver() .acquireContentProviderClient(MediaStore.AUTHORITY); mVolumeName = volumeName; - mMediaStoragePath = storagePath; mObjectsUri = Files.getMtpObjectsUri(volumeName); mMediaScanner = new MediaScanner(context, mVolumeName); - - mSubDirectories = subDirectories; - if (subDirectories != null) { - // Compute "where" string for restricting queries to subdirectories - StringBuilder builder = new StringBuilder(); - builder.append("("); - int count = subDirectories.length; - for (int i = 0; i < count; i++) { - builder.append(Files.FileColumns.DATA + "=? OR " - + Files.FileColumns.DATA + " LIKE ?"); - if (i != count - 1) { - builder.append(" OR "); - } + mManager = new MtpStorageManager(new MtpStorageManager.MtpNotifier() { + @Override + public void sendObjectAdded(int id) { + if (MtpDatabase.this.mServer != null) + MtpDatabase.this.mServer.sendObjectAdded(id); } - builder.append(")"); - mSubDirectoriesWhere = builder.toString(); - - // Compute "where" arguments for restricting queries to subdirectories - mSubDirectoriesWhereArgs = new String[count * 2]; - for (int i = 0, j = 0; i < count; i++) { - String path = subDirectories[i]; - mSubDirectoriesWhereArgs[j++] = path; - mSubDirectoriesWhereArgs[j++] = path + "/%"; + + @Override + public void sendObjectRemoved(int id) { + if (MtpDatabase.this.mServer != null) + MtpDatabase.this.mServer.sendObjectRemoved(id); } - } + }, subDirectories == null ? null : Sets.newHashSet(subDirectories)); initDeviceProperties(context); mDeviceType = SystemProperties.getInt("sys.usb.mtp.device_type", 0); - mCloseGuard.open("close"); } public void setServer(MtpServer server) { mServer = server; - // always unregister before registering try { mContext.unregisterReceiver(mBatteryReceiver); } catch (IllegalArgumentException e) { // wasn't previously registered, ignore } - // register for battery notifications when we are connected if (server != null) { mContext.registerReceiver(mBatteryReceiver, @@ -224,6 +288,7 @@ public class MtpDatabase implements AutoCloseable { @Override public void close() { + mManager.close(); mCloseGuard.close(); if (mClosed.compareAndSet(false, true)) { mMediaScanner.close(); @@ -238,24 +303,32 @@ public class MtpDatabase implements AutoCloseable { if (mCloseGuard != null) { mCloseGuard.warnIfOpen(); } - close(); } finally { super.finalize(); } } - public void addStorage(MtpStorage storage) { - mStorageMap.put(storage.getPath(), storage); + public void addStorage(StorageVolume storage) { + MtpStorage mtpStorage = mManager.addMtpStorage(storage); + mStorageMap.put(storage.getPath(), mtpStorage); + mServer.addStorage(mtpStorage); } - public void removeStorage(MtpStorage storage) { + public void removeStorage(StorageVolume storage) { + MtpStorage mtpStorage = mStorageMap.get(storage.getPath()); + if (mtpStorage == null) { + return; + } + mServer.removeStorage(mtpStorage); + mManager.removeMtpStorage(mtpStorage); mStorageMap.remove(storage.getPath()); } private void initDeviceProperties(Context context) { final String devicePropertiesName = "device-properties"; - mDeviceProperties = context.getSharedPreferences(devicePropertiesName, Context.MODE_PRIVATE); + mDeviceProperties = context.getSharedPreferences(devicePropertiesName, + Context.MODE_PRIVATE); File databaseFile = context.getDatabasePath(devicePropertiesName); if (databaseFile.exists()) { @@ -266,7 +339,7 @@ public class MtpDatabase implements AutoCloseable { try { db = context.openOrCreateDatabase("device-properties", Context.MODE_PRIVATE, null); if (db != null) { - c = db.query("properties", new String[] { "_id", "code", "value" }, + c = db.query("properties", new String[]{"_id", "code", "value"}, null, null, null, null, null); if (c != null) { SharedPreferences.Editor e = mDeviceProperties.edit(); @@ -288,608 +361,371 @@ public class MtpDatabase implements AutoCloseable { } } - // check to see if the path is contained in one of our storage subdirectories - // returns true if we have no special subdirectories - private boolean inStorageSubDirectory(String path) { - if (mSubDirectories == null) return true; - if (path == null) return false; - - boolean allowed = false; - int pathLength = path.length(); - for (int i = 0; i < mSubDirectories.length && !allowed; i++) { - String subdir = mSubDirectories[i]; - int subdirLength = subdir.length(); - if (subdirLength < pathLength && - path.charAt(subdirLength) == '/' && - path.startsWith(subdir)) { - allowed = true; - } - } - return allowed; - } - - // check to see if the path matches one of our storage subdirectories - // returns true if we have no special subdirectories - private boolean isStorageSubDirectory(String path) { - if (mSubDirectories == null) return false; - for (int i = 0; i < mSubDirectories.length; i++) { - if (path.equals(mSubDirectories[i])) { - return true; - } + private int beginSendObject(String path, int format, int parent, int storageId) { + MtpStorageManager.MtpObject parentObj = + parent == 0 ? mManager.getStorageRoot(storageId) : mManager.getObject(parent); + if (parentObj == null) { + return -1; } - return false; - } - // returns true if the path is in the storage root - private boolean inStorageRoot(String path) { - try { - File f = new File(path); - String canonical = f.getCanonicalPath(); - for (String root: mStorageMap.keySet()) { - if (canonical.startsWith(root)) { - return true; - } - } - } catch (IOException e) { - // ignore - } - return false; + Path objPath = Paths.get(path); + return mManager.beginSendObject(parentObj, objPath.getFileName().toString(), format); } - private int beginSendObject(String path, int format, int parent, - int storageId, long size, long modified) { - // if the path is outside of the storage root, do not allow access - if (!inStorageRoot(path)) { - Log.e(TAG, "attempt to put file outside of storage area: " + path); - return -1; + private void endSendObject(int handle, boolean succeeded) { + MtpStorageManager.MtpObject obj = mManager.getObject(handle); + if (obj == null || !mManager.endSendObject(obj, succeeded)) { + Log.e(TAG, "Failed to successfully end send object"); + return; } - // if mSubDirectories is not null, do not allow copying files to any other locations - if (!inStorageSubDirectory(path)) return -1; - - // make sure the object does not exist - if (path != null) { - Cursor c = null; + // Add the new file to MediaProvider + if (succeeded) { + String path = obj.getPath().toString(); + int format = obj.getFormat(); + // Get parent info from MediaProvider, since the id is different from MTP's + ContentValues values = new ContentValues(); + values.put(Files.FileColumns.DATA, path); + values.put(Files.FileColumns.FORMAT, format); + values.put(Files.FileColumns.SIZE, obj.getSize()); + values.put(Files.FileColumns.DATE_MODIFIED, obj.getModifiedTime()); try { - c = mMediaProvider.query(mObjectsUri, ID_PROJECTION, PATH_WHERE, - new String[] { path }, null, null); - if (c != null && c.getCount() > 0) { - Log.w(TAG, "file already exists in beginSendObject: " + path); - return -1; + if (obj.getParent().isRoot()) { + values.put(Files.FileColumns.PARENT, 0); + } else { + int parentId = findInMedia(obj.getParent().getPath()); + if (parentId != -1) { + values.put(Files.FileColumns.PARENT, parentId); + } else { + // The parent isn't in MediaProvider. Don't add the new file. + return; + } + } + + Uri uri = mMediaProvider.insert(mObjectsUri, values); + if (uri != null) { + rescanFile(path, Integer.parseInt(uri.getPathSegments().get(2)), format); } } catch (RemoteException e) { Log.e(TAG, "RemoteException in beginSendObject", e); - } finally { - if (c != null) { - c.close(); - } - } - } - - mDatabaseModified = true; - ContentValues values = new ContentValues(); - values.put(Files.FileColumns.DATA, path); - values.put(Files.FileColumns.FORMAT, format); - values.put(Files.FileColumns.PARENT, parent); - values.put(Files.FileColumns.STORAGE_ID, storageId); - values.put(Files.FileColumns.SIZE, size); - values.put(Files.FileColumns.DATE_MODIFIED, modified); - - try { - Uri uri = mMediaProvider.insert(mObjectsUri, values); - if (uri != null) { - return Integer.parseInt(uri.getPathSegments().get(2)); - } else { - return -1; } - } catch (RemoteException e) { - Log.e(TAG, "RemoteException in beginSendObject", e); - return -1; } } - private void endSendObject(String path, int handle, int format, boolean succeeded) { - if (succeeded) { - // handle abstract playlists separately - // they do not exist in the file system so don't use the media scanner here - if (format == MtpConstants.FORMAT_ABSTRACT_AV_PLAYLIST) { - // extract name from path - String name = path; - int lastSlash = name.lastIndexOf('/'); - if (lastSlash >= 0) { - name = name.substring(lastSlash + 1); - } - // strip trailing ".pla" from the name - if (name.endsWith(".pla")) { - name = name.substring(0, name.length() - 4); - } + private void rescanFile(String path, int handle, int format) { + // handle abstract playlists separately + // they do not exist in the file system so don't use the media scanner here + if (format == MtpConstants.FORMAT_ABSTRACT_AV_PLAYLIST) { + // extract name from path + String name = path; + int lastSlash = name.lastIndexOf('/'); + if (lastSlash >= 0) { + name = name.substring(lastSlash + 1); + } + // strip trailing ".pla" from the name + if (name.endsWith(".pla")) { + name = name.substring(0, name.length() - 4); + } - ContentValues values = new ContentValues(1); - values.put(Audio.Playlists.DATA, path); - values.put(Audio.Playlists.NAME, name); - values.put(Files.FileColumns.FORMAT, format); - values.put(Files.FileColumns.DATE_MODIFIED, System.currentTimeMillis() / 1000); - values.put(MediaColumns.MEDIA_SCANNER_NEW_OBJECT_ID, handle); - try { - Uri uri = mMediaProvider.insert( - Audio.Playlists.EXTERNAL_CONTENT_URI, values); - } catch (RemoteException e) { - Log.e(TAG, "RemoteException in endSendObject", e); - } - } else { - mMediaScanner.scanMtpFile(path, handle, format); + ContentValues values = new ContentValues(1); + values.put(Audio.Playlists.DATA, path); + values.put(Audio.Playlists.NAME, name); + values.put(Files.FileColumns.FORMAT, format); + values.put(Files.FileColumns.DATE_MODIFIED, System.currentTimeMillis() / 1000); + values.put(MediaColumns.MEDIA_SCANNER_NEW_OBJECT_ID, handle); + try { + mMediaProvider.insert( + Audio.Playlists.EXTERNAL_CONTENT_URI, values); + } catch (RemoteException e) { + Log.e(TAG, "RemoteException in endSendObject", e); } } else { - deleteFile(handle); + mMediaScanner.scanMtpFile(path, handle, format); } } - private void doScanDirectory(String path) { - String[] scanPath; - scanPath = new String[] { path }; - mMediaScanner.scanDirectories(scanPath); + private int[] getObjectList(int storageID, int format, int parent) { + Stream<MtpStorageManager.MtpObject> objectStream = mManager.getObjects(parent, + format, storageID); + if (objectStream == null) { + return null; + } + return objectStream.mapToInt(MtpStorageManager.MtpObject::getId).toArray(); } - private Cursor createObjectQuery(int storageID, int format, int parent) throws RemoteException { - String where; - String[] whereArgs; - - if (storageID == 0xFFFFFFFF) { - // query all stores - if (format == 0) { - // query all formats - if (parent == 0) { - // query all objects - where = null; - whereArgs = null; - } else { - if (parent == 0xFFFFFFFF) { - // all objects in root of store - parent = 0; - } - where = PARENT_WHERE; - whereArgs = new String[] { Integer.toString(parent) }; - } - } else { - // query specific format - if (parent == 0) { - // query all objects - where = FORMAT_WHERE; - whereArgs = new String[] { Integer.toString(format) }; - } else { - if (parent == 0xFFFFFFFF) { - // all objects in root of store - parent = 0; - } - where = FORMAT_PARENT_WHERE; - whereArgs = new String[] { Integer.toString(format), - Integer.toString(parent) }; - } - } - } else { - // query specific store - if (format == 0) { - // query all formats - if (parent == 0) { - // query all objects - where = STORAGE_WHERE; - whereArgs = new String[] { Integer.toString(storageID) }; - } else { - if (parent == 0xFFFFFFFF) { - // all objects in root of store - parent = 0; - where = STORAGE_PARENT_WHERE; - whereArgs = new String[]{Integer.toString(storageID), - Integer.toString(parent)}; - } else { - // If a parent is specified, the storage is redundant - where = PARENT_WHERE; - whereArgs = new String[]{Integer.toString(parent)}; - } - } - } else { - // query specific format - if (parent == 0) { - // query all objects - where = STORAGE_FORMAT_WHERE; - whereArgs = new String[] { Integer.toString(storageID), - Integer.toString(format) }; - } else { - if (parent == 0xFFFFFFFF) { - // all objects in root of store - parent = 0; - where = STORAGE_FORMAT_PARENT_WHERE; - whereArgs = new String[]{Integer.toString(storageID), - Integer.toString(format), - Integer.toString(parent)}; - } else { - // If a parent is specified, the storage is redundant - where = FORMAT_PARENT_WHERE; - whereArgs = new String[]{Integer.toString(format), - Integer.toString(parent)}; - } - } - } + private int getNumObjects(int storageID, int format, int parent) { + Stream<MtpStorageManager.MtpObject> objectStream = mManager.getObjects(parent, + format, storageID); + if (objectStream == null) { + return -1; } + return (int) objectStream.count(); + } - // if we are restricting queries to mSubDirectories, we need to add the restriction - // onto our "where" arguments - if (mSubDirectoriesWhere != null) { - if (where == null) { - where = mSubDirectoriesWhere; - whereArgs = mSubDirectoriesWhereArgs; - } else { - where = where + " AND " + mSubDirectoriesWhere; - - // create new array to hold whereArgs and mSubDirectoriesWhereArgs - String[] newWhereArgs = - new String[whereArgs.length + mSubDirectoriesWhereArgs.length]; - int i, j; - for (i = 0; i < whereArgs.length; i++) { - newWhereArgs[i] = whereArgs[i]; - } - for (j = 0; j < mSubDirectoriesWhereArgs.length; i++, j++) { - newWhereArgs[i] = mSubDirectoriesWhereArgs[j]; - } - whereArgs = newWhereArgs; + private MtpPropertyList getObjectPropertyList(int handle, int format, int property, + int groupCode, int depth) { + // FIXME - implement group support + if (property == 0) { + if (groupCode == 0) { + return new MtpPropertyList(MtpConstants.RESPONSE_PARAMETER_NOT_SUPPORTED); } + return new MtpPropertyList(MtpConstants.RESPONSE_SPECIFICATION_BY_GROUP_UNSUPPORTED); } - - return mMediaProvider.query(mObjectsUri, ID_PROJECTION, where, - whereArgs, null, null); - } - - private int[] getObjectList(int storageID, int format, int parent) { - Cursor c = null; - try { - c = createObjectQuery(storageID, format, parent); - if (c == null) { - return null; + if (depth == 0xFFFFFFFF && (handle == 0 || handle == 0xFFFFFFFF)) { + // request all objects starting at root + handle = 0xFFFFFFFF; + depth = 0; + } + if (!(depth == 0 || depth == 1)) { + // we only support depth 0 and 1 + // depth 0: single object, depth 1: immediate children + return new MtpPropertyList(MtpConstants.RESPONSE_SPECIFICATION_BY_DEPTH_UNSUPPORTED); + } + Stream<MtpStorageManager.MtpObject> objectStream = Stream.of(); + if (handle == 0xFFFFFFFF) { + // All objects are requested + objectStream = mManager.getObjects(0, format, 0xFFFFFFFF); + if (objectStream == null) { + return new MtpPropertyList(MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE); } - int count = c.getCount(); - if (count > 0) { - int[] result = new int[count]; - for (int i = 0; i < count; i++) { - c.moveToNext(); - result[i] = c.getInt(0); - } - return result; + } else if (handle != 0) { + // Add the requested object if format matches + MtpStorageManager.MtpObject obj = mManager.getObject(handle); + if (obj == null) { + return new MtpPropertyList(MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE); } - } catch (RemoteException e) { - Log.e(TAG, "RemoteException in getObjectList", e); - } finally { - if (c != null) { - c.close(); + if (obj.getFormat() == format || format == 0) { + objectStream = Stream.of(obj); } } - return null; - } - - private int getNumObjects(int storageID, int format, int parent) { - Cursor c = null; - try { - c = createObjectQuery(storageID, format, parent); - if (c != null) { - return c.getCount(); + if (handle == 0 || depth == 1) { + if (handle == 0) { + handle = 0xFFFFFFFF; } - } catch (RemoteException e) { - Log.e(TAG, "RemoteException in getNumObjects", e); - } finally { - if (c != null) { - c.close(); + // Get the direct children of root or this object. + Stream<MtpStorageManager.MtpObject> childStream = mManager.getObjects(handle, format, + 0xFFFFFFFF); + if (childStream == null) { + return new MtpPropertyList(MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE); } - } - return -1; - } - - private int[] getSupportedPlaybackFormats() { - return new int[] { - // allow transfering arbitrary files - MtpConstants.FORMAT_UNDEFINED, - - MtpConstants.FORMAT_ASSOCIATION, - MtpConstants.FORMAT_TEXT, - MtpConstants.FORMAT_HTML, - MtpConstants.FORMAT_WAV, - MtpConstants.FORMAT_MP3, - MtpConstants.FORMAT_MPEG, - MtpConstants.FORMAT_EXIF_JPEG, - MtpConstants.FORMAT_TIFF_EP, - MtpConstants.FORMAT_BMP, - MtpConstants.FORMAT_GIF, - MtpConstants.FORMAT_JFIF, - MtpConstants.FORMAT_PNG, - MtpConstants.FORMAT_TIFF, - MtpConstants.FORMAT_WMA, - MtpConstants.FORMAT_OGG, - MtpConstants.FORMAT_AAC, - MtpConstants.FORMAT_MP4_CONTAINER, - MtpConstants.FORMAT_MP2, - MtpConstants.FORMAT_3GP_CONTAINER, - MtpConstants.FORMAT_ABSTRACT_AV_PLAYLIST, - MtpConstants.FORMAT_WPL_PLAYLIST, - MtpConstants.FORMAT_M3U_PLAYLIST, - MtpConstants.FORMAT_PLS_PLAYLIST, - MtpConstants.FORMAT_XML_DOCUMENT, - MtpConstants.FORMAT_FLAC, - MtpConstants.FORMAT_DNG, - MtpConstants.FORMAT_HEIF, - }; - } - - private int[] getSupportedCaptureFormats() { - // no capture formats yet - return null; - } - - static final int[] FILE_PROPERTIES = { - // NOTE must match beginning of AUDIO_PROPERTIES, VIDEO_PROPERTIES - // and IMAGE_PROPERTIES below - MtpConstants.PROPERTY_STORAGE_ID, - MtpConstants.PROPERTY_OBJECT_FORMAT, - MtpConstants.PROPERTY_PROTECTION_STATUS, - MtpConstants.PROPERTY_OBJECT_SIZE, - MtpConstants.PROPERTY_OBJECT_FILE_NAME, - MtpConstants.PROPERTY_DATE_MODIFIED, - MtpConstants.PROPERTY_PARENT_OBJECT, - MtpConstants.PROPERTY_PERSISTENT_UID, - MtpConstants.PROPERTY_NAME, - MtpConstants.PROPERTY_DISPLAY_NAME, - MtpConstants.PROPERTY_DATE_ADDED, - }; - - static final int[] AUDIO_PROPERTIES = { - // NOTE must match FILE_PROPERTIES above - MtpConstants.PROPERTY_STORAGE_ID, - MtpConstants.PROPERTY_OBJECT_FORMAT, - MtpConstants.PROPERTY_PROTECTION_STATUS, - MtpConstants.PROPERTY_OBJECT_SIZE, - MtpConstants.PROPERTY_OBJECT_FILE_NAME, - MtpConstants.PROPERTY_DATE_MODIFIED, - MtpConstants.PROPERTY_PARENT_OBJECT, - MtpConstants.PROPERTY_PERSISTENT_UID, - MtpConstants.PROPERTY_NAME, - MtpConstants.PROPERTY_DISPLAY_NAME, - MtpConstants.PROPERTY_DATE_ADDED, - - // audio specific properties - MtpConstants.PROPERTY_ARTIST, - MtpConstants.PROPERTY_ALBUM_NAME, - MtpConstants.PROPERTY_ALBUM_ARTIST, - MtpConstants.PROPERTY_TRACK, - MtpConstants.PROPERTY_ORIGINAL_RELEASE_DATE, - MtpConstants.PROPERTY_DURATION, - MtpConstants.PROPERTY_GENRE, - MtpConstants.PROPERTY_COMPOSER, - MtpConstants.PROPERTY_AUDIO_WAVE_CODEC, - MtpConstants.PROPERTY_BITRATE_TYPE, - MtpConstants.PROPERTY_AUDIO_BITRATE, - MtpConstants.PROPERTY_NUMBER_OF_CHANNELS, - MtpConstants.PROPERTY_SAMPLE_RATE, - }; - - static final int[] VIDEO_PROPERTIES = { - // NOTE must match FILE_PROPERTIES above - MtpConstants.PROPERTY_STORAGE_ID, - MtpConstants.PROPERTY_OBJECT_FORMAT, - MtpConstants.PROPERTY_PROTECTION_STATUS, - MtpConstants.PROPERTY_OBJECT_SIZE, - MtpConstants.PROPERTY_OBJECT_FILE_NAME, - MtpConstants.PROPERTY_DATE_MODIFIED, - MtpConstants.PROPERTY_PARENT_OBJECT, - MtpConstants.PROPERTY_PERSISTENT_UID, - MtpConstants.PROPERTY_NAME, - MtpConstants.PROPERTY_DISPLAY_NAME, - MtpConstants.PROPERTY_DATE_ADDED, - - // video specific properties - MtpConstants.PROPERTY_ARTIST, - MtpConstants.PROPERTY_ALBUM_NAME, - MtpConstants.PROPERTY_DURATION, - MtpConstants.PROPERTY_DESCRIPTION, - }; - - static final int[] IMAGE_PROPERTIES = { - // NOTE must match FILE_PROPERTIES above - MtpConstants.PROPERTY_STORAGE_ID, - MtpConstants.PROPERTY_OBJECT_FORMAT, - MtpConstants.PROPERTY_PROTECTION_STATUS, - MtpConstants.PROPERTY_OBJECT_SIZE, - MtpConstants.PROPERTY_OBJECT_FILE_NAME, - MtpConstants.PROPERTY_DATE_MODIFIED, - MtpConstants.PROPERTY_PARENT_OBJECT, - MtpConstants.PROPERTY_PERSISTENT_UID, - MtpConstants.PROPERTY_NAME, - MtpConstants.PROPERTY_DISPLAY_NAME, - MtpConstants.PROPERTY_DATE_ADDED, - - // image specific properties - MtpConstants.PROPERTY_DESCRIPTION, - }; - - private int[] getSupportedObjectProperties(int format) { - switch (format) { - case MtpConstants.FORMAT_MP3: - case MtpConstants.FORMAT_WAV: - case MtpConstants.FORMAT_WMA: - case MtpConstants.FORMAT_OGG: - case MtpConstants.FORMAT_AAC: - return AUDIO_PROPERTIES; - case MtpConstants.FORMAT_MPEG: - case MtpConstants.FORMAT_3GP_CONTAINER: - case MtpConstants.FORMAT_WMV: - return VIDEO_PROPERTIES; - case MtpConstants.FORMAT_EXIF_JPEG: - case MtpConstants.FORMAT_GIF: - case MtpConstants.FORMAT_PNG: - case MtpConstants.FORMAT_BMP: - case MtpConstants.FORMAT_DNG: - case MtpConstants.FORMAT_HEIF: - return IMAGE_PROPERTIES; - default: - return FILE_PROPERTIES; - } - } - - private int[] getSupportedDeviceProperties() { - return new int[] { - MtpConstants.DEVICE_PROPERTY_SYNCHRONIZATION_PARTNER, - MtpConstants.DEVICE_PROPERTY_DEVICE_FRIENDLY_NAME, - MtpConstants.DEVICE_PROPERTY_IMAGE_SIZE, - MtpConstants.DEVICE_PROPERTY_BATTERY_LEVEL, - MtpConstants.DEVICE_PROPERTY_PERCEIVED_DEVICE_TYPE, - }; - } - - private MtpPropertyList getObjectPropertyList(int handle, int format, int property, - int groupCode, int depth) { - // FIXME - implement group support - if (groupCode != 0) { - return new MtpPropertyList(0, MtpConstants.RESPONSE_SPECIFICATION_BY_GROUP_UNSUPPORTED); + objectStream = Stream.concat(objectStream, childStream); } + MtpPropertyList ret = new MtpPropertyList(MtpConstants.RESPONSE_OK); MtpPropertyGroup propertyGroup; - if (property == 0xffffffff) { - if (format == 0 && handle != 0 && handle != 0xffffffff) { - // return properties based on the object's format - format = getObjectFormat(handle); - } - propertyGroup = mPropertyGroupsByFormat.get(format); - if (propertyGroup == null) { - int[] propertyList = getSupportedObjectProperties(format); - propertyGroup = new MtpPropertyGroup(this, mMediaProvider, - mVolumeName, propertyList); - mPropertyGroupsByFormat.put(format, propertyGroup); + Iterator<MtpStorageManager.MtpObject> iter = objectStream.iterator(); + while (iter.hasNext()) { + MtpStorageManager.MtpObject obj = iter.next(); + if (property == 0xffffffff) { + // Get all properties supported by this object + propertyGroup = mPropertyGroupsByFormat.get(obj.getFormat()); + if (propertyGroup == null) { + int[] propertyList = getSupportedObjectProperties(format); + propertyGroup = new MtpPropertyGroup(mMediaProvider, mVolumeName, + propertyList); + mPropertyGroupsByFormat.put(format, propertyGroup); + } + } else { + // Get this property value + final int[] propertyList = new int[]{property}; + propertyGroup = mPropertyGroupsByProperty.get(property); + if (propertyGroup == null) { + propertyGroup = new MtpPropertyGroup(mMediaProvider, mVolumeName, + propertyList); + mPropertyGroupsByProperty.put(property, propertyGroup); + } } - } else { - propertyGroup = mPropertyGroupsByProperty.get(property); - if (propertyGroup == null) { - final int[] propertyList = new int[] { property }; - propertyGroup = new MtpPropertyGroup( - this, mMediaProvider, mVolumeName, propertyList); - mPropertyGroupsByProperty.put(property, propertyGroup); + int err = propertyGroup.getPropertyList(obj, ret); + if (err != MtpConstants.RESPONSE_OK) { + return new MtpPropertyList(err); } } - - return propertyGroup.getPropertyList(handle, format, depth); + return ret; } private int renameFile(int handle, String newName) { - Cursor c = null; - - // first compute current path - String path = null; - String[] whereArgs = new String[] { Integer.toString(handle) }; - try { - c = mMediaProvider.query(mObjectsUri, PATH_PROJECTION, ID_WHERE, - whereArgs, null, null); - if (c != null && c.moveToNext()) { - path = c.getString(1); - } - } catch (RemoteException e) { - Log.e(TAG, "RemoteException in getObjectFilePath", e); - return MtpConstants.RESPONSE_GENERAL_ERROR; - } finally { - if (c != null) { - c.close(); - } - } - if (path == null) { + MtpStorageManager.MtpObject obj = mManager.getObject(handle); + if (obj == null) { return MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE; } - - // do not allow renaming any of the special subdirectories - if (isStorageSubDirectory(path)) { - return MtpConstants.RESPONSE_OBJECT_WRITE_PROTECTED; - } + Path oldPath = obj.getPath(); // now rename the file. make sure this succeeds before updating database - File oldFile = new File(path); - int lastSlash = path.lastIndexOf('/'); - if (lastSlash <= 1) { + if (!mManager.beginRenameObject(obj, newName)) return MtpConstants.RESPONSE_GENERAL_ERROR; + Path newPath = obj.getPath(); + boolean success = oldPath.toFile().renameTo(newPath.toFile()); + if (!mManager.endRenameObject(obj, oldPath.getFileName().toString(), success)) { + Log.e(TAG, "Failed to end rename object"); } - String newPath = path.substring(0, lastSlash + 1) + newName; - File newFile = new File(newPath); - boolean success = oldFile.renameTo(newFile); if (!success) { - Log.w(TAG, "renaming "+ path + " to " + newPath + " failed"); return MtpConstants.RESPONSE_GENERAL_ERROR; } - // finally update database + // finally update MediaProvider ContentValues values = new ContentValues(); - values.put(Files.FileColumns.DATA, newPath); - int updated = 0; + values.put(Files.FileColumns.DATA, newPath.toString()); + String[] whereArgs = new String[]{oldPath.toString()}; try { // note - we are relying on a special case in MediaProvider.update() to update // the paths for all children in the case where this is a directory. - updated = mMediaProvider.update(mObjectsUri, values, ID_WHERE, whereArgs); + mMediaProvider.update(mObjectsUri, values, PATH_WHERE, whereArgs); } catch (RemoteException e) { Log.e(TAG, "RemoteException in mMediaProvider.update", e); } - if (updated == 0) { - Log.e(TAG, "Unable to update path for " + path + " to " + newPath); - // this shouldn't happen, but if it does we need to rename the file to its original name - newFile.renameTo(oldFile); - return MtpConstants.RESPONSE_GENERAL_ERROR; - } // check if nomedia status changed - if (newFile.isDirectory()) { + if (obj.isDir()) { // for directories, check if renamed from something hidden to something non-hidden - if (oldFile.getName().startsWith(".") && !newPath.startsWith(".")) { + if (oldPath.getFileName().startsWith(".") && !newPath.startsWith(".")) { // directory was unhidden try { - mMediaProvider.call(MediaStore.UNHIDE_CALL, newPath, null); + mMediaProvider.call(MediaStore.UNHIDE_CALL, newPath.toString(), null); } catch (RemoteException e) { Log.e(TAG, "failed to unhide/rescan for " + newPath); } } } else { // for files, check if renamed from .nomedia to something else - if (oldFile.getName().toLowerCase(Locale.US).equals(".nomedia") - && !newPath.toLowerCase(Locale.US).equals(".nomedia")) { + if (oldPath.getFileName().toString().toLowerCase(Locale.US).equals(NO_MEDIA) + && !newPath.getFileName().toString().toLowerCase(Locale.US).equals(NO_MEDIA)) { try { - mMediaProvider.call(MediaStore.UNHIDE_CALL, oldFile.getParent(), null); + mMediaProvider.call(MediaStore.UNHIDE_CALL, + oldPath.getParent().toString(), null); } catch (RemoteException e) { Log.e(TAG, "failed to unhide/rescan for " + newPath); } } } - return MtpConstants.RESPONSE_OK; } - private int moveObject(int handle, int newParent, int newStorage, String newPath) { - String[] whereArgs = new String[] { Integer.toString(handle) }; + private int beginMoveObject(int handle, int newParent, int newStorage) { + MtpStorageManager.MtpObject obj = mManager.getObject(handle); + MtpStorageManager.MtpObject parent = newParent == 0 ? + mManager.getStorageRoot(newStorage) : mManager.getObject(newParent); + if (obj == null || parent == null) + return MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE; + + boolean allowed = mManager.beginMoveObject(obj, parent); + return allowed ? MtpConstants.RESPONSE_OK : MtpConstants.RESPONSE_GENERAL_ERROR; + } - // do not allow renaming any of the special subdirectories - if (isStorageSubDirectory(newPath)) { - return MtpConstants.RESPONSE_OBJECT_WRITE_PROTECTED; + private void endMoveObject(int oldParent, int newParent, int oldStorage, int newStorage, + int objId, boolean success) { + MtpStorageManager.MtpObject oldParentObj = oldParent == 0 ? + mManager.getStorageRoot(oldStorage) : mManager.getObject(oldParent); + MtpStorageManager.MtpObject newParentObj = newParent == 0 ? + mManager.getStorageRoot(newStorage) : mManager.getObject(newParent); + MtpStorageManager.MtpObject obj = mManager.getObject(objId); + String name = obj.getName(); + if (newParentObj == null || oldParentObj == null + ||!mManager.endMoveObject(oldParentObj, newParentObj, name, success)) { + Log.e(TAG, "Failed to end move object"); + return; } - // update database + obj = mManager.getObject(objId); + if (!success || obj == null) + return; + // Get parent info from MediaProvider, since the id is different from MTP's ContentValues values = new ContentValues(); - values.put(Files.FileColumns.DATA, newPath); - values.put(Files.FileColumns.PARENT, newParent); - values.put(Files.FileColumns.STORAGE_ID, newStorage); - int updated = 0; + Path path = newParentObj.getPath().resolve(name); + Path oldPath = oldParentObj.getPath().resolve(name); + values.put(Files.FileColumns.DATA, path.toString()); + if (obj.getParent().isRoot()) { + values.put(Files.FileColumns.PARENT, 0); + } else { + int parentId = findInMedia(path.getParent()); + if (parentId != -1) { + values.put(Files.FileColumns.PARENT, parentId); + } else { + // The new parent isn't in MediaProvider, so delete the object instead + deleteFromMedia(oldPath, obj.isDir()); + return; + } + } + // update MediaProvider + Cursor c = null; + String[] whereArgs = new String[]{oldPath.toString()}; try { - // note - we are relying on a special case in MediaProvider.update() to update - // the paths for all children in the case where this is a directory. - updated = mMediaProvider.update(mObjectsUri, values, ID_WHERE, whereArgs); + int parentId = -1; + if (!oldParentObj.isRoot()) { + parentId = findInMedia(oldPath.getParent()); + } + if (oldParentObj.isRoot() || parentId != -1) { + // Old parent exists in MediaProvider - perform a move + // note - we are relying on a special case in MediaProvider.update() to update + // the paths for all children in the case where this is a directory. + mMediaProvider.update(mObjectsUri, values, PATH_WHERE, whereArgs); + } else { + // Old parent doesn't exist - add the object + values.put(Files.FileColumns.FORMAT, obj.getFormat()); + values.put(Files.FileColumns.SIZE, obj.getSize()); + values.put(Files.FileColumns.DATE_MODIFIED, obj.getModifiedTime()); + Uri uri = mMediaProvider.insert(mObjectsUri, values); + if (uri != null) { + rescanFile(path.toString(), + Integer.parseInt(uri.getPathSegments().get(2)), obj.getFormat()); + } + } } catch (RemoteException e) { Log.e(TAG, "RemoteException in mMediaProvider.update", e); } - if (updated == 0) { - Log.e(TAG, "Unable to update path for " + handle + " to " + newPath); - return MtpConstants.RESPONSE_GENERAL_ERROR; + } + + private int beginCopyObject(int handle, int newParent, int newStorage) { + MtpStorageManager.MtpObject obj = mManager.getObject(handle); + MtpStorageManager.MtpObject parent = newParent == 0 ? + mManager.getStorageRoot(newStorage) : mManager.getObject(newParent); + if (obj == null || parent == null) + return MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE; + return mManager.beginCopyObject(obj, parent); + } + + private void endCopyObject(int handle, boolean success) { + MtpStorageManager.MtpObject obj = mManager.getObject(handle); + if (obj == null || !mManager.endCopyObject(obj, success)) { + Log.e(TAG, "Failed to end copy object"); + return; + } + if (!success) { + return; + } + String path = obj.getPath().toString(); + int format = obj.getFormat(); + // Get parent info from MediaProvider, since the id is different from MTP's + ContentValues values = new ContentValues(); + values.put(Files.FileColumns.DATA, path); + values.put(Files.FileColumns.FORMAT, format); + values.put(Files.FileColumns.SIZE, obj.getSize()); + values.put(Files.FileColumns.DATE_MODIFIED, obj.getModifiedTime()); + try { + if (obj.getParent().isRoot()) { + values.put(Files.FileColumns.PARENT, 0); + } else { + int parentId = findInMedia(obj.getParent().getPath()); + if (parentId != -1) { + values.put(Files.FileColumns.PARENT, parentId); + } else { + // The parent isn't in MediaProvider. Don't add the new file. + return; + } + } + if (obj.isDir()) { + mMediaScanner.scanDirectories(new String[]{path}); + } else { + Uri uri = mMediaProvider.insert(mObjectsUri, values); + if (uri != null) { + rescanFile(path, Integer.parseInt(uri.getPathSegments().get(2)), format); + } + } + } catch (RemoteException e) { + Log.e(TAG, "RemoteException in beginSendObject", e); } - return MtpConstants.RESPONSE_OK; } private int setObjectProperty(int handle, int property, - long intValue, String stringValue) { + long intValue, String stringValue) { switch (property) { case MtpConstants.PROPERTY_OBJECT_FILE_NAME: return renameFile(handle, stringValue); @@ -912,24 +748,23 @@ public class MtpDatabase implements AutoCloseable { value.getChars(0, length, outStringValue, 0); outStringValue[length] = 0; return MtpConstants.RESPONSE_OK; - case MtpConstants.DEVICE_PROPERTY_IMAGE_SIZE: // use screen size as max image size - Display display = ((WindowManager)mContext.getSystemService( + Display display = ((WindowManager) mContext.getSystemService( Context.WINDOW_SERVICE)).getDefaultDisplay(); int width = display.getMaximumSizeDimension(); int height = display.getMaximumSizeDimension(); - String imageSize = Integer.toString(width) + "x" + Integer.toString(height); + String imageSize = Integer.toString(width) + "x" + Integer.toString(height); imageSize.getChars(0, imageSize.length(), outStringValue, 0); outStringValue[imageSize.length()] = 0; return MtpConstants.RESPONSE_OK; - case MtpConstants.DEVICE_PROPERTY_PERCEIVED_DEVICE_TYPE: outIntValue[0] = mDeviceType; return MtpConstants.RESPONSE_OK; - - // DEVICE_PROPERTY_BATTERY_LEVEL is implemented in the JNI code - + case MtpConstants.DEVICE_PROPERTY_BATTERY_LEVEL: + outIntValue[0] = mBatteryLevel; + outIntValue[1] = mBatteryScale; + return MtpConstants.RESPONSE_OK; default: return MtpConstants.RESPONSE_DEVICE_PROP_NOT_SUPPORTED; } @@ -950,179 +785,144 @@ public class MtpDatabase implements AutoCloseable { } private boolean getObjectInfo(int handle, int[] outStorageFormatParent, - char[] outName, long[] outCreatedModified) { - Cursor c = null; - try { - c = mMediaProvider.query(mObjectsUri, OBJECT_INFO_PROJECTION, - ID_WHERE, new String[] { Integer.toString(handle) }, null, null); - if (c != null && c.moveToNext()) { - outStorageFormatParent[0] = c.getInt(1); - outStorageFormatParent[1] = c.getInt(2); - outStorageFormatParent[2] = c.getInt(3); - - // extract name from path - String path = c.getString(4); - int lastSlash = path.lastIndexOf('/'); - int start = (lastSlash >= 0 ? lastSlash + 1 : 0); - int end = path.length(); - if (end - start > 255) { - end = start + 255; - } - path.getChars(start, end, outName, 0); - outName[end - start] = 0; - - outCreatedModified[0] = c.getLong(5); - outCreatedModified[1] = c.getLong(6); - // use modification date as creation date if date added is not set - if (outCreatedModified[0] == 0) { - outCreatedModified[0] = outCreatedModified[1]; - } - return true; - } - } catch (RemoteException e) { - Log.e(TAG, "RemoteException in getObjectInfo", e); - } finally { - if (c != null) { - c.close(); - } + char[] outName, long[] outCreatedModified) { + MtpStorageManager.MtpObject obj = mManager.getObject(handle); + if (obj == null) { + return false; } - return false; + outStorageFormatParent[0] = obj.getStorageId(); + outStorageFormatParent[1] = obj.getFormat(); + outStorageFormatParent[2] = obj.getParent().isRoot() ? 0 : obj.getParent().getId(); + + int nameLen = Integer.min(obj.getName().length(), 255); + obj.getName().getChars(0, nameLen, outName, 0); + outName[nameLen] = 0; + + outCreatedModified[0] = obj.getModifiedTime(); + outCreatedModified[1] = obj.getModifiedTime(); + return true; } private int getObjectFilePath(int handle, char[] outFilePath, long[] outFileLengthFormat) { - if (handle == 0) { - // special case root directory - mMediaStoragePath.getChars(0, mMediaStoragePath.length(), outFilePath, 0); - outFilePath[mMediaStoragePath.length()] = 0; - outFileLengthFormat[0] = 0; - outFileLengthFormat[1] = MtpConstants.FORMAT_ASSOCIATION; - return MtpConstants.RESPONSE_OK; + MtpStorageManager.MtpObject obj = mManager.getObject(handle); + if (obj == null) { + return MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE; } - Cursor c = null; - try { - c = mMediaProvider.query(mObjectsUri, PATH_FORMAT_PROJECTION, - ID_WHERE, new String[] { Integer.toString(handle) }, null, null); - if (c != null && c.moveToNext()) { - String path = c.getString(1); - path.getChars(0, path.length(), outFilePath, 0); - outFilePath[path.length()] = 0; - // File transfers from device to host will likely fail if the size is incorrect. - // So to be safe, use the actual file size here. - outFileLengthFormat[0] = new File(path).length(); - outFileLengthFormat[1] = c.getLong(2); - return MtpConstants.RESPONSE_OK; - } else { - return MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE; - } - } catch (RemoteException e) { - Log.e(TAG, "RemoteException in getObjectFilePath", e); + + String path = obj.getPath().toString(); + int pathLen = Integer.min(path.length(), 4096); + path.getChars(0, pathLen, outFilePath, 0); + outFilePath[pathLen] = 0; + + outFileLengthFormat[0] = obj.getSize(); + outFileLengthFormat[1] = obj.getFormat(); + return MtpConstants.RESPONSE_OK; + } + + private int getObjectFormat(int handle) { + MtpStorageManager.MtpObject obj = mManager.getObject(handle); + if (obj == null) { + return -1; + } + return obj.getFormat(); + } + + private int beginDeleteObject(int handle) { + MtpStorageManager.MtpObject obj = mManager.getObject(handle); + if (obj == null) { + return MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE; + } + if (!mManager.beginRemoveObject(obj)) { return MtpConstants.RESPONSE_GENERAL_ERROR; - } finally { - if (c != null) { - c.close(); - } } + return MtpConstants.RESPONSE_OK; } - private int getObjectFormat(int handle) { + private void endDeleteObject(int handle, boolean success) { + MtpStorageManager.MtpObject obj = mManager.getObject(handle); + if (obj == null) { + return; + } + if (!mManager.endRemoveObject(obj, success)) + Log.e(TAG, "Failed to end remove object"); + if (success) + deleteFromMedia(obj.getPath(), obj.isDir()); + } + + private int findInMedia(Path path) { + int ret = -1; Cursor c = null; try { - c = mMediaProvider.query(mObjectsUri, FORMAT_PROJECTION, - ID_WHERE, new String[] { Integer.toString(handle) }, null, null); + c = mMediaProvider.query(mObjectsUri, ID_PROJECTION, PATH_WHERE, + new String[]{path.toString()}, null, null); if (c != null && c.moveToNext()) { - return c.getInt(1); - } else { - return -1; + ret = c.getInt(0); } } catch (RemoteException e) { - Log.e(TAG, "RemoteException in getObjectFilePath", e); - return -1; + Log.e(TAG, "Error finding " + path + " in MediaProvider"); } finally { - if (c != null) { + if (c != null) c.close(); - } } + return ret; } - private int deleteFile(int handle) { - mDatabaseModified = true; - String path = null; - int format = 0; - - Cursor c = null; + private void deleteFromMedia(Path path, boolean isDir) { try { - c = mMediaProvider.query(mObjectsUri, PATH_FORMAT_PROJECTION, - ID_WHERE, new String[] { Integer.toString(handle) }, null, null); - if (c != null && c.moveToNext()) { - // don't convert to media path here, since we will be matching - // against paths in the database matching /data/media - path = c.getString(1); - format = c.getInt(2); - } else { - return MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE; - } - - if (path == null || format == 0) { - return MtpConstants.RESPONSE_GENERAL_ERROR; - } - - // do not allow deleting any of the special subdirectories - if (isStorageSubDirectory(path)) { - return MtpConstants.RESPONSE_OBJECT_WRITE_PROTECTED; - } - - if (format == MtpConstants.FORMAT_ASSOCIATION) { + // Delete the object(s) from MediaProvider, but ignore errors. + if (isDir) { // recursive case - delete all children first - Uri uri = Files.getMtpObjectsUri(mVolumeName); - int count = mMediaProvider.delete(uri, - // the 'like' makes it use the index, the 'lower()' makes it correct - // when the path contains sqlite wildcard characters - "_data LIKE ?1 AND lower(substr(_data,1,?2))=lower(?3)", - new String[] { path + "/%",Integer.toString(path.length() + 1), path + "/"}); + mMediaProvider.delete(mObjectsUri, + // the 'like' makes it use the index, the 'lower()' makes it correct + // when the path contains sqlite wildcard characters + "_data LIKE ?1 AND lower(substr(_data,1,?2))=lower(?3)", + new String[]{path + "/%", Integer.toString(path.toString().length() + 1), + path.toString() + "/"}); } - Uri uri = Files.getMtpObjectsUri(mVolumeName, handle); - if (mMediaProvider.delete(uri, null, null) > 0) { - if (format != MtpConstants.FORMAT_ASSOCIATION - && path.toLowerCase(Locale.US).endsWith("/.nomedia")) { + String[] whereArgs = new String[]{path.toString()}; + if (mMediaProvider.delete(mObjectsUri, PATH_WHERE, whereArgs) > 0) { + if (!isDir && path.toString().toLowerCase(Locale.US).endsWith(NO_MEDIA)) { try { - String parentPath = path.substring(0, path.lastIndexOf("/")); + String parentPath = path.getParent().toString(); mMediaProvider.call(MediaStore.UNHIDE_CALL, parentPath, null); } catch (RemoteException e) { Log.e(TAG, "failed to unhide/rescan for " + path); } } - return MtpConstants.RESPONSE_OK; } else { - return MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE; - } - } catch (RemoteException e) { - Log.e(TAG, "RemoteException in deleteFile", e); - return MtpConstants.RESPONSE_GENERAL_ERROR; - } finally { - if (c != null) { - c.close(); + Log.i(TAG, "Mediaprovider didn't delete " + path); } + } catch (Exception e) { + Log.d(TAG, "Failed to delete " + path + " from MediaProvider"); } } private int[] getObjectReferences(int handle) { + MtpStorageManager.MtpObject obj = mManager.getObject(handle); + if (obj == null) + return null; + // Translate this handle to the MediaProvider Handle + handle = findInMedia(obj.getPath()); + if (handle == -1) + return null; Uri uri = Files.getMtpReferencesUri(mVolumeName, handle); Cursor c = null; try { - c = mMediaProvider.query(uri, ID_PROJECTION, null, null, null, null); + c = mMediaProvider.query(uri, PATH_PROJECTION, null, null, null, null); if (c == null) { return null; } - int count = c.getCount(); - if (count > 0) { - int[] result = new int[count]; - for (int i = 0; i < count; i++) { - c.moveToNext(); - result[i] = c.getInt(0); + ArrayList<Integer> result = new ArrayList<>(); + while (c.moveToNext()) { + // Translate result handles back into handles for this session. + String refPath = c.getString(0); + MtpStorageManager.MtpObject refObj = mManager.getByPath(refPath); + if (refObj != null) { + result.add(refObj.getId()); + } } - return result; - } + return result.stream().mapToInt(Integer::intValue).toArray(); } catch (RemoteException e) { Log.e(TAG, "RemoteException in getObjectList", e); } finally { @@ -1134,17 +934,29 @@ public class MtpDatabase implements AutoCloseable { } private int setObjectReferences(int handle, int[] references) { - mDatabaseModified = true; + MtpStorageManager.MtpObject obj = mManager.getObject(handle); + if (obj == null) + return MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE; + // Translate this handle to the MediaProvider Handle + handle = findInMedia(obj.getPath()); + if (handle == -1) + return MtpConstants.RESPONSE_GENERAL_ERROR; Uri uri = Files.getMtpReferencesUri(mVolumeName, handle); - int count = references.length; - ContentValues[] valuesList = new ContentValues[count]; - for (int i = 0; i < count; i++) { + ArrayList<ContentValues> valuesList = new ArrayList<>(); + for (int id : references) { + // Translate each reference id to the MediaProvider Id + MtpStorageManager.MtpObject refObj = mManager.getObject(id); + if (refObj == null) + continue; + int refHandle = findInMedia(refObj.getPath()); + if (refHandle == -1) + continue; ContentValues values = new ContentValues(); - values.put(Files.FileColumns._ID, references[i]); - valuesList[i] = values; + values.put(Files.FileColumns._ID, refHandle); + valuesList.add(values); } try { - if (mMediaProvider.bulkInsert(uri, valuesList) > 0) { + if (mMediaProvider.bulkInsert(uri, valuesList.toArray(new ContentValues[0])) > 0) { return MtpConstants.RESPONSE_OK; } } catch (RemoteException e) { @@ -1153,17 +965,6 @@ public class MtpDatabase implements AutoCloseable { return MtpConstants.RESPONSE_GENERAL_ERROR; } - private void sessionStarted() { - mDatabaseModified = false; - } - - private void sessionEnded() { - if (mDatabaseModified) { - mUserContext.sendBroadcast(new Intent(MediaStore.ACTION_MTP_SESSION_END)); - mDatabaseModified = false; - } - } - // used by the JNI code private long mNativeContext; diff --git a/media/java/android/mtp/MtpPropertyGroup.java b/media/java/android/mtp/MtpPropertyGroup.java index dea300838385..77d0f34f1ad6 100644 --- a/media/java/android/mtp/MtpPropertyGroup.java +++ b/media/java/android/mtp/MtpPropertyGroup.java @@ -23,22 +23,21 @@ import android.os.RemoteException; import android.provider.MediaStore.Audio; import android.provider.MediaStore.Files; import android.provider.MediaStore.Images; -import android.provider.MediaStore.MediaColumns; import android.util.Log; import java.util.ArrayList; +/** + * MtpPropertyGroup represents a list of MTP properties. + * {@hide} + */ class MtpPropertyGroup { - - private static final String TAG = "MtpPropertyGroup"; + private static final String TAG = MtpPropertyGroup.class.getSimpleName(); private class Property { - // MTP property code - int code; - // MTP data type - int type; - // column index for our query - int column; + int code; + int type; + int column; Property(int code, int type, int column) { this.code = code; @@ -47,32 +46,26 @@ class MtpPropertyGroup { } } - private final MtpDatabase mDatabase; private final ContentProviderClient mProvider; private final String mVolumeName; private final Uri mUri; // list of all properties in this group - private final Property[] mProperties; + private final Property[] mProperties; // list of columns for database query - private String[] mColumns; + private String[] mColumns; + + private static final String PATH_WHERE = Files.FileColumns.DATA + "=?"; - private static final String ID_WHERE = Files.FileColumns._ID + "=?"; - private static final String FORMAT_WHERE = Files.FileColumns.FORMAT + "=?"; - private static final String ID_FORMAT_WHERE = ID_WHERE + " AND " + FORMAT_WHERE; - private static final String PARENT_WHERE = Files.FileColumns.PARENT + "=?"; - private static final String PARENT_FORMAT_WHERE = PARENT_WHERE + " AND " + FORMAT_WHERE; // constructs a property group for a list of properties - public MtpPropertyGroup(MtpDatabase database, ContentProviderClient provider, String volumeName, - int[] properties) { - mDatabase = database; + public MtpPropertyGroup(ContentProviderClient provider, String volumeName, int[] properties) { mProvider = provider; mVolumeName = volumeName; mUri = Files.getMtpObjectsUri(volumeName); int count = properties.length; - ArrayList<String> columns = new ArrayList<String>(count); + ArrayList<String> columns = new ArrayList<>(count); columns.add(Files.FileColumns._ID); mProperties = new Property[count]; @@ -90,37 +83,29 @@ class MtpPropertyGroup { String column = null; int type; - switch (code) { + switch (code) { case MtpConstants.PROPERTY_STORAGE_ID: - column = Files.FileColumns.STORAGE_ID; type = MtpConstants.TYPE_UINT32; break; - case MtpConstants.PROPERTY_OBJECT_FORMAT: - column = Files.FileColumns.FORMAT; + case MtpConstants.PROPERTY_OBJECT_FORMAT: type = MtpConstants.TYPE_UINT16; break; case MtpConstants.PROPERTY_PROTECTION_STATUS: - // protection status is always 0 type = MtpConstants.TYPE_UINT16; break; case MtpConstants.PROPERTY_OBJECT_SIZE: - column = Files.FileColumns.SIZE; type = MtpConstants.TYPE_UINT64; break; case MtpConstants.PROPERTY_OBJECT_FILE_NAME: - column = Files.FileColumns.DATA; type = MtpConstants.TYPE_STR; break; case MtpConstants.PROPERTY_NAME: - column = MediaColumns.TITLE; type = MtpConstants.TYPE_STR; break; case MtpConstants.PROPERTY_DATE_MODIFIED: - column = Files.FileColumns.DATE_MODIFIED; type = MtpConstants.TYPE_STR; break; case MtpConstants.PROPERTY_DATE_ADDED: - column = Files.FileColumns.DATE_ADDED; type = MtpConstants.TYPE_STR; break; case MtpConstants.PROPERTY_ORIGINAL_RELEASE_DATE: @@ -128,12 +113,9 @@ class MtpPropertyGroup { type = MtpConstants.TYPE_STR; break; case MtpConstants.PROPERTY_PARENT_OBJECT: - column = Files.FileColumns.PARENT; type = MtpConstants.TYPE_UINT32; break; case MtpConstants.PROPERTY_PERSISTENT_UID: - // PUID is concatenation of storageID and object handle - column = Files.FileColumns.STORAGE_ID; type = MtpConstants.TYPE_UINT128; break; case MtpConstants.PROPERTY_DURATION: @@ -145,7 +127,6 @@ class MtpPropertyGroup { type = MtpConstants.TYPE_UINT16; break; case MtpConstants.PROPERTY_DISPLAY_NAME: - column = MediaColumns.DISPLAY_NAME; type = MtpConstants.TYPE_STR; break; case MtpConstants.PROPERTY_ARTIST: @@ -195,40 +176,19 @@ class MtpPropertyGroup { } } - private String queryString(int id, String column) { - Cursor c = null; - try { - // for now we are only reading properties from the "objects" table - c = mProvider.query(mUri, - new String [] { Files.FileColumns._ID, column }, - ID_WHERE, new String[] { Integer.toString(id) }, null, null); - if (c != null && c.moveToNext()) { - return c.getString(1); - } else { - return ""; - } - } catch (Exception e) { - return null; - } finally { - if (c != null) { - c.close(); - } - } - } - - private String queryAudio(int id, String column) { + private String queryAudio(String path, String column) { Cursor c = null; try { c = mProvider.query(Audio.Media.getContentUri(mVolumeName), - new String [] { Files.FileColumns._ID, column }, - ID_WHERE, new String[] { Integer.toString(id) }, null, null); + new String [] { column }, + PATH_WHERE, new String[] {path}, null, null); if (c != null && c.moveToNext()) { - return c.getString(1); + return c.getString(0); } else { return ""; } } catch (Exception e) { - return null; + return ""; } finally { if (c != null) { c.close(); @@ -236,21 +196,19 @@ class MtpPropertyGroup { } } - private String queryGenre(int id) { + private String queryGenre(String path) { Cursor c = null; try { - Uri uri = Audio.Genres.getContentUriForAudioId(mVolumeName, id); - c = mProvider.query(uri, - new String [] { Files.FileColumns._ID, Audio.GenresColumns.NAME }, - null, null, null, null); + c = mProvider.query(Audio.Genres.getContentUri(mVolumeName), + new String [] { Audio.GenresColumns.NAME }, + PATH_WHERE, new String[] {path}, null, null); if (c != null && c.moveToNext()) { - return c.getString(1); + return c.getString(0); } else { return ""; } } catch (Exception e) { - Log.e(TAG, "queryGenre exception", e); - return null; + return ""; } finally { if (c != null) { c.close(); @@ -258,211 +216,127 @@ class MtpPropertyGroup { } } - private Long queryLong(int id, String column) { - Cursor c = null; - try { - // for now we are only reading properties from the "objects" table - c = mProvider.query(mUri, - new String [] { Files.FileColumns._ID, column }, - ID_WHERE, new String[] { Integer.toString(id) }, null, null); - if (c != null && c.moveToNext()) { - return new Long(c.getLong(1)); - } - } catch (Exception e) { - } finally { - if (c != null) { - c.close(); - } - } - return null; - } - - private static String nameFromPath(String path) { - // extract name from full path - int start = 0; - int lastSlash = path.lastIndexOf('/'); - if (lastSlash >= 0) { - start = lastSlash + 1; - } - int end = path.length(); - if (end - start > 255) { - end = start + 255; - } - return path.substring(start, end); - } - - MtpPropertyList getPropertyList(int handle, int format, int depth) { - //Log.d(TAG, "getPropertyList handle: " + handle + " format: " + format + " depth: " + depth); - if (depth > 1) { - // we only support depth 0 and 1 - // depth 0: single object, depth 1: immediate children - return new MtpPropertyList(0, MtpConstants.RESPONSE_SPECIFICATION_BY_DEPTH_UNSUPPORTED); - } - - String where; - String[] whereArgs; - if (format == 0) { - if (handle == 0xFFFFFFFF) { - // select all objects - where = null; - whereArgs = null; - } else { - whereArgs = new String[] { Integer.toString(handle) }; - if (depth == 1) { - where = PARENT_WHERE; - } else { - where = ID_WHERE; - } - } - } else { - if (handle == 0xFFFFFFFF) { - // select all objects with given format - where = FORMAT_WHERE; - whereArgs = new String[] { Integer.toString(format) }; - } else { - whereArgs = new String[] { Integer.toString(handle), Integer.toString(format) }; - if (depth == 1) { - where = PARENT_FORMAT_WHERE; - } else { - where = ID_FORMAT_WHERE; - } - } - } - + /** + * Gets the values of the properties represented by this property group for the given + * object and adds them to the given property list. + * @return Response_OK if the operation succeeded. + */ + public int getPropertyList(MtpStorageManager.MtpObject object, MtpPropertyList list) { Cursor c = null; - try { - // don't query if not necessary - if (depth > 0 || handle == 0xFFFFFFFF || mColumns.length > 1) { - c = mProvider.query(mUri, mColumns, where, whereArgs, null, null); - if (c == null) { - return new MtpPropertyList(0, MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE); + int id = object.getId(); + String path = object.getPath().toString(); + for (Property property : mProperties) { + if (property.column != -1 && c == null) { + try { + // Look up the entry in MediaProvider only if one of those properties is needed. + c = mProvider.query(mUri, mColumns, + PATH_WHERE, new String[] {path}, null, null); + if (c != null && !c.moveToNext()) { + c.close(); + c = null; + } + } catch (RemoteException e) { + Log.e(TAG, "Mediaprovider lookup failed"); } } - - int count = (c == null ? 1 : c.getCount()); - MtpPropertyList result = new MtpPropertyList(count * mProperties.length, - MtpConstants.RESPONSE_OK); - - // iterate over all objects in the query - for (int objectIndex = 0; objectIndex < count; objectIndex++) { - if (c != null) { - c.moveToNext(); - handle = (int)c.getLong(0); - } - - // iterate over all properties in the query for the given object - for (int propertyIndex = 0; propertyIndex < mProperties.length; propertyIndex++) { - Property property = mProperties[propertyIndex]; - int propertyCode = property.code; - int column = property.column; - - // handle some special cases - switch (propertyCode) { - case MtpConstants.PROPERTY_PROTECTION_STATUS: - // protection status is always 0 - result.append(handle, propertyCode, MtpConstants.TYPE_UINT16, 0); - break; - case MtpConstants.PROPERTY_OBJECT_FILE_NAME: - // special case - need to extract file name from full path - String value = c.getString(column); - if (value != null) { - result.append(handle, propertyCode, nameFromPath(value)); - } else { - result.setResult(MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE); - } - break; - case MtpConstants.PROPERTY_NAME: - // first try title - String name = c.getString(column); - // then try name - if (name == null) { - name = queryString(handle, Audio.PlaylistsColumns.NAME); - } - // if title and name fail, extract name from full path - if (name == null) { - name = queryString(handle, Files.FileColumns.DATA); - if (name != null) { - name = nameFromPath(name); - } - } - if (name != null) { - result.append(handle, propertyCode, name); - } else { - result.setResult(MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE); - } - break; - case MtpConstants.PROPERTY_DATE_MODIFIED: - case MtpConstants.PROPERTY_DATE_ADDED: - // convert from seconds to DateTime - result.append(handle, propertyCode, format_date_time(c.getInt(column))); - break; - case MtpConstants.PROPERTY_ORIGINAL_RELEASE_DATE: - // release date is stored internally as just the year - int year = c.getInt(column); - String dateTime = Integer.toString(year) + "0101T000000"; - result.append(handle, propertyCode, dateTime); - break; - case MtpConstants.PROPERTY_PERSISTENT_UID: - // PUID is concatenation of storageID and object handle - long puid = c.getLong(column); - puid <<= 32; - puid += handle; - result.append(handle, propertyCode, MtpConstants.TYPE_UINT128, puid); - break; - case MtpConstants.PROPERTY_TRACK: - result.append(handle, propertyCode, MtpConstants.TYPE_UINT16, - c.getInt(column) % 1000); - break; - case MtpConstants.PROPERTY_ARTIST: - result.append(handle, propertyCode, - queryAudio(handle, Audio.AudioColumns.ARTIST)); - break; - case MtpConstants.PROPERTY_ALBUM_NAME: - result.append(handle, propertyCode, - queryAudio(handle, Audio.AudioColumns.ALBUM)); - break; - case MtpConstants.PROPERTY_GENRE: - String genre = queryGenre(handle); - if (genre != null) { - result.append(handle, propertyCode, genre); - } else { - result.setResult(MtpConstants.RESPONSE_INVALID_OBJECT_HANDLE); - } - break; - case MtpConstants.PROPERTY_AUDIO_WAVE_CODEC: - case MtpConstants.PROPERTY_AUDIO_BITRATE: - case MtpConstants.PROPERTY_SAMPLE_RATE: - // we don't have these in our database, so return 0 - result.append(handle, propertyCode, MtpConstants.TYPE_UINT32, 0); + switch (property.code) { + case MtpConstants.PROPERTY_PROTECTION_STATUS: + // protection status is always 0 + list.append(id, property.code, property.type, 0); + break; + case MtpConstants.PROPERTY_NAME: + case MtpConstants.PROPERTY_OBJECT_FILE_NAME: + case MtpConstants.PROPERTY_DISPLAY_NAME: + list.append(id, property.code, object.getName()); + break; + case MtpConstants.PROPERTY_DATE_MODIFIED: + case MtpConstants.PROPERTY_DATE_ADDED: + // convert from seconds to DateTime + list.append(id, property.code, + format_date_time(object.getModifiedTime())); + break; + case MtpConstants.PROPERTY_STORAGE_ID: + list.append(id, property.code, property.type, object.getStorageId()); + break; + case MtpConstants.PROPERTY_OBJECT_FORMAT: + list.append(id, property.code, property.type, object.getFormat()); + break; + case MtpConstants.PROPERTY_OBJECT_SIZE: + list.append(id, property.code, property.type, object.getSize()); + break; + case MtpConstants.PROPERTY_PARENT_OBJECT: + list.append(id, property.code, property.type, + object.getParent().isRoot() ? 0 : object.getParent().getId()); + break; + case MtpConstants.PROPERTY_PERSISTENT_UID: + // The persistent uid must be unique and never reused among all objects, + // and remain the same between sessions. + long puid = (object.getPath().toString().hashCode() << 32) + + object.getModifiedTime(); + list.append(id, property.code, property.type, puid); + break; + case MtpConstants.PROPERTY_ORIGINAL_RELEASE_DATE: + // release date is stored internally as just the year + int year = 0; + if (c != null) + year = c.getInt(property.column); + String dateTime = Integer.toString(year) + "0101T000000"; + list.append(id, property.code, dateTime); + break; + case MtpConstants.PROPERTY_TRACK: + int track = 0; + if (c != null) + track = c.getInt(property.column); + list.append(id, property.code, MtpConstants.TYPE_UINT16, + track % 1000); + break; + case MtpConstants.PROPERTY_ARTIST: + list.append(id, property.code, + queryAudio(path, Audio.AudioColumns.ARTIST)); + break; + case MtpConstants.PROPERTY_ALBUM_NAME: + list.append(id, property.code, + queryAudio(path, Audio.AudioColumns.ALBUM)); + break; + case MtpConstants.PROPERTY_GENRE: + String genre = queryGenre(path); + if (genre != null) { + list.append(id, property.code, genre); + } + break; + case MtpConstants.PROPERTY_AUDIO_WAVE_CODEC: + case MtpConstants.PROPERTY_AUDIO_BITRATE: + case MtpConstants.PROPERTY_SAMPLE_RATE: + // we don't have these in our database, so return 0 + list.append(id, property.code, MtpConstants.TYPE_UINT32, 0); + break; + case MtpConstants.PROPERTY_BITRATE_TYPE: + case MtpConstants.PROPERTY_NUMBER_OF_CHANNELS: + // we don't have these in our database, so return 0 + list.append(id, property.code, MtpConstants.TYPE_UINT16, 0); + break; + default: + switch(property.type) { + case MtpConstants.TYPE_UNDEFINED: + list.append(id, property.code, property.type, 0); break; - case MtpConstants.PROPERTY_BITRATE_TYPE: - case MtpConstants.PROPERTY_NUMBER_OF_CHANNELS: - // we don't have these in our database, so return 0 - result.append(handle, propertyCode, MtpConstants.TYPE_UINT16, 0); + case MtpConstants.TYPE_STR: + String value = ""; + if (c != null) + value = c.getString(property.column); + list.append(id, property.code, value); break; default: - if (property.type == MtpConstants.TYPE_STR) { - result.append(handle, propertyCode, c.getString(column)); - } else if (property.type == MtpConstants.TYPE_UNDEFINED) { - result.append(handle, propertyCode, property.type, 0); - } else { - result.append(handle, propertyCode, property.type, - c.getLong(column)); - } - break; + long longValue = 0L; + if (c != null) + longValue = c.getLong(property.column); + list.append(id, property.code, property.type, longValue); } - } - } - - return result; - } catch (RemoteException e) { - return new MtpPropertyList(0, MtpConstants.RESPONSE_GENERAL_ERROR); - } finally { - if (c != null) { - c.close(); } } - // impossible to get here, so no return statement + if (c != null) + c.close(); + return MtpConstants.RESPONSE_OK; } private native String format_date_time(long seconds); diff --git a/media/java/android/mtp/MtpPropertyList.java b/media/java/android/mtp/MtpPropertyList.java index f9bc603e3de0..ede90dac517c 100644 --- a/media/java/android/mtp/MtpPropertyList.java +++ b/media/java/android/mtp/MtpPropertyList.java @@ -16,6 +16,9 @@ package android.mtp; +import java.util.ArrayList; +import java.util.List; + /** * Encapsulates the ObjectPropList dataset used by the GetObjectPropList command. * The fields of this class are read by JNI code in android_media_MtpDatabase.cpp @@ -23,56 +26,70 @@ package android.mtp; class MtpPropertyList { - // number of results returned - private int mCount; - // maximum number of results - private final int mMaxCount; - // result code for GetObjectPropList - public int mResult; // list of object handles (first field in quadruplet) - public final int[] mObjectHandles; - // list of object propery codes (second field in quadruplet) - public final int[] mPropertyCodes; + private List<Integer> mObjectHandles; + // list of object property codes (second field in quadruplet) + private List<Integer> mPropertyCodes; // list of data type codes (third field in quadruplet) - public final int[] mDataTypes; + private List<Integer> mDataTypes; // list of long int property values (fourth field in quadruplet, when value is integer type) - public long[] mLongValues; + private List<Long> mLongValues; // list of long int property values (fourth field in quadruplet, when value is string type) - public String[] mStringValues; - - // constructor only called from MtpDatabase - public MtpPropertyList(int maxCount, int result) { - mMaxCount = maxCount; - mResult = result; - mObjectHandles = new int[maxCount]; - mPropertyCodes = new int[maxCount]; - mDataTypes = new int[maxCount]; - // mLongValues and mStringValues are created lazily since both might not be necessary + private List<String> mStringValues; + + // Return value of this operation + private int mCode; + + public MtpPropertyList(int code) { + mCode = code; + mObjectHandles = new ArrayList<>(); + mPropertyCodes = new ArrayList<>(); + mDataTypes = new ArrayList<>(); + mLongValues = new ArrayList<>(); + mStringValues = new ArrayList<>(); } public void append(int handle, int property, int type, long value) { - int index = mCount++; - if (mLongValues == null) { - mLongValues = new long[mMaxCount]; - } - mObjectHandles[index] = handle; - mPropertyCodes[index] = property; - mDataTypes[index] = type; - mLongValues[index] = value; + mObjectHandles.add(handle); + mPropertyCodes.add(property); + mDataTypes.add(type); + mLongValues.add(value); + mStringValues.add(null); } public void append(int handle, int property, String value) { - int index = mCount++; - if (mStringValues == null) { - mStringValues = new String[mMaxCount]; - } - mObjectHandles[index] = handle; - mPropertyCodes[index] = property; - mDataTypes[index] = MtpConstants.TYPE_STR; - mStringValues[index] = value; + mObjectHandles.add(handle); + mPropertyCodes.add(property); + mDataTypes.add(MtpConstants.TYPE_STR); + mStringValues.add(value); + mLongValues.add(0L); + } + + public int getCode() { + return mCode; + } + + public int getCount() { + return mObjectHandles.size(); + } + + public int[] getObjectHandles() { + return mObjectHandles.stream().mapToInt(Integer::intValue).toArray(); + } + + public int[] getPropertyCodes() { + return mPropertyCodes.stream().mapToInt(Integer::intValue).toArray(); + } + + public int[] getDataTypes() { + return mDataTypes.stream().mapToInt(Integer::intValue).toArray(); + } + + public long[] getLongValues() { + return mLongValues.stream().mapToLong(Long::longValue).toArray(); } - public void setResult(int result) { - mResult = result; + public String[] getStringValues() { + return mStringValues.toArray(new String[0]); } } diff --git a/media/java/android/mtp/MtpStorage.java b/media/java/android/mtp/MtpStorage.java index 6ca442c7e66f..c72b827d8a2d 100644 --- a/media/java/android/mtp/MtpStorage.java +++ b/media/java/android/mtp/MtpStorage.java @@ -31,15 +31,13 @@ public class MtpStorage { private final int mStorageId; private final String mPath; private final String mDescription; - private final long mReserveSpace; private final boolean mRemovable; private final long mMaxFileSize; - public MtpStorage(StorageVolume volume, Context context) { - mStorageId = volume.getStorageId(); + public MtpStorage(StorageVolume volume, int storageId) { + mStorageId = storageId; mPath = volume.getPath(); - mDescription = volume.getDescription(context); - mReserveSpace = volume.getMtpReserveSpace() * 1024L * 1024L; + mDescription = volume.getDescription(null); mRemovable = volume.isRemovable(); mMaxFileSize = volume.getMaxFileSize(); } @@ -72,16 +70,6 @@ public class MtpStorage { } /** - * Returns the amount of space to reserve on the storage file system. - * This can be set to a non-zero value to prevent MTP from filling up the entire storage. - * - * @return reserved space in bytes. - */ - public final long getReserveSpace() { - return mReserveSpace; - } - - /** * Returns true if the storage is removable. * * @return is removable diff --git a/media/java/android/mtp/MtpStorageManager.java b/media/java/android/mtp/MtpStorageManager.java new file mode 100644 index 000000000000..bdc87413288a --- /dev/null +++ b/media/java/android/mtp/MtpStorageManager.java @@ -0,0 +1,1210 @@ +/* + * 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.mtp; + +import android.media.MediaFile; +import android.os.FileObserver; +import android.os.storage.StorageVolume; +import android.util.Log; + +import java.io.IOException; +import java.nio.file.DirectoryIteratorException; +import java.nio.file.DirectoryStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Objects; +import java.util.Set; +import java.util.stream.Stream; + +/** + * MtpStorageManager provides functionality for listing, tracking, and notifying MtpServer of + * filesystem changes. As directories are listed, this class will cache the results, + * and send events when objects are added/removed from cached directories. + * {@hide} + */ +public class MtpStorageManager { + private static final String TAG = MtpStorageManager.class.getSimpleName(); + public static boolean sDebug = false; + + // Inotify flags not provided by FileObserver + private static final int IN_ONLYDIR = 0x01000000; + private static final int IN_Q_OVERFLOW = 0x00004000; + private static final int IN_IGNORED = 0x00008000; + private static final int IN_ISDIR = 0x40000000; + + private class MtpObjectObserver extends FileObserver { + MtpObject mObject; + + MtpObjectObserver(MtpObject object) { + super(object.getPath().toString(), + MOVED_FROM | MOVED_TO | DELETE | CREATE | IN_ONLYDIR); + mObject = object; + } + + @Override + public void onEvent(int event, String path) { + synchronized (MtpStorageManager.this) { + if ((event & IN_Q_OVERFLOW) != 0) { + // We are out of space in the inotify queue. + Log.e(TAG, "Received Inotify overflow event!"); + } + MtpObject obj = mObject.getChild(path); + if ((event & MOVED_TO) != 0 || (event & CREATE) != 0) { + if (sDebug) + Log.i(TAG, "Got inotify added event for " + path + " " + event); + handleAddedObject(mObject, path, (event & IN_ISDIR) != 0); + } else if ((event & MOVED_FROM) != 0 || (event & DELETE) != 0) { + if (obj == null) { + Log.w(TAG, "Object was null in event " + path); + return; + } + if (sDebug) + Log.i(TAG, "Got inotify removed event for " + path + " " + event); + handleRemovedObject(obj); + } else if ((event & IN_IGNORED) != 0) { + if (sDebug) + Log.i(TAG, "inotify for " + mObject.getPath() + " deleted"); + if (mObject.mObserver != null) + mObject.mObserver.stopWatching(); + mObject.mObserver = null; + } else { + Log.w(TAG, "Got unrecognized event " + path + " " + event); + } + } + } + + @Override + public void finalize() { + // If the server shuts down and starts up again, the new server's observers can be + // invalidated by the finalize() calls of the previous server's observers. + // Hence, disable the automatic stopWatching() call in FileObserver#finalize, and + // always call stopWatching() manually whenever an observer should be shut down. + } + } + + /** + * Describes how the object is being acted on, to determine how events are handled. + */ + private enum MtpObjectState { + NORMAL, + FROZEN, // Object is going to be modified in this session. + FROZEN_ADDED, // Object was frozen, and has been added. + FROZEN_REMOVED, // Object was frozen, and has been removed. + FROZEN_ONESHOT_ADD, // Object is waiting for single add event before being unfrozen. + FROZEN_ONESHOT_DEL, // Object is waiting for single remove event and will then be removed. + } + + /** + * Describes the current operation being done on an object. Determines whether observers are + * created on new folders. + */ + private enum MtpOperation { + NONE, // Any new folders not added as part of the session are immediately observed. + ADD, // New folders added as part of the session are immediately observed. + RENAME, // Renamed or moved folders are not immediately observed. + COPY, // Copied folders are immediately observed iff the original was. + DELETE, // Exists for debugging purposes only. + } + + /** MtpObject represents either a file or directory in an associated storage. **/ + public static class MtpObject { + // null for root objects + private MtpObject mParent; + + private String mName; + private int mId; + private MtpObjectState mState; + private MtpOperation mOp; + + private boolean mVisited; + private boolean mIsDir; + + // null if not a directory + private HashMap<String, MtpObject> mChildren; + // null if not both a directory and visited + private FileObserver mObserver; + + MtpObject(String name, int id, MtpObject parent, boolean isDir) { + mId = id; + mName = name; + mParent = parent; + mObserver = null; + mVisited = false; + mState = MtpObjectState.NORMAL; + mIsDir = isDir; + mOp = MtpOperation.NONE; + + mChildren = mIsDir ? new HashMap<>() : null; + } + + /** Public methods for getting object info **/ + + public String getName() { + return mName; + } + + public int getId() { + return mId; + } + + public boolean isDir() { + return mIsDir; + } + + public int getFormat() { + return mIsDir ? MtpConstants.FORMAT_ASSOCIATION : MediaFile.getFormatCode(mName, null); + } + + public int getStorageId() { + return getRoot().getId(); + } + + public long getModifiedTime() { + return getPath().toFile().lastModified() / 1000; + } + + public MtpObject getParent() { + return mParent; + } + + public MtpObject getRoot() { + return isRoot() ? this : mParent.getRoot(); + } + + public long getSize() { + return mIsDir ? 0 : getPath().toFile().length(); + } + + public Path getPath() { + return isRoot() ? Paths.get(mName) : mParent.getPath().resolve(mName); + } + + public boolean isRoot() { + return mParent == null; + } + + /** For MtpStorageManager only **/ + + private void setName(String name) { + mName = name; + } + + private void setId(int id) { + mId = id; + } + + private boolean isVisited() { + return mVisited; + } + + private void setParent(MtpObject parent) { + mParent = parent; + } + + private void setDir(boolean dir) { + if (dir != mIsDir) { + mIsDir = dir; + mChildren = mIsDir ? new HashMap<>() : null; + } + } + + private void setVisited(boolean visited) { + mVisited = visited; + } + + private MtpObjectState getState() { + return mState; + } + + private void setState(MtpObjectState state) { + mState = state; + if (mState == MtpObjectState.NORMAL) + mOp = MtpOperation.NONE; + } + + private MtpOperation getOperation() { + return mOp; + } + + private void setOperation(MtpOperation op) { + mOp = op; + } + + private FileObserver getObserver() { + return mObserver; + } + + private void setObserver(FileObserver observer) { + mObserver = observer; + } + + private void addChild(MtpObject child) { + mChildren.put(child.getName(), child); + } + + private MtpObject getChild(String name) { + return mChildren.get(name); + } + + private Collection<MtpObject> getChildren() { + return mChildren.values(); + } + + private boolean exists() { + return getPath().toFile().exists(); + } + + private MtpObject copy(boolean recursive) { + MtpObject copy = new MtpObject(mName, mId, mParent, mIsDir); + copy.mIsDir = mIsDir; + copy.mVisited = mVisited; + copy.mState = mState; + copy.mChildren = mIsDir ? new HashMap<>() : null; + if (recursive && mIsDir) { + for (MtpObject child : mChildren.values()) { + MtpObject childCopy = child.copy(true); + childCopy.setParent(copy); + copy.addChild(childCopy); + } + } + return copy; + } + } + + /** + * A class that processes generated filesystem events. + */ + public static abstract class MtpNotifier { + /** + * Called when an object is added. + */ + public abstract void sendObjectAdded(int id); + + /** + * Called when an object is deleted. + */ + public abstract void sendObjectRemoved(int id); + } + + private MtpNotifier mMtpNotifier; + + // A cache of MtpObjects. The objects in the cache are keyed by object id. + // The root object of each storage isn't in this map since they all have ObjectId 0. + // Instead, they can be found in mRoots keyed by storageId. + private HashMap<Integer, MtpObject> mObjects; + + // A cache of the root MtpObject for each storage, keyed by storage id. + private HashMap<Integer, MtpObject> mRoots; + + // Object and Storage ids are allocated incrementally and not to be reused. + private int mNextObjectId; + private int mNextStorageId; + + // Special subdirectories. When set, only return objects rooted in these directories, and do + // not allow them to be modified. + private Set<String> mSubdirectories; + + private volatile boolean mCheckConsistency; + private Thread mConsistencyThread; + + public MtpStorageManager(MtpNotifier notifier, Set<String> subdirectories) { + mMtpNotifier = notifier; + mSubdirectories = subdirectories; + mObjects = new HashMap<>(); + mRoots = new HashMap<>(); + mNextObjectId = 1; + mNextStorageId = 1; + + mCheckConsistency = false; // Set to true to turn on automatic consistency checking + mConsistencyThread = new Thread(() -> { + while (mCheckConsistency) { + try { + Thread.sleep(15 * 1000); + } catch (InterruptedException e) { + return; + } + if (MtpStorageManager.this.checkConsistency()) { + Log.v(TAG, "Cache is consistent"); + } else { + Log.w(TAG, "Cache is not consistent"); + } + } + }); + if (mCheckConsistency) + mConsistencyThread.start(); + } + + /** + * Clean up resources used by the storage manager. + */ + public synchronized void close() { + Stream<MtpObject> objs = Stream.concat(mRoots.values().stream(), + mObjects.values().stream()); + + Iterator<MtpObject> iter = objs.iterator(); + while (iter.hasNext()) { + // Close all FileObservers. + MtpObject obj = iter.next(); + if (obj.getObserver() != null) { + obj.getObserver().stopWatching(); + obj.setObserver(null); + } + } + + // Shut down the consistency checking thread + if (mCheckConsistency) { + mCheckConsistency = false; + mConsistencyThread.interrupt(); + try { + mConsistencyThread.join(); + } catch (InterruptedException e) { + // ignore + } + } + } + + /** + * Sets the special subdirectories, which are the subdirectories of root storage that queries + * are restricted to. Must be done before any root storages are accessed. + * @param subDirs Subdirectories to set, or null to reset. + */ + public synchronized void setSubdirectories(Set<String> subDirs) { + mSubdirectories = subDirs; + } + + /** + * Allocates an MTP storage id for the given volume and add it to current roots. + * @param volume Storage to add. + * @return the associated MtpStorage + */ + public synchronized MtpStorage addMtpStorage(StorageVolume volume) { + int storageId = ((getNextStorageId() & 0x0000FFFF) << 16) + 1; + MtpObject root = new MtpObject(volume.getPath(), storageId, null, true); + MtpStorage storage = new MtpStorage(volume, storageId); + mRoots.put(storageId, root); + return storage; + } + + /** + * Removes the given storage and all associated items from the cache. + * @param storage Storage to remove. + */ + public synchronized void removeMtpStorage(MtpStorage storage) { + removeObjectFromCache(getStorageRoot(storage.getStorageId()), true, true); + } + + /** + * Checks if the given object can be renamed, moved, or deleted. + * If there are special subdirectories, they cannot be modified. + * @param obj Object to check. + * @return Whether object can be modified. + */ + private synchronized boolean isSpecialSubDir(MtpObject obj) { + return obj.getParent().isRoot() && mSubdirectories != null + && !mSubdirectories.contains(obj.getName()); + } + + /** + * Get the object with the specified path. Visit any necessary directories on the way. + * @param path Full path of the object to find. + * @return The desired object, or null if it cannot be found. + */ + public synchronized MtpObject getByPath(String path) { + MtpObject obj = null; + for (MtpObject root : mRoots.values()) { + if (path.startsWith(root.getName())) { + obj = root; + path = path.substring(root.getName().length()); + } + } + for (String name : path.split("/")) { + if (obj == null || !obj.isDir()) + return null; + if ("".equals(name)) + continue; + if (!obj.isVisited()) + getChildren(obj); + obj = obj.getChild(name); + } + return obj; + } + + /** + * Get the object with specified id. + * @param id Id of object. must not be 0 or 0xFFFFFFFF + * @return Object, or null if error. + */ + public synchronized MtpObject getObject(int id) { + if (id == 0 || id == 0xFFFFFFFF) { + Log.w(TAG, "Can't get root storages with getObject()"); + return null; + } + if (!mObjects.containsKey(id)) { + Log.w(TAG, "Id " + id + " doesn't exist"); + return null; + } + return mObjects.get(id); + } + + /** + * Get the storage with specified id. + * @param id Storage id. + * @return Object that is the root of the storage, or null if error. + */ + public MtpObject getStorageRoot(int id) { + if (!mRoots.containsKey(id)) { + Log.w(TAG, "StorageId " + id + " doesn't exist"); + return null; + } + return mRoots.get(id); + } + + private int getNextObjectId() { + int ret = mNextObjectId; + // Treat the id as unsigned int + mNextObjectId = (int) ((long) mNextObjectId + 1); + return ret; + } + + private int getNextStorageId() { + return mNextStorageId++; + } + + /** + * Get all objects matching the given parent, format, and storage + * @param parent object id of the parent. 0 for all objects, 0xFFFFFFFF for all object in root + * @param format format of returned objects. 0 for any format + * @param storageId storage id to look in. 0xFFFFFFFF for all storages + * @return A stream of matched objects, or null if error + */ + public synchronized Stream<MtpObject> getObjects(int parent, int format, int storageId) { + boolean recursive = parent == 0; + if (parent == 0xFFFFFFFF) + parent = 0; + if (storageId == 0xFFFFFFFF) { + // query all stores + if (parent == 0) { + // Get the objects of this format and parent in each store. + ArrayList<Stream<MtpObject>> streamList = new ArrayList<>(); + for (MtpObject root : mRoots.values()) { + streamList.add(getObjects(root, format, recursive)); + } + return Stream.of(streamList).flatMap(Collection::stream).reduce(Stream::concat) + .orElseGet(Stream::empty); + } + } + MtpObject obj = parent == 0 ? getStorageRoot(storageId) : getObject(parent); + if (obj == null) + return null; + return getObjects(obj, format, recursive); + } + + private synchronized Stream<MtpObject> getObjects(MtpObject parent, int format, boolean rec) { + Collection<MtpObject> children = getChildren(parent); + if (children == null) + return null; + Stream<MtpObject> ret = Stream.of(children).flatMap(Collection::stream); + + if (format != 0) { + ret = ret.filter(o -> o.getFormat() == format); + } + if (rec) { + // Get all objects recursively. + ArrayList<Stream<MtpObject>> streamList = new ArrayList<>(); + streamList.add(ret); + for (MtpObject o : children) { + if (o.isDir()) + streamList.add(getObjects(o, format, true)); + } + ret = Stream.of(streamList).filter(Objects::nonNull).flatMap(Collection::stream) + .reduce(Stream::concat).orElseGet(Stream::empty); + } + return ret; + } + + /** + * Return the children of the given object. If the object hasn't been visited yet, add + * its children to the cache and start observing it. + * @param object the parent object + * @return The collection of child objects or null if error + */ + private synchronized Collection<MtpObject> getChildren(MtpObject object) { + if (object == null || !object.isDir()) { + Log.w(TAG, "Can't find children of " + (object == null ? "null" : object.getId())); + return null; + } + if (!object.isVisited()) { + Path dir = object.getPath(); + /* + * If a file is added after the observer starts watching the directory, but before + * the contents are listed, it will generate an event that will get processed + * after this synchronized function returns. We handle this by ignoring object + * added events if an object at that path already exists. + */ + if (object.getObserver() != null) + Log.e(TAG, "Observer is not null!"); + object.setObserver(new MtpObjectObserver(object)); + object.getObserver().startWatching(); + try (DirectoryStream<Path> stream = Files.newDirectoryStream(dir)) { + for (Path file : stream) { + addObjectToCache(object, file.getFileName().toString(), + file.toFile().isDirectory()); + } + } catch (IOException | DirectoryIteratorException e) { + Log.e(TAG, e.toString()); + object.getObserver().stopWatching(); + object.setObserver(null); + return null; + } + object.setVisited(true); + } + return object.getChildren(); + } + + /** + * Create a new object from the given path and add it to the cache. + * @param parent The parent object + * @param newName Path of the new object + * @return the new object if success, else null + */ + private synchronized MtpObject addObjectToCache(MtpObject parent, String newName, + boolean isDir) { + if (!parent.isRoot() && getObject(parent.getId()) != parent) + // parent object has been removed + return null; + if (parent.getChild(newName) != null) { + // Object already exists + return null; + } + if (mSubdirectories != null && parent.isRoot() && !mSubdirectories.contains(newName)) { + // Not one of the restricted subdirectories. + return null; + } + + MtpObject obj = new MtpObject(newName, getNextObjectId(), parent, isDir); + mObjects.put(obj.getId(), obj); + parent.addChild(obj); + return obj; + } + + /** + * Remove the given path from the cache. + * @param removed The removed object + * @param removeGlobal Whether to remove the object from the global id map + * @param recursive Whether to also remove its children recursively. + * @return true if successfully removed + */ + private synchronized boolean removeObjectFromCache(MtpObject removed, boolean removeGlobal, + boolean recursive) { + boolean ret = removed.isRoot() + || removed.getParent().mChildren.remove(removed.getName(), removed); + if (!ret && sDebug) + Log.w(TAG, "Failed to remove from parent " + removed.getPath()); + if (removed.isRoot()) { + ret = mRoots.remove(removed.getId(), removed) && ret; + } else if (removeGlobal) { + ret = mObjects.remove(removed.getId(), removed) && ret; + } + if (!ret && sDebug) + Log.w(TAG, "Failed to remove from global cache " + removed.getPath()); + if (removed.getObserver() != null) { + removed.getObserver().stopWatching(); + removed.setObserver(null); + } + if (removed.isDir() && recursive) { + // Remove all descendants from cache recursively + Collection<MtpObject> children = new ArrayList<>(removed.getChildren()); + for (MtpObject child : children) { + ret = removeObjectFromCache(child, removeGlobal, true) && ret; + } + } + return ret; + } + + private synchronized void handleAddedObject(MtpObject parent, String path, boolean isDir) { + MtpOperation op = MtpOperation.NONE; + MtpObject obj = parent.getChild(path); + if (obj != null) { + MtpObjectState state = obj.getState(); + op = obj.getOperation(); + if (obj.isDir() != isDir && state != MtpObjectState.FROZEN_REMOVED) + Log.d(TAG, "Inconsistent directory info! " + obj.getPath()); + obj.setDir(isDir); + switch (state) { + case FROZEN: + case FROZEN_REMOVED: + obj.setState(MtpObjectState.FROZEN_ADDED); + break; + case FROZEN_ONESHOT_ADD: + obj.setState(MtpObjectState.NORMAL); + break; + case NORMAL: + case FROZEN_ADDED: + // This can happen when handling listed object in a new directory. + return; + default: + Log.w(TAG, "Unexpected state in add " + path + " " + state); + } + if (sDebug) + Log.i(TAG, state + " transitioned to " + obj.getState() + " in op " + op); + } else { + obj = MtpStorageManager.this.addObjectToCache(parent, path, isDir); + if (obj != null) { + MtpStorageManager.this.mMtpNotifier.sendObjectAdded(obj.getId()); + } else { + if (sDebug) + Log.w(TAG, "object " + path + " already exists"); + return; + } + } + if (isDir) { + // If this was added as part of a rename do not visit or send events. + if (op == MtpOperation.RENAME) + return; + + // If it was part of a copy operation, then only add observer if it was visited before. + if (op == MtpOperation.COPY && !obj.isVisited()) + return; + + if (obj.getObserver() != null) { + Log.e(TAG, "Observer is not null!"); + return; + } + obj.setObserver(new MtpObjectObserver(obj)); + obj.getObserver().startWatching(); + obj.setVisited(true); + + // It's possible that objects were added to a watched directory before the watch can be + // created, so manually handle those. + try (DirectoryStream<Path> stream = Files.newDirectoryStream(obj.getPath())) { + for (Path file : stream) { + if (sDebug) + Log.i(TAG, "Manually handling event for " + file.getFileName().toString()); + handleAddedObject(obj, file.getFileName().toString(), + file.toFile().isDirectory()); + } + } catch (IOException | DirectoryIteratorException e) { + Log.e(TAG, e.toString()); + obj.getObserver().stopWatching(); + obj.setObserver(null); + } + } + } + + private synchronized void handleRemovedObject(MtpObject obj) { + MtpObjectState state = obj.getState(); + MtpOperation op = obj.getOperation(); + switch (state) { + case FROZEN_ADDED: + obj.setState(MtpObjectState.FROZEN_REMOVED); + break; + case FROZEN_ONESHOT_DEL: + removeObjectFromCache(obj, op != MtpOperation.RENAME, false); + break; + case FROZEN: + obj.setState(MtpObjectState.FROZEN_REMOVED); + break; + case NORMAL: + if (MtpStorageManager.this.removeObjectFromCache(obj, true, true)) + MtpStorageManager.this.mMtpNotifier.sendObjectRemoved(obj.getId()); + break; + default: + // This shouldn't happen; states correspond to objects that don't exist + Log.e(TAG, "Got unexpected object remove for " + obj.getName()); + } + if (sDebug) + Log.i(TAG, state + " transitioned to " + obj.getState() + " in op " + op); + } + + /** + * Block the caller until all events currently in the event queue have been + * read and processed. Used for testing purposes. + */ + public void flushEvents() { + try { + // TODO make this smarter + Thread.sleep(500); + } catch (InterruptedException e) { + + } + } + + /** + * Dumps a representation of the cache to log. + */ + public synchronized void dump() { + for (int key : mObjects.keySet()) { + MtpObject obj = mObjects.get(key); + Log.i(TAG, key + " | " + (obj.getParent() == null ? obj.getParent().getId() : "null") + + " | " + obj.getName() + " | " + (obj.isDir() ? "dir" : "obj") + + " | " + (obj.isVisited() ? "v" : "nv") + " | " + obj.getState()); + } + } + + /** + * Checks consistency of the cache. This checks whether all objects have correct links + * to their parent, and whether directories are missing or have extraneous objects. + * @return true iff cache is consistent + */ + public synchronized boolean checkConsistency() { + Stream<MtpObject> objs = Stream.concat(mRoots.values().stream(), + mObjects.values().stream()); + Iterator<MtpObject> iter = objs.iterator(); + boolean ret = true; + while (iter.hasNext()) { + MtpObject obj = iter.next(); + if (!obj.exists()) { + Log.w(TAG, "Object doesn't exist " + obj.getPath() + " " + obj.getId()); + ret = false; + } + if (obj.getState() != MtpObjectState.NORMAL) { + Log.w(TAG, "Object " + obj.getPath() + " in state " + obj.getState()); + ret = false; + } + if (obj.getOperation() != MtpOperation.NONE) { + Log.w(TAG, "Object " + obj.getPath() + " in operation " + obj.getOperation()); + ret = false; + } + if (!obj.isRoot() && mObjects.get(obj.getId()) != obj) { + Log.w(TAG, "Object " + obj.getPath() + " is not in map correctly"); + ret = false; + } + if (obj.getParent() != null) { + if (obj.getParent().isRoot() && obj.getParent() + != mRoots.get(obj.getParent().getId())) { + Log.w(TAG, "Root parent is not in root mapping " + obj.getPath()); + ret = false; + } + if (!obj.getParent().isRoot() && obj.getParent() + != mObjects.get(obj.getParent().getId())) { + Log.w(TAG, "Parent is not in object mapping " + obj.getPath()); + ret = false; + } + if (obj.getParent().getChild(obj.getName()) != obj) { + Log.w(TAG, "Child does not exist in parent " + obj.getPath()); + ret = false; + } + } + if (obj.isDir()) { + if (obj.isVisited() == (obj.getObserver() == null)) { + Log.w(TAG, obj.getPath() + " is " + (obj.isVisited() ? "" : "not ") + + " visited but observer is " + obj.getObserver()); + ret = false; + } + if (!obj.isVisited() && obj.getChildren().size() > 0) { + Log.w(TAG, obj.getPath() + " is not visited but has children"); + ret = false; + } + try (DirectoryStream<Path> stream = Files.newDirectoryStream(obj.getPath())) { + Set<String> files = new HashSet<>(); + for (Path file : stream) { + if (obj.isVisited() && + obj.getChild(file.getFileName().toString()) == null && + (mSubdirectories == null || !obj.isRoot() || + mSubdirectories.contains(file.getFileName().toString()))) { + Log.w(TAG, "File exists in fs but not in children " + file); + ret = false; + } + files.add(file.toString()); + } + for (MtpObject child : obj.getChildren()) { + if (!files.contains(child.getPath().toString())) { + Log.w(TAG, "File in children doesn't exist in fs " + child.getPath()); + ret = false; + } + if (child != mObjects.get(child.getId())) { + Log.w(TAG, "Child is not in object map " + child.getPath()); + ret = false; + } + } + } catch (IOException | DirectoryIteratorException e) { + Log.w(TAG, e.toString()); + ret = false; + } + } + } + return ret; + } + + /** + * Informs MtpStorageManager that an object with the given path is about to be added. + * @param parent The parent object of the object to be added. + * @param name Filename of object to add. + * @return Object id of the added object, or -1 if it cannot be added. + */ + public synchronized int beginSendObject(MtpObject parent, String name, int format) { + if (sDebug) + Log.v(TAG, "beginSendObject " + name); + if (!parent.isDir()) + return -1; + if (parent.isRoot() && mSubdirectories != null && !mSubdirectories.contains(name)) + return -1; + getChildren(parent); // Ensure parent is visited + MtpObject obj = addObjectToCache(parent, name, format == MtpConstants.FORMAT_ASSOCIATION); + if (obj == null) + return -1; + obj.setState(MtpObjectState.FROZEN); + obj.setOperation(MtpOperation.ADD); + return obj.getId(); + } + + /** + * Clean up the object state after a sendObject operation. + * @param obj The object, returned from beginAddObject(). + * @param succeeded Whether the file was successfully created. + * @return Whether cache state was successfully cleaned up. + */ + public synchronized boolean endSendObject(MtpObject obj, boolean succeeded) { + if (sDebug) + Log.v(TAG, "endSendObject " + succeeded); + return generalEndAddObject(obj, succeeded, true); + } + + /** + * Informs MtpStorageManager that the given object is about to be renamed. + * If this returns true, it must be followed with an endRenameObject() + * @param obj Object to be renamed. + * @param newName New name of the object. + * @return Whether renaming is allowed. + */ + public synchronized boolean beginRenameObject(MtpObject obj, String newName) { + if (sDebug) + Log.v(TAG, "beginRenameObject " + obj.getName() + " " + newName); + if (obj.isRoot()) + return false; + if (isSpecialSubDir(obj)) + return false; + if (obj.getParent().getChild(newName) != null) + // Object already exists in parent with that name. + return false; + + MtpObject oldObj = obj.copy(false); + obj.setName(newName); + obj.getParent().addChild(obj); + oldObj.getParent().addChild(oldObj); + return generalBeginRenameObject(oldObj, obj); + } + + /** + * Cleans up cache state after a rename operation and sends any events that were missed. + * @param obj The object being renamed, the same one that was passed in beginRenameObject(). + * @param oldName The previous name of the object. + * @param success Whether the rename operation succeeded. + * @return Whether state was successfully cleaned up. + */ + public synchronized boolean endRenameObject(MtpObject obj, String oldName, boolean success) { + if (sDebug) + Log.v(TAG, "endRenameObject " + success); + MtpObject parent = obj.getParent(); + MtpObject oldObj = parent.getChild(oldName); + if (!success) { + // If the rename failed, we want oldObj to be the original and obj to be the dummy. + // Switch the objects, except for their name and state. + MtpObject temp = oldObj; + MtpObjectState oldState = oldObj.getState(); + temp.setName(obj.getName()); + temp.setState(obj.getState()); + oldObj = obj; + oldObj.setName(oldName); + oldObj.setState(oldState); + obj = temp; + parent.addChild(obj); + parent.addChild(oldObj); + } + return generalEndRenameObject(oldObj, obj, success); + } + + /** + * Informs MtpStorageManager that the given object is about to be deleted by the initiator, + * so don't send an event. + * @param obj Object to be deleted. + * @return Whether cache deletion is allowed. + */ + public synchronized boolean beginRemoveObject(MtpObject obj) { + if (sDebug) + Log.v(TAG, "beginRemoveObject " + obj.getName()); + return !obj.isRoot() && !isSpecialSubDir(obj) + && generalBeginRemoveObject(obj, MtpOperation.DELETE); + } + + /** + * Clean up cache state after a delete operation and send any events that were missed. + * @param obj Object to be deleted, same one passed in beginRemoveObject(). + * @param success Whether operation was completed successfully. + * @return Whether cache state is correct. + */ + public synchronized boolean endRemoveObject(MtpObject obj, boolean success) { + if (sDebug) + Log.v(TAG, "endRemoveObject " + success); + boolean ret = true; + if (obj.isDir()) { + for (MtpObject child : new ArrayList<>(obj.getChildren())) + if (child.getOperation() == MtpOperation.DELETE) + ret = endRemoveObject(child, success) && ret; + } + return generalEndRemoveObject(obj, success, true) && ret; + } + + /** + * Informs MtpStorageManager that the given object is about to be moved to a new parent. + * @param obj Object to be moved. + * @param newParent The new parent object. + * @return Whether the move is allowed. + */ + public synchronized boolean beginMoveObject(MtpObject obj, MtpObject newParent) { + if (sDebug) + Log.v(TAG, "beginMoveObject " + newParent.getPath()); + if (obj.isRoot()) + return false; + if (isSpecialSubDir(obj)) + return false; + getChildren(newParent); // Ensure parent is visited + if (newParent.getChild(obj.getName()) != null) + // Object already exists in parent with that name. + return false; + if (obj.getStorageId() != newParent.getStorageId()) { + /* + * The move is occurring across storages. The observers will not remain functional + * after the move, and the move will not be atomic. We have to copy the file tree + * to the destination and recreate the observers once copy is complete. + */ + MtpObject newObj = obj.copy(true); + newObj.setParent(newParent); + newParent.addChild(newObj); + return generalBeginRemoveObject(obj, MtpOperation.RENAME) + && generalBeginCopyObject(newObj, false); + } + // Move obj to new parent, create a dummy object in the old parent. + MtpObject oldObj = obj.copy(false); + obj.setParent(newParent); + oldObj.getParent().addChild(oldObj); + obj.getParent().addChild(obj); + return generalBeginRenameObject(oldObj, obj); + } + + /** + * Clean up cache state after a move operation and send any events that were missed. + * @param oldParent The old parent object. + * @param newParent The new parent object. + * @param name The name of the object being moved. + * @param success Whether operation was completed successfully. + * @return Whether cache state is correct. + */ + public synchronized boolean endMoveObject(MtpObject oldParent, MtpObject newParent, String name, + boolean success) { + if (sDebug) + Log.v(TAG, "endMoveObject " + success); + MtpObject oldObj = oldParent.getChild(name); + MtpObject newObj = newParent.getChild(name); + if (oldObj == null || newObj == null) + return false; + if (oldParent.getStorageId() != newObj.getStorageId()) { + boolean ret = endRemoveObject(oldObj, success); + return generalEndCopyObject(newObj, success, true) && ret; + } + if (!success) { + // If the rename failed, we want oldObj to be the original and obj to be the dummy. + // Switch the objects, except for their parent and state. + MtpObject temp = oldObj; + MtpObjectState oldState = oldObj.getState(); + temp.setParent(newObj.getParent()); + temp.setState(newObj.getState()); + oldObj = newObj; + oldObj.setParent(oldParent); + oldObj.setState(oldState); + newObj = temp; + newObj.getParent().addChild(newObj); + oldParent.addChild(oldObj); + } + return generalEndRenameObject(oldObj, newObj, success); + } + + /** + * Informs MtpStorageManager that the given object is about to be copied recursively. + * @param object Object to be copied + * @param newParent New parent for the object. + * @return The object id for the new copy, or -1 if error. + */ + public synchronized int beginCopyObject(MtpObject object, MtpObject newParent) { + if (sDebug) + Log.v(TAG, "beginCopyObject " + object.getName() + " to " + newParent.getPath()); + String name = object.getName(); + if (!newParent.isDir()) + return -1; + if (newParent.isRoot() && mSubdirectories != null && !mSubdirectories.contains(name)) + return -1; + getChildren(newParent); // Ensure parent is visited + if (newParent.getChild(name) != null) + return -1; + MtpObject newObj = object.copy(object.isDir()); + newParent.addChild(newObj); + newObj.setParent(newParent); + if (!generalBeginCopyObject(newObj, true)) + return -1; + return newObj.getId(); + } + + /** + * Cleans up cache state after a copy operation. + * @param object Object that was copied. + * @param success Whether the operation was successful. + * @return Whether cache state is consistent. + */ + public synchronized boolean endCopyObject(MtpObject object, boolean success) { + if (sDebug) + Log.v(TAG, "endCopyObject " + object.getName() + " " + success); + return generalEndCopyObject(object, success, false); + } + + private synchronized boolean generalEndAddObject(MtpObject obj, boolean succeeded, + boolean removeGlobal) { + switch (obj.getState()) { + case FROZEN: + // Object was never created. + if (succeeded) { + // The operation was successful so the event must still be in the queue. + obj.setState(MtpObjectState.FROZEN_ONESHOT_ADD); + } else { + // The operation failed and never created the file. + if (!removeObjectFromCache(obj, removeGlobal, false)) { + return false; + } + } + break; + case FROZEN_ADDED: + obj.setState(MtpObjectState.NORMAL); + if (!succeeded) { + MtpObject parent = obj.getParent(); + // The operation failed but some other process created the file. Send an event. + if (!removeObjectFromCache(obj, removeGlobal, false)) + return false; + handleAddedObject(parent, obj.getName(), obj.isDir()); + } + // else: The operation successfully created the object. + break; + case FROZEN_REMOVED: + if (!removeObjectFromCache(obj, removeGlobal, false)) + return false; + if (succeeded) { + // Some other process deleted the object. Send an event. + mMtpNotifier.sendObjectRemoved(obj.getId()); + } + // else: Mtp deleted the object as part of cleanup. Don't send an event. + break; + default: + return false; + } + return true; + } + + private synchronized boolean generalEndRemoveObject(MtpObject obj, boolean success, + boolean removeGlobal) { + switch (obj.getState()) { + case FROZEN: + if (success) { + // Object was deleted successfully, and event is still in the queue. + obj.setState(MtpObjectState.FROZEN_ONESHOT_DEL); + } else { + // Object was not deleted. + obj.setState(MtpObjectState.NORMAL); + } + break; + case FROZEN_ADDED: + // Object was deleted, and then readded. + obj.setState(MtpObjectState.NORMAL); + if (success) { + // Some other process readded the object. + MtpObject parent = obj.getParent(); + if (!removeObjectFromCache(obj, removeGlobal, false)) + return false; + handleAddedObject(parent, obj.getName(), obj.isDir()); + } + // else : Object still exists after failure. + break; + case FROZEN_REMOVED: + if (!removeObjectFromCache(obj, removeGlobal, false)) + return false; + if (!success) { + // Some other process deleted the object. + mMtpNotifier.sendObjectRemoved(obj.getId()); + } + // else : This process deleted the object as part of the operation. + break; + default: + return false; + } + return true; + } + + private synchronized boolean generalBeginRenameObject(MtpObject fromObj, MtpObject toObj) { + fromObj.setState(MtpObjectState.FROZEN); + toObj.setState(MtpObjectState.FROZEN); + fromObj.setOperation(MtpOperation.RENAME); + toObj.setOperation(MtpOperation.RENAME); + return true; + } + + private synchronized boolean generalEndRenameObject(MtpObject fromObj, MtpObject toObj, + boolean success) { + boolean ret = generalEndRemoveObject(fromObj, success, !success); + return generalEndAddObject(toObj, success, success) && ret; + } + + private synchronized boolean generalBeginRemoveObject(MtpObject obj, MtpOperation op) { + obj.setState(MtpObjectState.FROZEN); + obj.setOperation(op); + if (obj.isDir()) { + for (MtpObject child : obj.getChildren()) + generalBeginRemoveObject(child, op); + } + return true; + } + + private synchronized boolean generalBeginCopyObject(MtpObject obj, boolean newId) { + obj.setState(MtpObjectState.FROZEN); + obj.setOperation(MtpOperation.COPY); + if (newId) { + obj.setId(getNextObjectId()); + mObjects.put(obj.getId(), obj); + } + if (obj.isDir()) + for (MtpObject child : obj.getChildren()) + if (!generalBeginCopyObject(child, newId)) + return false; + return true; + } + + private synchronized boolean generalEndCopyObject(MtpObject obj, boolean success, boolean addGlobal) { + if (success && addGlobal) + mObjects.put(obj.getId(), obj); + boolean ret = true; + if (obj.isDir()) { + for (MtpObject child : new ArrayList<>(obj.getChildren())) { + if (child.getOperation() == MtpOperation.COPY) + ret = generalEndCopyObject(child, success, addGlobal) && ret; + } + } + ret = generalEndAddObject(obj, success, success || !addGlobal) && ret; + return ret; + } +} diff --git a/media/jni/android_mtp_MtpDatabase.cpp b/media/jni/android_mtp_MtpDatabase.cpp index 4e8c72bae9b9..23ef84f6ad90 100644 --- a/media/jni/android_mtp_MtpDatabase.cpp +++ b/media/jni/android_mtp_MtpDatabase.cpp @@ -19,7 +19,7 @@ #include "android_media_Utils.h" #include "mtp.h" -#include "MtpDatabase.h" +#include "IMtpDatabase.h" #include "MtpDataPacket.h" #include "MtpObjectInfo.h" #include "MtpProperty.h" @@ -55,7 +55,7 @@ using namespace android; static jmethodID method_beginSendObject; static jmethodID method_endSendObject; -static jmethodID method_doScanDirectory; +static jmethodID method_rescanFile; static jmethodID method_getObjectList; static jmethodID method_getNumObjects; static jmethodID method_getSupportedPlaybackFormats; @@ -68,35 +68,34 @@ static jmethodID method_setDeviceProperty; static jmethodID method_getObjectPropertyList; static jmethodID method_getObjectInfo; static jmethodID method_getObjectFilePath; -static jmethodID method_deleteFile; -static jmethodID method_moveObject; +static jmethodID method_beginDeleteObject; +static jmethodID method_endDeleteObject; +static jmethodID method_beginMoveObject; +static jmethodID method_endMoveObject; +static jmethodID method_beginCopyObject; +static jmethodID method_endCopyObject; static jmethodID method_getObjectReferences; static jmethodID method_setObjectReferences; -static jmethodID method_sessionStarted; -static jmethodID method_sessionEnded; static jfieldID field_context; -static jfieldID field_batteryLevel; -static jfieldID field_batteryScale; -static jfieldID field_deviceType; - -// MtpPropertyList fields -static jfieldID field_mCount; -static jfieldID field_mResult; -static jfieldID field_mObjectHandles; -static jfieldID field_mPropertyCodes; -static jfieldID field_mDataTypes; -static jfieldID field_mLongValues; -static jfieldID field_mStringValues; - - -MtpDatabase* getMtpDatabase(JNIEnv *env, jobject database) { - return (MtpDatabase *)env->GetLongField(database, field_context); + +// MtpPropertyList methods +static jmethodID method_getCode; +static jmethodID method_getCount; +static jmethodID method_getObjectHandles; +static jmethodID method_getPropertyCodes; +static jmethodID method_getDataTypes; +static jmethodID method_getLongValues; +static jmethodID method_getStringValues; + + +IMtpDatabase* getMtpDatabase(JNIEnv *env, jobject database) { + return (IMtpDatabase *)env->GetLongField(database, field_context); } // ---------------------------------------------------------------------------- -class MyMtpDatabase : public MtpDatabase { +class MtpDatabase : public IMtpDatabase { private: jobject mDatabase; jintArray mIntBuffer; @@ -104,23 +103,20 @@ private: jcharArray mStringBuffer; public: - MyMtpDatabase(JNIEnv *env, jobject client); - virtual ~MyMtpDatabase(); + MtpDatabase(JNIEnv *env, jobject client); + virtual ~MtpDatabase(); void cleanup(JNIEnv *env); virtual MtpObjectHandle beginSendObject(const char* path, MtpObjectFormat format, MtpObjectHandle parent, - MtpStorageID storage, - uint64_t size, - time_t modified); + MtpStorageID storage); - virtual void endSendObject(const char* path, - MtpObjectHandle handle, - MtpObjectFormat format, - bool succeeded); + virtual void endSendObject(MtpObjectHandle handle, bool succeeded); - virtual void doScanDirectory(const char* path); + virtual void rescanFile(const char* path, + MtpObjectHandle handle, + MtpObjectFormat format); virtual MtpObjectHandleList* getObjectList(MtpStorageID storageID, MtpObjectFormat format, @@ -167,7 +163,8 @@ public: MtpString& outFilePath, int64_t& outFileLength, MtpObjectFormat& outFormat); - virtual MtpResponseCode deleteFile(MtpObjectHandle handle); + virtual MtpResponseCode beginDeleteObject(MtpObjectHandle handle); + virtual void endDeleteObject(MtpObjectHandle handle, bool succeeded); bool getObjectPropertyInfo(MtpObjectProperty property, int& type); bool getDevicePropertyInfo(MtpDeviceProperty property, int& type); @@ -182,12 +179,17 @@ public: virtual MtpProperty* getDevicePropertyDesc(MtpDeviceProperty property); - virtual MtpResponseCode moveObject(MtpObjectHandle handle, MtpObjectHandle newParent, - MtpStorageID newStorage, MtpString& newPath); + virtual MtpResponseCode beginMoveObject(MtpObjectHandle handle, MtpObjectHandle newParent, + MtpStorageID newStorage); + + virtual void endMoveObject(MtpObjectHandle oldParent, MtpObjectHandle newParent, + MtpStorageID oldStorage, MtpStorageID newStorage, + MtpObjectHandle handle, bool succeeded); - virtual void sessionStarted(); + virtual MtpResponseCode beginCopyObject(MtpObjectHandle handle, MtpObjectHandle newParent, + MtpStorageID newStorage); + virtual void endCopyObject(MtpObjectHandle handle, bool succeeded); - virtual void sessionEnded(); }; // ---------------------------------------------------------------------------- @@ -202,7 +204,7 @@ static void checkAndClearExceptionFromCallback(JNIEnv* env, const char* methodNa // ---------------------------------------------------------------------------- -MyMtpDatabase::MyMtpDatabase(JNIEnv *env, jobject client) +MtpDatabase::MtpDatabase(JNIEnv *env, jobject client) : mDatabase(env->NewGlobalRef(client)), mIntBuffer(NULL), mLongBuffer(NULL), @@ -228,27 +230,24 @@ MyMtpDatabase::MyMtpDatabase(JNIEnv *env, jobject client) mStringBuffer = (jcharArray)env->NewGlobalRef(charArray); } -void MyMtpDatabase::cleanup(JNIEnv *env) { +void MtpDatabase::cleanup(JNIEnv *env) { env->DeleteGlobalRef(mDatabase); env->DeleteGlobalRef(mIntBuffer); env->DeleteGlobalRef(mLongBuffer); env->DeleteGlobalRef(mStringBuffer); } -MyMtpDatabase::~MyMtpDatabase() { +MtpDatabase::~MtpDatabase() { } -MtpObjectHandle MyMtpDatabase::beginSendObject(const char* path, +MtpObjectHandle MtpDatabase::beginSendObject(const char* path, MtpObjectFormat format, MtpObjectHandle parent, - MtpStorageID storage, - uint64_t size, - time_t modified) { + MtpStorageID storage) { JNIEnv* env = AndroidRuntime::getJNIEnv(); jstring pathStr = env->NewStringUTF(path); MtpObjectHandle result = env->CallIntMethod(mDatabase, method_beginSendObject, - pathStr, (jint)format, (jint)parent, (jint)storage, - (jlong)size, (jlong)modified); + pathStr, (jint)format, (jint)parent, (jint)storage); if (pathStr) env->DeleteLocalRef(pathStr); @@ -256,29 +255,26 @@ MtpObjectHandle MyMtpDatabase::beginSendObject(const char* path, return result; } -void MyMtpDatabase::endSendObject(const char* path, MtpObjectHandle handle, - MtpObjectFormat format, bool succeeded) { +void MtpDatabase::endSendObject(MtpObjectHandle handle, bool succeeded) { JNIEnv* env = AndroidRuntime::getJNIEnv(); - jstring pathStr = env->NewStringUTF(path); - env->CallVoidMethod(mDatabase, method_endSendObject, pathStr, - (jint)handle, (jint)format, (jboolean)succeeded); + env->CallVoidMethod(mDatabase, method_endSendObject, (jint)handle, (jboolean)succeeded); - if (pathStr) - env->DeleteLocalRef(pathStr); checkAndClearExceptionFromCallback(env, __FUNCTION__); } -void MyMtpDatabase::doScanDirectory(const char* path) { +void MtpDatabase::rescanFile(const char* path, MtpObjectHandle handle, + MtpObjectFormat format) { JNIEnv* env = AndroidRuntime::getJNIEnv(); jstring pathStr = env->NewStringUTF(path); - env->CallVoidMethod(mDatabase, method_doScanDirectory, pathStr); + env->CallVoidMethod(mDatabase, method_rescanFile, pathStr, + (jint)handle, (jint)format); if (pathStr) env->DeleteLocalRef(pathStr); checkAndClearExceptionFromCallback(env, __FUNCTION__); } -MtpObjectHandleList* MyMtpDatabase::getObjectList(MtpStorageID storageID, +MtpObjectHandleList* MtpDatabase::getObjectList(MtpStorageID storageID, MtpObjectFormat format, MtpObjectHandle parent) { JNIEnv* env = AndroidRuntime::getJNIEnv(); @@ -298,7 +294,7 @@ MtpObjectHandleList* MyMtpDatabase::getObjectList(MtpStorageID storageID, return list; } -int MyMtpDatabase::getNumObjects(MtpStorageID storageID, +int MtpDatabase::getNumObjects(MtpStorageID storageID, MtpObjectFormat format, MtpObjectHandle parent) { JNIEnv* env = AndroidRuntime::getJNIEnv(); @@ -309,7 +305,7 @@ int MyMtpDatabase::getNumObjects(MtpStorageID storageID, return result; } -MtpObjectFormatList* MyMtpDatabase::getSupportedPlaybackFormats() { +MtpObjectFormatList* MtpDatabase::getSupportedPlaybackFormats() { JNIEnv* env = AndroidRuntime::getJNIEnv(); jintArray array = (jintArray)env->CallObjectMethod(mDatabase, method_getSupportedPlaybackFormats); @@ -327,7 +323,7 @@ MtpObjectFormatList* MyMtpDatabase::getSupportedPlaybackFormats() { return list; } -MtpObjectFormatList* MyMtpDatabase::getSupportedCaptureFormats() { +MtpObjectFormatList* MtpDatabase::getSupportedCaptureFormats() { JNIEnv* env = AndroidRuntime::getJNIEnv(); jintArray array = (jintArray)env->CallObjectMethod(mDatabase, method_getSupportedCaptureFormats); @@ -345,7 +341,7 @@ MtpObjectFormatList* MyMtpDatabase::getSupportedCaptureFormats() { return list; } -MtpObjectPropertyList* MyMtpDatabase::getSupportedObjectProperties(MtpObjectFormat format) { +MtpObjectPropertyList* MtpDatabase::getSupportedObjectProperties(MtpObjectFormat format) { JNIEnv* env = AndroidRuntime::getJNIEnv(); jintArray array = (jintArray)env->CallObjectMethod(mDatabase, method_getSupportedObjectProperties, (jint)format); @@ -363,7 +359,7 @@ MtpObjectPropertyList* MyMtpDatabase::getSupportedObjectProperties(MtpObjectForm return list; } -MtpDevicePropertyList* MyMtpDatabase::getSupportedDeviceProperties() { +MtpDevicePropertyList* MtpDatabase::getSupportedDeviceProperties() { JNIEnv* env = AndroidRuntime::getJNIEnv(); jintArray array = (jintArray)env->CallObjectMethod(mDatabase, method_getSupportedDeviceProperties); @@ -381,7 +377,7 @@ MtpDevicePropertyList* MyMtpDatabase::getSupportedDeviceProperties() { return list; } -MtpResponseCode MyMtpDatabase::getObjectPropertyValue(MtpObjectHandle handle, +MtpResponseCode MtpDatabase::getObjectPropertyValue(MtpObjectHandle handle, MtpObjectProperty property, MtpDataPacket& packet) { static_assert(sizeof(jint) >= sizeof(MtpObjectHandle), @@ -397,42 +393,26 @@ MtpResponseCode MyMtpDatabase::getObjectPropertyValue(MtpObjectHandle handle, static_cast<jint>(property), 0, 0); - MtpResponseCode result = env->GetIntField(list, field_mResult); - int count = env->GetIntField(list, field_mCount); - if (result == MTP_RESPONSE_OK && count != 1) + MtpResponseCode result = env->CallIntMethod(list, method_getCode); + jint count = env->CallIntMethod(list, method_getCount); + if (count != 1) result = MTP_RESPONSE_GENERAL_ERROR; if (result == MTP_RESPONSE_OK) { - jintArray objectHandlesArray = (jintArray)env->GetObjectField(list, field_mObjectHandles); - jintArray propertyCodesArray = (jintArray)env->GetObjectField(list, field_mPropertyCodes); - jintArray dataTypesArray = (jintArray)env->GetObjectField(list, field_mDataTypes); - jlongArray longValuesArray = (jlongArray)env->GetObjectField(list, field_mLongValues); - jobjectArray stringValuesArray = (jobjectArray)env->GetObjectField(list, field_mStringValues); + jintArray objectHandlesArray = (jintArray)env->CallObjectMethod(list, method_getObjectHandles); + jintArray propertyCodesArray = (jintArray)env->CallObjectMethod(list, method_getPropertyCodes); + jintArray dataTypesArray = (jintArray)env->CallObjectMethod(list, method_getDataTypes); + jlongArray longValuesArray = (jlongArray)env->CallObjectMethod(list, method_getLongValues); + jobjectArray stringValuesArray = (jobjectArray)env->CallObjectMethod(list, method_getStringValues); jint* objectHandles = env->GetIntArrayElements(objectHandlesArray, 0); jint* propertyCodes = env->GetIntArrayElements(propertyCodesArray, 0); jint* dataTypes = env->GetIntArrayElements(dataTypesArray, 0); - jlong* longValues = (longValuesArray ? env->GetLongArrayElements(longValuesArray, 0) : NULL); + jlong* longValues = env->GetLongArrayElements(longValuesArray, 0); int type = dataTypes[0]; jlong longValue = (longValues ? longValues[0] : 0); - // special case date properties, which are strings to MTP - // but stored internally as a uint64 - if (property == MTP_PROPERTY_DATE_MODIFIED || property == MTP_PROPERTY_DATE_ADDED) { - char date[20]; - formatDateTime(longValue, date, sizeof(date)); - packet.putString(date); - goto out; - } - // release date is stored internally as just the year - if (property == MTP_PROPERTY_ORIGINAL_RELEASE_DATE) { - char date[20]; - snprintf(date, sizeof(date), "%04" PRId64 "0101T000000", longValue); - packet.putString(date); - goto out; - } - switch (type) { case MTP_TYPE_INT8: packet.putInt8(longValue); @@ -481,20 +461,16 @@ MtpResponseCode MyMtpDatabase::getObjectPropertyValue(MtpObjectHandle handle, ALOGE("unsupported type in getObjectPropertyValue\n"); result = MTP_RESPONSE_INVALID_OBJECT_PROP_FORMAT; } -out: env->ReleaseIntArrayElements(objectHandlesArray, objectHandles, 0); env->ReleaseIntArrayElements(propertyCodesArray, propertyCodes, 0); env->ReleaseIntArrayElements(dataTypesArray, dataTypes, 0); - if (longValues) - env->ReleaseLongArrayElements(longValuesArray, longValues, 0); + env->ReleaseLongArrayElements(longValuesArray, longValues, 0); env->DeleteLocalRef(objectHandlesArray); env->DeleteLocalRef(propertyCodesArray); env->DeleteLocalRef(dataTypesArray); - if (longValuesArray) - env->DeleteLocalRef(longValuesArray); - if (stringValuesArray) - env->DeleteLocalRef(stringValuesArray); + env->DeleteLocalRef(longValuesArray); + env->DeleteLocalRef(stringValuesArray); } env->DeleteLocalRef(list); @@ -559,7 +535,7 @@ static bool readLongValue(int type, MtpDataPacket& packet, jlong& longValue) { return true; } -MtpResponseCode MyMtpDatabase::setObjectPropertyValue(MtpObjectHandle handle, +MtpResponseCode MtpDatabase::setObjectPropertyValue(MtpObjectHandle handle, MtpObjectProperty property, MtpDataPacket& packet) { int type; @@ -590,80 +566,73 @@ fail: return result; } -MtpResponseCode MyMtpDatabase::getDevicePropertyValue(MtpDeviceProperty property, +MtpResponseCode MtpDatabase::getDevicePropertyValue(MtpDeviceProperty property, MtpDataPacket& packet) { JNIEnv* env = AndroidRuntime::getJNIEnv(); + int type; - if (property == MTP_DEVICE_PROPERTY_BATTERY_LEVEL) { - // special case - implemented here instead of Java - packet.putUInt8((uint8_t)env->GetIntField(mDatabase, field_batteryLevel)); - return MTP_RESPONSE_OK; - } else { - int type; - - if (!getDevicePropertyInfo(property, type)) - return MTP_RESPONSE_DEVICE_PROP_NOT_SUPPORTED; + if (!getDevicePropertyInfo(property, type)) + return MTP_RESPONSE_DEVICE_PROP_NOT_SUPPORTED; - jint result = env->CallIntMethod(mDatabase, method_getDeviceProperty, - (jint)property, mLongBuffer, mStringBuffer); - if (result != MTP_RESPONSE_OK) { - checkAndClearExceptionFromCallback(env, __FUNCTION__); - return result; - } + jint result = env->CallIntMethod(mDatabase, method_getDeviceProperty, + (jint)property, mLongBuffer, mStringBuffer); + if (result != MTP_RESPONSE_OK) { + checkAndClearExceptionFromCallback(env, __FUNCTION__); + return result; + } - jlong* longValues = env->GetLongArrayElements(mLongBuffer, 0); - jlong longValue = longValues[0]; - env->ReleaseLongArrayElements(mLongBuffer, longValues, 0); + jlong* longValues = env->GetLongArrayElements(mLongBuffer, 0); + jlong longValue = longValues[0]; + env->ReleaseLongArrayElements(mLongBuffer, longValues, 0); - switch (type) { - case MTP_TYPE_INT8: - packet.putInt8(longValue); - break; - case MTP_TYPE_UINT8: - packet.putUInt8(longValue); - break; - case MTP_TYPE_INT16: - packet.putInt16(longValue); - break; - case MTP_TYPE_UINT16: - packet.putUInt16(longValue); - break; - case MTP_TYPE_INT32: - packet.putInt32(longValue); - break; - case MTP_TYPE_UINT32: - packet.putUInt32(longValue); - break; - case MTP_TYPE_INT64: - packet.putInt64(longValue); - break; - case MTP_TYPE_UINT64: - packet.putUInt64(longValue); - break; - case MTP_TYPE_INT128: - packet.putInt128(longValue); - break; - case MTP_TYPE_UINT128: - packet.putInt128(longValue); - break; - case MTP_TYPE_STR: - { - jchar* str = env->GetCharArrayElements(mStringBuffer, 0); - packet.putString(str); - env->ReleaseCharArrayElements(mStringBuffer, str, 0); - break; - } - default: - ALOGE("unsupported type in getDevicePropertyValue\n"); - return MTP_RESPONSE_INVALID_DEVICE_PROP_FORMAT; + switch (type) { + case MTP_TYPE_INT8: + packet.putInt8(longValue); + break; + case MTP_TYPE_UINT8: + packet.putUInt8(longValue); + break; + case MTP_TYPE_INT16: + packet.putInt16(longValue); + break; + case MTP_TYPE_UINT16: + packet.putUInt16(longValue); + break; + case MTP_TYPE_INT32: + packet.putInt32(longValue); + break; + case MTP_TYPE_UINT32: + packet.putUInt32(longValue); + break; + case MTP_TYPE_INT64: + packet.putInt64(longValue); + break; + case MTP_TYPE_UINT64: + packet.putUInt64(longValue); + break; + case MTP_TYPE_INT128: + packet.putInt128(longValue); + break; + case MTP_TYPE_UINT128: + packet.putInt128(longValue); + break; + case MTP_TYPE_STR: + { + jchar* str = env->GetCharArrayElements(mStringBuffer, 0); + packet.putString(str); + env->ReleaseCharArrayElements(mStringBuffer, str, 0); + break; } - - checkAndClearExceptionFromCallback(env, __FUNCTION__); - return MTP_RESPONSE_OK; + default: + ALOGE("unsupported type in getDevicePropertyValue\n"); + return MTP_RESPONSE_INVALID_DEVICE_PROP_FORMAT; } + + checkAndClearExceptionFromCallback(env, __FUNCTION__); + return MTP_RESPONSE_OK; } -MtpResponseCode MyMtpDatabase::setDevicePropertyValue(MtpDeviceProperty property, +MtpResponseCode MtpDatabase::setDevicePropertyValue(MtpDeviceProperty property, MtpDataPacket& packet) { int type; @@ -693,11 +662,11 @@ fail: return result; } -MtpResponseCode MyMtpDatabase::resetDeviceProperty(MtpDeviceProperty /*property*/) { +MtpResponseCode MtpDatabase::resetDeviceProperty(MtpDeviceProperty /*property*/) { return -1; } -MtpResponseCode MyMtpDatabase::getObjectPropertyList(MtpObjectHandle handle, +MtpResponseCode MtpDatabase::getObjectPropertyList(MtpObjectHandle handle, uint32_t format, uint32_t property, int groupCode, int depth, MtpDataPacket& packet) { @@ -715,16 +684,16 @@ MtpResponseCode MyMtpDatabase::getObjectPropertyList(MtpObjectHandle handle, checkAndClearExceptionFromCallback(env, __FUNCTION__); if (!list) return MTP_RESPONSE_GENERAL_ERROR; - int count = env->GetIntField(list, field_mCount); - MtpResponseCode result = env->GetIntField(list, field_mResult); + int count = env->CallIntMethod(list, method_getCount); + MtpResponseCode result = env->CallIntMethod(list, method_getCode); packet.putUInt32(count); if (count > 0) { - jintArray objectHandlesArray = (jintArray)env->GetObjectField(list, field_mObjectHandles); - jintArray propertyCodesArray = (jintArray)env->GetObjectField(list, field_mPropertyCodes); - jintArray dataTypesArray = (jintArray)env->GetObjectField(list, field_mDataTypes); - jlongArray longValuesArray = (jlongArray)env->GetObjectField(list, field_mLongValues); - jobjectArray stringValuesArray = (jobjectArray)env->GetObjectField(list, field_mStringValues); + jintArray objectHandlesArray = (jintArray)env->CallObjectMethod(list, method_getObjectHandles); + jintArray propertyCodesArray = (jintArray)env->CallObjectMethod(list, method_getPropertyCodes); + jintArray dataTypesArray = (jintArray)env->CallObjectMethod(list, method_getDataTypes); + jlongArray longValuesArray = (jlongArray)env->CallObjectMethod(list, method_getLongValues); + jobjectArray stringValuesArray = (jobjectArray)env->CallObjectMethod(list, method_getStringValues); jint* objectHandles = env->GetIntArrayElements(objectHandlesArray, 0); jint* propertyCodes = env->GetIntArrayElements(propertyCodesArray, 0); @@ -781,7 +750,7 @@ MtpResponseCode MyMtpDatabase::getObjectPropertyList(MtpObjectHandle handle, break; } default: - ALOGE("bad or unsupported data type in MyMtpDatabase::getObjectPropertyList"); + ALOGE("bad or unsupported data type in MtpDatabase::getObjectPropertyList"); break; } } @@ -789,16 +758,13 @@ MtpResponseCode MyMtpDatabase::getObjectPropertyList(MtpObjectHandle handle, env->ReleaseIntArrayElements(objectHandlesArray, objectHandles, 0); env->ReleaseIntArrayElements(propertyCodesArray, propertyCodes, 0); env->ReleaseIntArrayElements(dataTypesArray, dataTypes, 0); - if (longValues) - env->ReleaseLongArrayElements(longValuesArray, longValues, 0); + env->ReleaseLongArrayElements(longValuesArray, longValues, 0); env->DeleteLocalRef(objectHandlesArray); env->DeleteLocalRef(propertyCodesArray); env->DeleteLocalRef(dataTypesArray); - if (longValuesArray) - env->DeleteLocalRef(longValuesArray); - if (stringValuesArray) - env->DeleteLocalRef(stringValuesArray); + env->DeleteLocalRef(longValuesArray); + env->DeleteLocalRef(stringValuesArray); } env->DeleteLocalRef(list); @@ -822,7 +788,7 @@ static long getLongFromExifEntry(ExifEntry *e) { return exif_get_long(e->data, o); } -MtpResponseCode MyMtpDatabase::getObjectInfo(MtpObjectHandle handle, +MtpResponseCode MtpDatabase::getObjectInfo(MtpObjectHandle handle, MtpObjectInfo& info) { MtpString path; int64_t length; @@ -914,7 +880,7 @@ MtpResponseCode MyMtpDatabase::getObjectInfo(MtpObjectHandle handle, return MTP_RESPONSE_OK; } -void* MyMtpDatabase::getThumbnail(MtpObjectHandle handle, size_t& outThumbSize) { +void* MtpDatabase::getThumbnail(MtpObjectHandle handle, size_t& outThumbSize) { MtpString path; int64_t length; MtpObjectFormat format; @@ -979,7 +945,7 @@ void* MyMtpDatabase::getThumbnail(MtpObjectHandle handle, size_t& outThumbSize) return result; } -MtpResponseCode MyMtpDatabase::getObjectFilePath(MtpObjectHandle handle, +MtpResponseCode MtpDatabase::getObjectFilePath(MtpObjectHandle handle, MtpString& outFilePath, int64_t& outFileLength, MtpObjectFormat& outFormat) { @@ -1005,26 +971,60 @@ MtpResponseCode MyMtpDatabase::getObjectFilePath(MtpObjectHandle handle, return result; } -MtpResponseCode MyMtpDatabase::deleteFile(MtpObjectHandle handle) { +MtpResponseCode MtpDatabase::beginDeleteObject(MtpObjectHandle handle) { JNIEnv* env = AndroidRuntime::getJNIEnv(); - MtpResponseCode result = env->CallIntMethod(mDatabase, method_deleteFile, (jint)handle); + MtpResponseCode result = env->CallIntMethod(mDatabase, method_beginDeleteObject, (jint)handle); checkAndClearExceptionFromCallback(env, __FUNCTION__); return result; } -MtpResponseCode MyMtpDatabase::moveObject(MtpObjectHandle handle, MtpObjectHandle newParent, - MtpStorageID newStorage, MtpString &newPath) { +void MtpDatabase::endDeleteObject(MtpObjectHandle handle, bool succeeded) { + JNIEnv* env = AndroidRuntime::getJNIEnv(); + env->CallVoidMethod(mDatabase, method_endDeleteObject, (jint)handle, (jboolean) succeeded); + + checkAndClearExceptionFromCallback(env, __FUNCTION__); +} + +MtpResponseCode MtpDatabase::beginMoveObject(MtpObjectHandle handle, MtpObjectHandle newParent, + MtpStorageID newStorage) { + JNIEnv* env = AndroidRuntime::getJNIEnv(); + MtpResponseCode result = env->CallIntMethod(mDatabase, method_beginMoveObject, + (jint)handle, (jint)newParent, (jint) newStorage); + + checkAndClearExceptionFromCallback(env, __FUNCTION__); + return result; +} + +void MtpDatabase::endMoveObject(MtpObjectHandle oldParent, MtpObjectHandle newParent, + MtpStorageID oldStorage, MtpStorageID newStorage, + MtpObjectHandle handle, bool succeeded) { + JNIEnv* env = AndroidRuntime::getJNIEnv(); + env->CallVoidMethod(mDatabase, method_endMoveObject, + (jint)oldParent, (jint) newParent, (jint) oldStorage, (jint) newStorage, + (jint) handle, (jboolean) succeeded); + + checkAndClearExceptionFromCallback(env, __FUNCTION__); +} + +MtpResponseCode MtpDatabase::beginCopyObject(MtpObjectHandle handle, MtpObjectHandle newParent, + MtpStorageID newStorage) { JNIEnv* env = AndroidRuntime::getJNIEnv(); - jstring stringValue = env->NewStringUTF((const char *) newPath); - MtpResponseCode result = env->CallIntMethod(mDatabase, method_moveObject, - (jint)handle, (jint)newParent, (jint) newStorage, stringValue); + MtpResponseCode result = env->CallIntMethod(mDatabase, method_beginCopyObject, + (jint)handle, (jint)newParent, (jint) newStorage); checkAndClearExceptionFromCallback(env, __FUNCTION__); - env->DeleteLocalRef(stringValue); return result; } +void MtpDatabase::endCopyObject(MtpObjectHandle handle, bool succeeded) { + JNIEnv* env = AndroidRuntime::getJNIEnv(); + env->CallVoidMethod(mDatabase, method_endCopyObject, (jint)handle, (jboolean)succeeded); + + checkAndClearExceptionFromCallback(env, __FUNCTION__); +} + + struct PropertyTableEntry { MtpObjectProperty property; int type; @@ -1066,7 +1066,7 @@ static const PropertyTableEntry kDevicePropertyTable[] = { { MTP_DEVICE_PROPERTY_PERCEIVED_DEVICE_TYPE, MTP_TYPE_UINT32 }, }; -bool MyMtpDatabase::getObjectPropertyInfo(MtpObjectProperty property, int& type) { +bool MtpDatabase::getObjectPropertyInfo(MtpObjectProperty property, int& type) { int count = sizeof(kObjectPropertyTable) / sizeof(kObjectPropertyTable[0]); const PropertyTableEntry* entry = kObjectPropertyTable; for (int i = 0; i < count; i++, entry++) { @@ -1078,7 +1078,7 @@ bool MyMtpDatabase::getObjectPropertyInfo(MtpObjectProperty property, int& type) return false; } -bool MyMtpDatabase::getDevicePropertyInfo(MtpDeviceProperty property, int& type) { +bool MtpDatabase::getDevicePropertyInfo(MtpDeviceProperty property, int& type) { int count = sizeof(kDevicePropertyTable) / sizeof(kDevicePropertyTable[0]); const PropertyTableEntry* entry = kDevicePropertyTable; for (int i = 0; i < count; i++, entry++) { @@ -1090,7 +1090,7 @@ bool MyMtpDatabase::getDevicePropertyInfo(MtpDeviceProperty property, int& type) return false; } -MtpObjectHandleList* MyMtpDatabase::getObjectReferences(MtpObjectHandle handle) { +MtpObjectHandleList* MtpDatabase::getObjectReferences(MtpObjectHandle handle) { JNIEnv* env = AndroidRuntime::getJNIEnv(); jintArray array = (jintArray)env->CallObjectMethod(mDatabase, method_getObjectReferences, (jint)handle); @@ -1108,7 +1108,7 @@ MtpObjectHandleList* MyMtpDatabase::getObjectReferences(MtpObjectHandle handle) return list; } -MtpResponseCode MyMtpDatabase::setObjectReferences(MtpObjectHandle handle, +MtpResponseCode MtpDatabase::setObjectReferences(MtpObjectHandle handle, MtpObjectHandleList* references) { JNIEnv* env = AndroidRuntime::getJNIEnv(); int count = references->size(); @@ -1129,7 +1129,7 @@ MtpResponseCode MyMtpDatabase::setObjectReferences(MtpObjectHandle handle, return result; } -MtpProperty* MyMtpDatabase::getObjectPropertyDesc(MtpObjectProperty property, +MtpProperty* MtpDatabase::getObjectPropertyDesc(MtpObjectProperty property, MtpObjectFormat format) { static const int channelEnum[] = { 1, // mono @@ -1210,67 +1210,65 @@ MtpProperty* MyMtpDatabase::getObjectPropertyDesc(MtpObjectProperty property, return result; } -MtpProperty* MyMtpDatabase::getDevicePropertyDesc(MtpDeviceProperty property) { +MtpProperty* MtpDatabase::getDevicePropertyDesc(MtpDeviceProperty property) { JNIEnv* env = AndroidRuntime::getJNIEnv(); MtpProperty* result = NULL; bool writable = false; - switch (property) { - case MTP_DEVICE_PROPERTY_SYNCHRONIZATION_PARTNER: - case MTP_DEVICE_PROPERTY_DEVICE_FRIENDLY_NAME: - writable = true; - // fall through - case MTP_DEVICE_PROPERTY_IMAGE_SIZE: { - result = new MtpProperty(property, MTP_TYPE_STR, writable); - - // get current value - jint ret = env->CallIntMethod(mDatabase, method_getDeviceProperty, - (jint)property, mLongBuffer, mStringBuffer); - if (ret == MTP_RESPONSE_OK) { + // get current value + jint ret = env->CallIntMethod(mDatabase, method_getDeviceProperty, + (jint)property, mLongBuffer, mStringBuffer); + if (ret == MTP_RESPONSE_OK) { + switch (property) { + case MTP_DEVICE_PROPERTY_SYNCHRONIZATION_PARTNER: + case MTP_DEVICE_PROPERTY_DEVICE_FRIENDLY_NAME: + writable = true; + // fall through + case MTP_DEVICE_PROPERTY_IMAGE_SIZE: + { + result = new MtpProperty(property, MTP_TYPE_STR, writable); jchar* str = env->GetCharArrayElements(mStringBuffer, 0); result->setCurrentValue(str); // for read-only properties it is safe to assume current value is default value if (!writable) result->setDefaultValue(str); env->ReleaseCharArrayElements(mStringBuffer, str, 0); - } else { - ALOGE("unable to read device property, response: %04X", ret); + break; } - break; + case MTP_DEVICE_PROPERTY_BATTERY_LEVEL: + { + result = new MtpProperty(property, MTP_TYPE_UINT8); + jlong* arr = env->GetLongArrayElements(mLongBuffer, 0); + result->setFormRange(0, arr[1], 1); + result->mCurrentValue.u.u8 = (uint8_t) arr[0]; + env->ReleaseLongArrayElements(mLongBuffer, arr, 0); + break; + } + case MTP_DEVICE_PROPERTY_PERCEIVED_DEVICE_TYPE: + { + jlong* arr = env->GetLongArrayElements(mLongBuffer, 0); + result = new MtpProperty(property, MTP_TYPE_UINT32); + result->mCurrentValue.u.u32 = (uint32_t) arr[0]; + env->ReleaseLongArrayElements(mLongBuffer, arr, 0); + break; + } + default: + ALOGE("Unrecognized property %x", property); } - case MTP_DEVICE_PROPERTY_BATTERY_LEVEL: - result = new MtpProperty(property, MTP_TYPE_UINT8); - result->setFormRange(0, env->GetIntField(mDatabase, field_batteryScale), 1); - result->mCurrentValue.u.u8 = (uint8_t)env->GetIntField(mDatabase, field_batteryLevel); - break; - case MTP_DEVICE_PROPERTY_PERCEIVED_DEVICE_TYPE: - result = new MtpProperty(property, MTP_TYPE_UINT32); - result->mCurrentValue.u.u32 = (uint32_t)env->GetIntField(mDatabase, field_deviceType); - break; + } else { + ALOGE("unable to read device property, response: %04X", ret); } checkAndClearExceptionFromCallback(env, __FUNCTION__); return result; } -void MyMtpDatabase::sessionStarted() { - JNIEnv* env = AndroidRuntime::getJNIEnv(); - env->CallVoidMethod(mDatabase, method_sessionStarted); - checkAndClearExceptionFromCallback(env, __FUNCTION__); -} - -void MyMtpDatabase::sessionEnded() { - JNIEnv* env = AndroidRuntime::getJNIEnv(); - env->CallVoidMethod(mDatabase, method_sessionEnded); - checkAndClearExceptionFromCallback(env, __FUNCTION__); -} - // ---------------------------------------------------------------------------- static void android_mtp_MtpDatabase_setup(JNIEnv *env, jobject thiz) { - MyMtpDatabase* database = new MyMtpDatabase(env, thiz); + MtpDatabase* database = new MtpDatabase(env, thiz); env->SetLongField(thiz, field_context, (jlong)database); checkAndClearExceptionFromCallback(env, __FUNCTION__); } @@ -1278,7 +1276,7 @@ android_mtp_MtpDatabase_setup(JNIEnv *env, jobject thiz) static void android_mtp_MtpDatabase_finalize(JNIEnv *env, jobject thiz) { - MyMtpDatabase* database = (MyMtpDatabase *)env->GetLongField(thiz, field_context); + MtpDatabase* database = (MtpDatabase *)env->GetLongField(thiz, field_context); database->cleanup(env); delete database; env->SetLongField(thiz, field_context, 0); @@ -1305,6 +1303,13 @@ static const JNINativeMethod gMtpPropertyGroupMethods[] = { (void *)android_mtp_MtpPropertyGroup_format_date_time}, }; +#define GET_METHOD_ID(name, jclass, signature) \ + method_##name = env->GetMethodID(jclass, #name, signature); \ + if (method_##name == NULL) { \ + ALOGE("Can't find " #name); \ + return -1; \ + } \ + int register_android_mtp_MtpDatabase(JNIEnv *env) { jclass clazz; @@ -1314,175 +1319,48 @@ int register_android_mtp_MtpDatabase(JNIEnv *env) ALOGE("Can't find android/mtp/MtpDatabase"); return -1; } - method_beginSendObject = env->GetMethodID(clazz, "beginSendObject", "(Ljava/lang/String;IIIJJ)I"); - if (method_beginSendObject == NULL) { - ALOGE("Can't find beginSendObject"); - return -1; - } - method_endSendObject = env->GetMethodID(clazz, "endSendObject", "(Ljava/lang/String;IIZ)V"); - if (method_endSendObject == NULL) { - ALOGE("Can't find endSendObject"); - return -1; - } - method_doScanDirectory = env->GetMethodID(clazz, "doScanDirectory", "(Ljava/lang/String;)V"); - if (method_doScanDirectory == NULL) { - ALOGE("Can't find doScanDirectory"); - return -1; - } - method_getObjectList = env->GetMethodID(clazz, "getObjectList", "(III)[I"); - if (method_getObjectList == NULL) { - ALOGE("Can't find getObjectList"); - return -1; - } - method_getNumObjects = env->GetMethodID(clazz, "getNumObjects", "(III)I"); - if (method_getNumObjects == NULL) { - ALOGE("Can't find getNumObjects"); - return -1; - } - method_getSupportedPlaybackFormats = env->GetMethodID(clazz, "getSupportedPlaybackFormats", "()[I"); - if (method_getSupportedPlaybackFormats == NULL) { - ALOGE("Can't find getSupportedPlaybackFormats"); - return -1; - } - method_getSupportedCaptureFormats = env->GetMethodID(clazz, "getSupportedCaptureFormats", "()[I"); - if (method_getSupportedCaptureFormats == NULL) { - ALOGE("Can't find getSupportedCaptureFormats"); - return -1; - } - method_getSupportedObjectProperties = env->GetMethodID(clazz, "getSupportedObjectProperties", "(I)[I"); - if (method_getSupportedObjectProperties == NULL) { - ALOGE("Can't find getSupportedObjectProperties"); - return -1; - } - method_getSupportedDeviceProperties = env->GetMethodID(clazz, "getSupportedDeviceProperties", "()[I"); - if (method_getSupportedDeviceProperties == NULL) { - ALOGE("Can't find getSupportedDeviceProperties"); - return -1; - } - method_setObjectProperty = env->GetMethodID(clazz, "setObjectProperty", "(IIJLjava/lang/String;)I"); - if (method_setObjectProperty == NULL) { - ALOGE("Can't find setObjectProperty"); - return -1; - } - method_getDeviceProperty = env->GetMethodID(clazz, "getDeviceProperty", "(I[J[C)I"); - if (method_getDeviceProperty == NULL) { - ALOGE("Can't find getDeviceProperty"); - return -1; - } - method_setDeviceProperty = env->GetMethodID(clazz, "setDeviceProperty", "(IJLjava/lang/String;)I"); - if (method_setDeviceProperty == NULL) { - ALOGE("Can't find setDeviceProperty"); - return -1; - } - method_getObjectPropertyList = env->GetMethodID(clazz, "getObjectPropertyList", - "(IIIII)Landroid/mtp/MtpPropertyList;"); - if (method_getObjectPropertyList == NULL) { - ALOGE("Can't find getObjectPropertyList"); - return -1; - } - method_getObjectInfo = env->GetMethodID(clazz, "getObjectInfo", "(I[I[C[J)Z"); - if (method_getObjectInfo == NULL) { - ALOGE("Can't find getObjectInfo"); - return -1; - } - method_getObjectFilePath = env->GetMethodID(clazz, "getObjectFilePath", "(I[C[J)I"); - if (method_getObjectFilePath == NULL) { - ALOGE("Can't find getObjectFilePath"); - return -1; - } - method_deleteFile = env->GetMethodID(clazz, "deleteFile", "(I)I"); - if (method_deleteFile == NULL) { - ALOGE("Can't find deleteFile"); - return -1; - } - method_moveObject = env->GetMethodID(clazz, "moveObject", "(IIILjava/lang/String;)I"); - if (method_moveObject == NULL) { - ALOGE("Can't find moveObject"); - return -1; - } - method_getObjectReferences = env->GetMethodID(clazz, "getObjectReferences", "(I)[I"); - if (method_getObjectReferences == NULL) { - ALOGE("Can't find getObjectReferences"); - return -1; - } - method_setObjectReferences = env->GetMethodID(clazz, "setObjectReferences", "(I[I)I"); - if (method_setObjectReferences == NULL) { - ALOGE("Can't find setObjectReferences"); - return -1; - } - method_sessionStarted = env->GetMethodID(clazz, "sessionStarted", "()V"); - if (method_sessionStarted == NULL) { - ALOGE("Can't find sessionStarted"); - return -1; - } - method_sessionEnded = env->GetMethodID(clazz, "sessionEnded", "()V"); - if (method_sessionEnded == NULL) { - ALOGE("Can't find sessionEnded"); - return -1; - } + GET_METHOD_ID(beginSendObject, clazz, "(Ljava/lang/String;III)I"); + GET_METHOD_ID(endSendObject, clazz, "(IZ)V"); + GET_METHOD_ID(rescanFile, clazz, "(Ljava/lang/String;II)V"); + GET_METHOD_ID(getObjectList, clazz, "(III)[I"); + GET_METHOD_ID(getNumObjects, clazz, "(III)I"); + GET_METHOD_ID(getSupportedPlaybackFormats, clazz, "()[I"); + GET_METHOD_ID(getSupportedCaptureFormats, clazz, "()[I"); + GET_METHOD_ID(getSupportedObjectProperties, clazz, "(I)[I"); + GET_METHOD_ID(getSupportedDeviceProperties, clazz, "()[I"); + GET_METHOD_ID(setObjectProperty, clazz, "(IIJLjava/lang/String;)I"); + GET_METHOD_ID(getDeviceProperty, clazz, "(I[J[C)I"); + GET_METHOD_ID(setDeviceProperty, clazz, "(IJLjava/lang/String;)I"); + GET_METHOD_ID(getObjectPropertyList, clazz, "(IIIII)Landroid/mtp/MtpPropertyList;"); + GET_METHOD_ID(getObjectInfo, clazz, "(I[I[C[J)Z"); + GET_METHOD_ID(getObjectFilePath, clazz, "(I[C[J)I"); + GET_METHOD_ID(beginDeleteObject, clazz, "(I)I"); + GET_METHOD_ID(endDeleteObject, clazz, "(IZ)V"); + GET_METHOD_ID(beginMoveObject, clazz, "(III)I"); + GET_METHOD_ID(endMoveObject, clazz, "(IIIIIZ)V"); + GET_METHOD_ID(beginCopyObject, clazz, "(III)I"); + GET_METHOD_ID(endCopyObject, clazz, "(IZ)V"); + GET_METHOD_ID(getObjectReferences, clazz, "(I)[I"); + GET_METHOD_ID(setObjectReferences, clazz, "(I[I)I"); field_context = env->GetFieldID(clazz, "mNativeContext", "J"); if (field_context == NULL) { ALOGE("Can't find MtpDatabase.mNativeContext"); return -1; } - field_batteryLevel = env->GetFieldID(clazz, "mBatteryLevel", "I"); - if (field_batteryLevel == NULL) { - ALOGE("Can't find MtpDatabase.mBatteryLevel"); - return -1; - } - field_batteryScale = env->GetFieldID(clazz, "mBatteryScale", "I"); - if (field_batteryScale == NULL) { - ALOGE("Can't find MtpDatabase.mBatteryScale"); - return -1; - } - field_deviceType = env->GetFieldID(clazz, "mDeviceType", "I"); - if (field_deviceType == NULL) { - ALOGE("Can't find MtpDatabase.mDeviceType"); - return -1; - } - // now set up fields for MtpPropertyList class clazz = env->FindClass("android/mtp/MtpPropertyList"); if (clazz == NULL) { ALOGE("Can't find android/mtp/MtpPropertyList"); return -1; } - field_mCount = env->GetFieldID(clazz, "mCount", "I"); - if (field_mCount == NULL) { - ALOGE("Can't find MtpPropertyList.mCount"); - return -1; - } - field_mResult = env->GetFieldID(clazz, "mResult", "I"); - if (field_mResult == NULL) { - ALOGE("Can't find MtpPropertyList.mResult"); - return -1; - } - field_mObjectHandles = env->GetFieldID(clazz, "mObjectHandles", "[I"); - if (field_mObjectHandles == NULL) { - ALOGE("Can't find MtpPropertyList.mObjectHandles"); - return -1; - } - field_mPropertyCodes = env->GetFieldID(clazz, "mPropertyCodes", "[I"); - if (field_mPropertyCodes == NULL) { - ALOGE("Can't find MtpPropertyList.mPropertyCodes"); - return -1; - } - field_mDataTypes = env->GetFieldID(clazz, "mDataTypes", "[I"); - if (field_mDataTypes == NULL) { - ALOGE("Can't find MtpPropertyList.mDataTypes"); - return -1; - } - field_mLongValues = env->GetFieldID(clazz, "mLongValues", "[J"); - if (field_mLongValues == NULL) { - ALOGE("Can't find MtpPropertyList.mLongValues"); - return -1; - } - field_mStringValues = env->GetFieldID(clazz, "mStringValues", "[Ljava/lang/String;"); - if (field_mStringValues == NULL) { - ALOGE("Can't find MtpPropertyList.mStringValues"); - return -1; - } + GET_METHOD_ID(getCode, clazz, "()I"); + GET_METHOD_ID(getCount, clazz, "()I"); + GET_METHOD_ID(getObjectHandles, clazz, "()[I"); + GET_METHOD_ID(getPropertyCodes, clazz, "()[I"); + GET_METHOD_ID(getDataTypes, clazz, "()[I"); + GET_METHOD_ID(getLongValues, clazz, "()[J"); + GET_METHOD_ID(getStringValues, clazz, "()[Ljava/lang/String;"); if (AndroidRuntime::registerNativeMethods(env, "android/mtp/MtpDatabase", gMtpDatabaseMethods, NELEM(gMtpDatabaseMethods))) diff --git a/media/jni/android_mtp_MtpServer.cpp b/media/jni/android_mtp_MtpServer.cpp index 6ce104d01a9e..c76cebebc730 100644 --- a/media/jni/android_mtp_MtpServer.cpp +++ b/media/jni/android_mtp_MtpServer.cpp @@ -41,7 +41,6 @@ static jfieldID field_MtpServer_nativeContext; static jfieldID field_MtpStorage_storageId; static jfieldID field_MtpStorage_path; static jfieldID field_MtpStorage_description; -static jfieldID field_MtpStorage_reserveSpace; static jfieldID field_MtpStorage_removable; static jfieldID field_MtpStorage_maxFileSize; @@ -50,7 +49,7 @@ static Mutex sMutex; // ---------------------------------------------------------------------------- // in android_mtp_MtpDatabase.cpp -extern MtpDatabase* getMtpDatabase(JNIEnv *env, jobject database); +extern IMtpDatabase* getMtpDatabase(JNIEnv *env, jobject database); static inline MtpServer* getMtpServer(JNIEnv *env, jobject thiz) { return (MtpServer*)env->GetLongField(thiz, field_MtpServer_nativeContext); @@ -162,7 +161,6 @@ android_mtp_MtpServer_add_storage(JNIEnv *env, jobject thiz, jobject jstorage) jint storageID = env->GetIntField(jstorage, field_MtpStorage_storageId); jstring path = (jstring)env->GetObjectField(jstorage, field_MtpStorage_path); jstring description = (jstring)env->GetObjectField(jstorage, field_MtpStorage_description); - jlong reserveSpace = env->GetLongField(jstorage, field_MtpStorage_reserveSpace); jboolean removable = env->GetBooleanField(jstorage, field_MtpStorage_removable); jlong maxFileSize = env->GetLongField(jstorage, field_MtpStorage_maxFileSize); @@ -171,7 +169,7 @@ android_mtp_MtpServer_add_storage(JNIEnv *env, jobject thiz, jobject jstorage) const char *descriptionStr = env->GetStringUTFChars(description, NULL); if (descriptionStr != NULL) { MtpStorage* storage = new MtpStorage(storageID, pathStr, descriptionStr, - reserveSpace, removable, maxFileSize); + removable, maxFileSize); server->addStorage(storage); env->ReleaseStringUTFChars(path, pathStr); env->ReleaseStringUTFChars(description, descriptionStr); @@ -241,11 +239,6 @@ int register_android_mtp_MtpServer(JNIEnv *env) ALOGE("Can't find MtpStorage.mDescription"); return -1; } - field_MtpStorage_reserveSpace = env->GetFieldID(clazz, "mReserveSpace", "J"); - if (field_MtpStorage_reserveSpace == NULL) { - ALOGE("Can't find MtpStorage.mReserveSpace"); - return -1; - } field_MtpStorage_removable = env->GetFieldID(clazz, "mRemovable", "Z"); if (field_MtpStorage_removable == NULL) { ALOGE("Can't find MtpStorage.mRemovable"); diff --git a/media/tests/MtpTests/Android.mk b/media/tests/MtpTests/Android.mk new file mode 100644 index 000000000000..616e600ad6e7 --- /dev/null +++ b/media/tests/MtpTests/Android.mk @@ -0,0 +1,12 @@ +LOCAL_PATH:= $(call my-dir) +include $(CLEAR_VARS) + +LOCAL_MODULE_TAGS := tests + +LOCAL_SRC_FILES := $(call all-subdir-java-files) + +LOCAL_STATIC_JAVA_LIBRARIES := android-support-test + +LOCAL_PACKAGE_NAME := MtpTests + +include $(BUILD_PACKAGE) diff --git a/media/tests/MtpTests/AndroidManifest.xml b/media/tests/MtpTests/AndroidManifest.xml new file mode 100644 index 000000000000..21e2b0115878 --- /dev/null +++ b/media/tests/MtpTests/AndroidManifest.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2008 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + 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. +--> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="android.mtp" > + + <uses-sdk android:minSdkVersion="21" android:targetSdkVersion="21" /> + + <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> + + <application> + <uses-library android:name="android.test.runner" /> + </application> + + <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner" + android:targetPackage="android.mtp" + android:label="MtpTests"/> +</manifest> diff --git a/media/tests/MtpTests/AndroidTest.xml b/media/tests/MtpTests/AndroidTest.xml new file mode 100644 index 000000000000..a61a3b49c8f7 --- /dev/null +++ b/media/tests/MtpTests/AndroidTest.xml @@ -0,0 +1,15 @@ +<configuration description="Runs sample instrumentation test."> + <target_preparer class="com.android.tradefed.targetprep.TestFilePushSetup"/> + <target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup"> + <option name="test-file-name" value="MtpTests.apk"/> + </target_preparer> + <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer"/> + <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer"/> + <option name="test-suite-tag" value="apct"/> + <option name="test-tag" value="MtpTests"/> + + <test class="com.android.tradefed.testtype.AndroidJUnitTest"> + <option name="package" value="android.mtp"/> + <option name="runner" value="android.support.test.runner.AndroidJUnitRunner"/> + </test> +</configuration>
\ No newline at end of file diff --git a/media/tests/MtpTests/src/android/mtp/MtpStorageManagerTest.java b/media/tests/MtpTests/src/android/mtp/MtpStorageManagerTest.java new file mode 100644 index 000000000000..0d7f3feaaae6 --- /dev/null +++ b/media/tests/MtpTests/src/android/mtp/MtpStorageManagerTest.java @@ -0,0 +1,1657 @@ +/* + * 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.mtp; + +import android.os.FileUtils; +import android.os.UserHandle; +import android.os.storage.StorageVolume; +import android.support.test.filters.SmallTest; +import android.support.test.InstrumentationRegistry; +import android.util.Log; + +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Assert; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.FixMethodOrder; +import org.junit.Test; +import org.junit.runners.MethodSorters; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.UUID; +import java.util.function.Predicate; +import java.util.stream.Stream; + +/** + * Tests for MtpStorageManager functionality. + */ +@RunWith(JUnit4.class) +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +public class MtpStorageManagerTest { + private static final String TAG = MtpStorageManagerTest.class.getSimpleName(); + + private static final String TEMP_DIR = InstrumentationRegistry.getContext().getFilesDir() + + "/" + TAG + "/"; + private static final File TEMP_DIR_FILE = new File(TEMP_DIR); + + private MtpStorageManager manager; + + private ArrayList<Integer> objectsAdded; + private ArrayList<Integer> objectsRemoved; + + private File mainStorageDir; + private File secondaryStorageDir; + + private MtpStorage mainMtpStorage; + private MtpStorage secondaryMtpStorage; + + static { + MtpStorageManager.sDebug = true; + } + + private static void logMethodName() { + Log.d(TAG, Thread.currentThread().getStackTrace()[3].getMethodName()); + } + + private static File createNewFile(File parent) { + return createNewFile(parent, UUID.randomUUID().toString()); + } + + private static File createNewFile(File parent, String name) { + try { + File ret = new File(parent, name); + if (!ret.createNewFile()) + throw new AssertionError("Failed to create file"); + return ret; + } catch (IOException e) { + throw new AssertionError(e.getMessage()); + } + } + + private static File createNewDir(File parent, String name) { + File ret = new File(parent, name); + if (!ret.mkdir()) + throw new AssertionError("Failed to create file"); + return ret; + } + + private static File createNewDir(File parent) { + return createNewDir(parent, UUID.randomUUID().toString()); + } + + @Before + public void before() { + Assert.assertTrue(TEMP_DIR_FILE.mkdir()); + mainStorageDir = createNewDir(TEMP_DIR_FILE); + secondaryStorageDir = createNewDir(TEMP_DIR_FILE); + + StorageVolume mainStorage = new StorageVolume("1", mainStorageDir, "", true, false, true, + false, -1, UserHandle.CURRENT, "", ""); + StorageVolume secondaryStorage = new StorageVolume("2", secondaryStorageDir, "", false, + false, true, false, -1, UserHandle.CURRENT, "", ""); + + objectsAdded = new ArrayList<>(); + objectsRemoved = new ArrayList<>(); + + manager = new MtpStorageManager(new MtpStorageManager.MtpNotifier() { + @Override + public void sendObjectAdded(int id) { + objectsAdded.add(id); + } + + @Override + public void sendObjectRemoved(int id) { + objectsRemoved.add(id); + } + }, null); + + mainMtpStorage = manager.addMtpStorage(mainStorage); + secondaryMtpStorage = manager.addMtpStorage(secondaryStorage); + } + + @After + public void after() { + manager.close(); + FileUtils.deleteContentsAndDir(TEMP_DIR_FILE); + } + + /** MtpObject getter tests. **/ + + @Test + @SmallTest + public void testMtpObjectGetNameRoot() { + logMethodName(); + MtpStorageManager.MtpObject obj = manager.getStorageRoot(mainMtpStorage.getStorageId()); + Assert.assertEquals(obj.getName(), mainStorageDir.getPath()); + } + + @Test + @SmallTest + public void testMtpObjectGetNameNonRoot() { + logMethodName(); + File newFile = createNewFile(mainStorageDir); + Stream<MtpStorageManager.MtpObject> stream = manager.getObjects(0xFFFFFFFF, 0, + mainMtpStorage.getStorageId()); + Assert.assertEquals(stream.findFirst().get().getName(), newFile.getName()); + } + + @Test + @SmallTest + public void testMtpObjectGetIdRoot() { + logMethodName(); + MtpStorageManager.MtpObject obj = manager.getStorageRoot(mainMtpStorage.getStorageId()); + Assert.assertEquals(obj.getId(), mainMtpStorage.getStorageId()); + } + + @Test + @SmallTest + public void testMtpObjectGetIdNonRoot() { + logMethodName(); + File newFile = createNewFile(mainStorageDir); + Stream<MtpStorageManager.MtpObject> stream = manager.getObjects(0xFFFFFFFF, 0, + mainMtpStorage.getStorageId()); + Assert.assertEquals(stream.findFirst().get().getId(), 1); + } + + @Test + @SmallTest + public void testMtpObjectIsDirTrue() { + logMethodName(); + MtpStorageManager.MtpObject obj = manager.getStorageRoot(mainMtpStorage.getStorageId()); + Assert.assertTrue(obj.isDir()); + } + + @Test + @SmallTest + public void testMtpObjectIsDirFalse() { + logMethodName(); + File newFile = createNewFile(mainStorageDir, "TEST123.mp3"); + Stream<MtpStorageManager.MtpObject> stream = manager.getObjects(0xFFFFFFFF, 0, + mainMtpStorage.getStorageId()); + Assert.assertFalse(stream.findFirst().get().isDir()); + } + + @Test + @SmallTest + public void testMtpObjectGetFormatDir() { + logMethodName(); + File newFile = createNewDir(mainStorageDir); + Stream<MtpStorageManager.MtpObject> stream = manager.getObjects(0xFFFFFFFF, 0, + mainMtpStorage.getStorageId()); + Assert.assertEquals(stream.findFirst().get().getFormat(), MtpConstants.FORMAT_ASSOCIATION); + } + + @Test + @SmallTest + public void testMtpObjectGetFormatNonDir() { + logMethodName(); + File newFile = createNewFile(mainStorageDir, "TEST123.mp3"); + Stream<MtpStorageManager.MtpObject> stream = manager.getObjects(0xFFFFFFFF, 0, + mainMtpStorage.getStorageId()); + Assert.assertEquals(stream.findFirst().get().getFormat(), MtpConstants.FORMAT_MP3); + } + + @Test + @SmallTest + public void testMtpObjectGetStorageId() { + logMethodName(); + File newFile = createNewFile(mainStorageDir); + Stream<MtpStorageManager.MtpObject> stream = manager.getObjects(0xFFFFFFFF, 0, + mainMtpStorage.getStorageId()); + Assert.assertEquals(stream.findFirst().get().getStorageId(), mainMtpStorage.getStorageId()); + } + + @Test + @SmallTest + public void testMtpObjectGetLastModified() { + logMethodName(); + File newFile = createNewFile(mainStorageDir); + Stream<MtpStorageManager.MtpObject> stream = manager.getObjects(0xFFFFFFFF, 0, + mainMtpStorage.getStorageId()); + Assert.assertEquals(stream.findFirst().get().getModifiedTime(), + newFile.lastModified() / 1000); + } + + @Test + @SmallTest + public void testMtpObjectGetParent() { + logMethodName(); + File newFile = createNewFile(mainStorageDir); + Stream<MtpStorageManager.MtpObject> stream = manager.getObjects(0xFFFFFFFF, 0, + mainMtpStorage.getStorageId()); + Assert.assertEquals(stream.findFirst().get().getParent(), + manager.getStorageRoot(mainMtpStorage.getStorageId())); + } + + @Test + @SmallTest + public void testMtpObjectGetRoot() { + logMethodName(); + File newFile = createNewFile(mainStorageDir); + Stream<MtpStorageManager.MtpObject> stream = manager.getObjects(0xFFFFFFFF, 0, + mainMtpStorage.getStorageId()); + Assert.assertEquals(stream.findFirst().get().getRoot(), + manager.getStorageRoot(mainMtpStorage.getStorageId())); + } + + @Test + @SmallTest + public void testMtpObjectGetPath() { + logMethodName(); + File newFile = createNewFile(mainStorageDir); + Stream<MtpStorageManager.MtpObject> stream = manager.getObjects(0xFFFFFFFF, 0, + mainMtpStorage.getStorageId()); + Assert.assertEquals(stream.findFirst().get().getPath().toString(), newFile.getPath()); + } + + @Test + @SmallTest + public void testMtpObjectGetSize() { + logMethodName(); + File newFile = createNewFile(mainStorageDir); + try { + new FileOutputStream(newFile).write(new byte[] {0, 0, 0, 0, 0, 0, 0, 0}); + } catch (IOException e) { + Assert.fail(); + } + Stream<MtpStorageManager.MtpObject> stream = manager.getObjects(0xFFFFFFFF, 0, + mainMtpStorage.getStorageId()); + Assert.assertEquals(stream.findFirst().get().getSize(), 8); + } + + @Test + @SmallTest + public void testMtpObjectGetSizeDir() { + logMethodName(); + File newDir = createNewDir(mainStorageDir); + Stream<MtpStorageManager.MtpObject> stream = manager.getObjects(0xFFFFFFFF, 0, + mainMtpStorage.getStorageId()); + Assert.assertEquals(stream.findFirst().get().getSize(), 0); + } + + /** MtpStorageManager cache access tests. **/ + + @Test + @SmallTest + public void testAddMtpStorage() { + logMethodName(); + Assert.assertEquals(mainMtpStorage.getPath(), mainStorageDir.getPath()); + Assert.assertNotNull(manager.getStorageRoot(mainMtpStorage.getStorageId())); + Assert.assertTrue(manager.checkConsistency()); + } + + @Test + @SmallTest + public void testRemoveMtpStorage() { + logMethodName(); + File newFile = createNewFile(secondaryStorageDir); + Stream<MtpStorageManager.MtpObject> stream = manager.getObjects(0xFFFFFFFF, 0, + secondaryMtpStorage.getStorageId()); + Assert.assertEquals(stream.count(), 1); + + manager.removeMtpStorage(secondaryMtpStorage); + Assert.assertNull(manager.getStorageRoot(secondaryMtpStorage.getStorageId())); + Assert.assertNull(manager.getObject(1)); + Assert.assertTrue(manager.checkConsistency()); + } + + @Test + @SmallTest + public void testGetByPath() { + logMethodName(); + File newFile = createNewFile(createNewDir(createNewDir(mainStorageDir))); + + MtpStorageManager.MtpObject obj = manager.getByPath(newFile.getPath()); + Assert.assertNotNull(obj); + Assert.assertEquals(obj.getPath().toString(), newFile.getPath()); + Assert.assertTrue(manager.checkConsistency()); + } + + @Test + @SmallTest + public void testGetByPathError() { + logMethodName(); + File newFile = createNewFile(createNewDir(createNewDir(mainStorageDir))); + + MtpStorageManager.MtpObject obj = manager.getByPath(newFile.getPath() + "q"); + Assert.assertNull(obj); + Assert.assertTrue(manager.checkConsistency()); + } + + @Test + @SmallTest + public void testGetObject() { + logMethodName(); + File newFile = createNewFile(createNewDir(createNewDir(mainStorageDir))); + MtpStorageManager.MtpObject obj = manager.getByPath(newFile.getPath()); + Assert.assertNotNull(obj); + + Assert.assertEquals(manager.getObject(obj.getId()), obj); + Assert.assertTrue(manager.checkConsistency()); + } + + @Test + @SmallTest + public void testGetObjectError() { + logMethodName(); + File newFile = createNewFile(createNewDir(createNewDir(mainStorageDir))); + + Assert.assertNull(manager.getObject(42)); + Assert.assertTrue(manager.checkConsistency()); + } + + @Test + @SmallTest + public void testGetStorageRoot() { + logMethodName(); + MtpStorageManager.MtpObject obj = manager.getStorageRoot(mainMtpStorage.getStorageId()); + Assert.assertEquals(obj.getPath().toString(), mainStorageDir.getPath()); + } + + @Test + @SmallTest + public void testGetObjectsParent() { + logMethodName(); + File newDir = createNewDir(createNewDir(mainStorageDir)); + File newFile = createNewFile(newDir); + File newMP3File = createNewFile(newDir, "lalala.mp3"); + MtpStorageManager.MtpObject parent = manager.getByPath(newDir.getPath()); + Assert.assertNotNull(parent); + + Stream<MtpStorageManager.MtpObject> stream = manager.getObjects(parent.getId(), 0, + mainMtpStorage.getStorageId()); + Assert.assertEquals(stream.count(), 2); + Assert.assertTrue(manager.checkConsistency()); + } + + @Test + @SmallTest + public void testGetObjectsFormat() { + logMethodName(); + File newDir = createNewDir(createNewDir(mainStorageDir)); + File newFile = createNewFile(newDir); + File newMP3File = createNewFile(newDir, "lalala.mp3"); + MtpStorageManager.MtpObject parent = manager.getByPath(newDir.getPath()); + Assert.assertNotNull(parent); + + Stream<MtpStorageManager.MtpObject> stream = manager.getObjects(parent.getId(), + MtpConstants.FORMAT_MP3, mainMtpStorage.getStorageId()); + Assert.assertEquals(stream.findFirst().get().getPath().toString(), newMP3File.toString()); + Assert.assertTrue(manager.checkConsistency()); + } + + @Test + @SmallTest + public void testGetObjectsRoot() { + logMethodName(); + File newDir = createNewDir(mainStorageDir); + File newFile = createNewFile(mainStorageDir); + File newMP3File = createNewFile(newDir, "lalala.mp3"); + + Stream<MtpStorageManager.MtpObject> stream = manager.getObjects(0xFFFFFFFF, 0, + mainMtpStorage.getStorageId()); + Assert.assertEquals(stream.count(), 2); + Assert.assertTrue(manager.checkConsistency()); + } + + @Test + @SmallTest + public void testGetObjectsAll() { + logMethodName(); + File newDir = createNewDir(mainStorageDir); + File newFile = createNewFile(mainStorageDir); + File newMP3File = createNewFile(newDir, "lalala.mp3"); + + Stream<MtpStorageManager.MtpObject> stream = manager.getObjects(0, 0, + mainMtpStorage.getStorageId()); + Assert.assertEquals(stream.count(), 3); + Assert.assertTrue(manager.checkConsistency()); + } + + @Test + @SmallTest + public void testGetObjectsAllStorages() { + logMethodName(); + File newDir = createNewDir(mainStorageDir); + createNewFile(mainStorageDir); + createNewFile(newDir, "lalala.mp3"); + File newDir2 = createNewDir(secondaryStorageDir); + createNewFile(secondaryStorageDir); + createNewFile(newDir2, "lalala.mp3"); + + Stream<MtpStorageManager.MtpObject> stream = manager.getObjects(0, 0, 0xFFFFFFFF); + Assert.assertEquals(stream.count(), 6); + Assert.assertTrue(manager.checkConsistency()); + } + + @Test + @SmallTest + public void testGetObjectsAllStoragesRoot() { + logMethodName(); + File newDir = createNewDir(mainStorageDir); + createNewFile(mainStorageDir); + createNewFile(newDir, "lalala.mp3"); + File newDir2 = createNewDir(secondaryStorageDir); + createNewFile(secondaryStorageDir); + createNewFile(newDir2, "lalala.mp3"); + + Stream<MtpStorageManager.MtpObject> stream = manager.getObjects(0xFFFFFFFF, 0, 0xFFFFFFFF); + Assert.assertEquals(stream.count(), 4); + Assert.assertTrue(manager.checkConsistency()); + } + + /** MtpStorageManager event handling tests. **/ + + @Test + @SmallTest + public void testObjectAdded() { + logMethodName(); + Stream<MtpStorageManager.MtpObject> stream = manager.getObjects(0xFFFFFFFF, 0, + mainMtpStorage.getStorageId()); + Assert.assertEquals(stream.count(), 0); + + File newFile = createNewFile(mainStorageDir); + manager.flushEvents(); + Assert.assertEquals(objectsAdded.size(), 1); + Assert.assertEquals(manager.getObject(objectsAdded.get(0)).getPath().toString(), + newFile.getPath()); + Assert.assertTrue(manager.checkConsistency()); + } + + @Test + @SmallTest + public void testObjectAddedDir() { + logMethodName(); + Stream<MtpStorageManager.MtpObject> stream = manager.getObjects(0xFFFFFFFF, 0, + mainMtpStorage.getStorageId()); + Assert.assertEquals(stream.count(), 0); + + File newDir = createNewDir(mainStorageDir); + manager.flushEvents(); + Assert.assertEquals(objectsAdded.size(), 1); + Assert.assertEquals(manager.getObject(objectsAdded.get(0)).getPath().toString(), + newDir.getPath()); + Assert.assertTrue(manager.getObject(objectsAdded.get(0)).isDir()); + Assert.assertTrue(manager.checkConsistency()); + } + + @Test + @SmallTest + public void testObjectAddedRecursiveDir() { + logMethodName(); + Stream<MtpStorageManager.MtpObject> stream = manager.getObjects(0xFFFFFFFF, 0, + mainMtpStorage.getStorageId()); + Assert.assertEquals(stream.count(), 0); + + File newDir = createNewDir(createNewDir(createNewDir(mainStorageDir))); + manager.flushEvents(); + Assert.assertEquals(objectsAdded.size(), 3); + Assert.assertEquals(manager.getObject(objectsAdded.get(2)).getPath().toString(), + newDir.getPath()); + Assert.assertTrue(manager.getObject(objectsAdded.get(2)).isDir()); + Assert.assertTrue(manager.checkConsistency()); + } + + @Test + @SmallTest + public void testObjectRemoved() { + logMethodName(); + File newFile = createNewFile(mainStorageDir); + Stream<MtpStorageManager.MtpObject> stream = manager.getObjects(0xFFFFFFFF, 0, + mainMtpStorage.getStorageId()); + Assert.assertEquals(stream.count(), 1); + + Assert.assertTrue(newFile.delete()); + manager.flushEvents(); + Assert.assertEquals(objectsRemoved.size(), 1); + Assert.assertNull(manager.getObject(objectsRemoved.get(0))); + Assert.assertTrue(manager.checkConsistency()); + } + + @Test + @SmallTest + public void testObjectMoved() { + logMethodName(); + File newFile = createNewFile(mainStorageDir); + Stream<MtpStorageManager.MtpObject> stream = manager.getObjects(0xFFFFFFFF, 0, + mainMtpStorage.getStorageId()); + Assert.assertEquals(stream.count(), 1); + File toFile = new File(mainStorageDir, "to" + newFile.getName()); + + Assert.assertTrue(newFile.renameTo(toFile)); + manager.flushEvents(); + Assert.assertEquals(objectsAdded.size(), 1); + Assert.assertEquals(objectsRemoved.size(), 1); + Assert.assertEquals(manager.getObject(objectsAdded.get(0)).getPath().toString(), + toFile.getPath()); + Assert.assertNull(manager.getObject(objectsRemoved.get(0))); + Assert.assertTrue(manager.checkConsistency()); + } + + /** MtpStorageManager operation tests. Ensure that events are not sent for the main operation, + and also test all possible cases of other processes accessing the file at the same time, as + well as cases of both failure and success. **/ + + @Test + @SmallTest + public void testSendObjectSuccess() { + logMethodName(); + Stream<MtpStorageManager.MtpObject> stream = manager.getObjects(0xFFFFFFFF, 0, + mainMtpStorage.getStorageId()); + int id = manager.beginSendObject(manager.getStorageRoot(mainMtpStorage.getStorageId()), + "newFile", MtpConstants.FORMAT_UNDEFINED); + Assert.assertEquals(id, 1); + + File newFile = createNewFile(mainStorageDir, "newFile"); + manager.flushEvents(); + MtpStorageManager.MtpObject obj = manager.getObject(id); + Assert.assertTrue(manager.endSendObject(obj, true)); + Assert.assertEquals(obj.getPath().toString(), newFile.getPath()); + Assert.assertEquals(objectsAdded.size(), 0); + Assert.assertTrue(manager.checkConsistency()); + } + + @Test + @SmallTest + public void testSendObjectSuccessDir() { + logMethodName(); + Stream<MtpStorageManager.MtpObject> stream = manager.getObjects(0xFFFFFFFF, 0, + mainMtpStorage.getStorageId()); + int id = manager.beginSendObject(manager.getStorageRoot(mainMtpStorage.getStorageId()), + "newDir", MtpConstants.FORMAT_ASSOCIATION); + Assert.assertEquals(id, 1); + + File newFile = createNewDir(mainStorageDir, "newDir"); + manager.flushEvents(); + MtpStorageManager.MtpObject obj = manager.getObject(id); + Assert.assertTrue(manager.endSendObject(obj, true)); + Assert.assertEquals(obj.getPath().toString(), newFile.getPath()); + Assert.assertEquals(objectsAdded.size(), 0); + Assert.assertEquals(obj.getFormat(), MtpConstants.FORMAT_ASSOCIATION); + Assert.assertTrue(manager.checkConsistency()); + + // Check that new dir receives events + File newerFile = createNewFile(newFile); + manager.flushEvents(); + Assert.assertEquals(objectsAdded.size(), 1); + Assert.assertEquals(manager.getObject(objectsAdded.get(0)).getPath().toString(), + newerFile.getPath()); + } + + @Test + @SmallTest + public void testSendObjectSuccessDelayed() { + logMethodName(); + Stream<MtpStorageManager.MtpObject> stream = manager.getObjects(0xFFFFFFFF, 0, + mainMtpStorage.getStorageId()); + int id = manager.beginSendObject(manager.getStorageRoot(mainMtpStorage.getStorageId()), + "newFile", MtpConstants.FORMAT_UNDEFINED); + Assert.assertEquals(id, 1); + MtpStorageManager.MtpObject obj = manager.getObject(id); + Assert.assertTrue(manager.endSendObject(obj, true)); + + File newFile = createNewFile(mainStorageDir, "newFile"); + manager.flushEvents(); + Assert.assertEquals(obj.getPath().toString(), newFile.getPath()); + Assert.assertEquals(objectsAdded.size(), 0); + Assert.assertTrue(manager.checkConsistency()); + } + + @Test + @SmallTest + public void testSendObjectSuccessDirDelayed() { + logMethodName(); + Stream<MtpStorageManager.MtpObject> stream = manager.getObjects(0xFFFFFFFF, 0, + mainMtpStorage.getStorageId()); + int id = manager.beginSendObject(manager.getStorageRoot(mainMtpStorage.getStorageId()), + "newDir", MtpConstants.FORMAT_ASSOCIATION); + Assert.assertEquals(id, 1); + + MtpStorageManager.MtpObject obj = manager.getObject(id); + Assert.assertTrue(manager.endSendObject(obj, true)); + File newFile = createNewDir(mainStorageDir, "newDir"); + manager.flushEvents(); + Assert.assertEquals(obj.getPath().toString(), newFile.getPath()); + Assert.assertEquals(objectsAdded.size(), 0); + Assert.assertEquals(obj.getFormat(), MtpConstants.FORMAT_ASSOCIATION); + Assert.assertTrue(manager.checkConsistency()); + + // Check that new dir receives events + File newerFile = createNewFile(newFile); + manager.flushEvents(); + Assert.assertEquals(objectsAdded.size(), 1); + Assert.assertEquals(manager.getObject(objectsAdded.get(0)).getPath().toString(), + newerFile.getPath()); + } + + @Test + @SmallTest + public void testSendObjectSuccessDeleted() { + logMethodName(); + Stream<MtpStorageManager.MtpObject> stream = manager.getObjects(0xFFFFFFFF, 0, + mainMtpStorage.getStorageId()); + int id = manager.beginSendObject(manager.getStorageRoot(mainMtpStorage.getStorageId()), + "newFile", MtpConstants.FORMAT_UNDEFINED); + Assert.assertEquals(id, 1); + + File newFile = createNewFile(mainStorageDir, "newFile"); + Assert.assertTrue(newFile.delete()); + manager.flushEvents(); + MtpStorageManager.MtpObject obj = manager.getObject(id); + Assert.assertTrue(manager.endSendObject(obj, true)); + Assert.assertNull(manager.getObject(obj.getId())); + Assert.assertEquals(objectsRemoved.get(0).intValue(), obj.getId()); + Assert.assertTrue(manager.checkConsistency()); + } + + @Test + @SmallTest + public void testSendObjectFailed() { + logMethodName(); + Stream<MtpStorageManager.MtpObject> stream = manager.getObjects(0xFFFFFFFF, 0, + mainMtpStorage.getStorageId()); + int id = manager.beginSendObject(manager.getStorageRoot(mainMtpStorage.getStorageId()), + "newFile", MtpConstants.FORMAT_UNDEFINED); + Assert.assertEquals(id, 1); + + MtpStorageManager.MtpObject obj = manager.getObject(id); + Assert.assertTrue(manager.endSendObject(obj, false)); + Assert.assertTrue(manager.checkConsistency()); + } + + @Test + @SmallTest + public void testSendObjectFailedDeleted() { + logMethodName(); + Stream<MtpStorageManager.MtpObject> stream = manager.getObjects(0xFFFFFFFF, 0, + mainMtpStorage.getStorageId()); + int id = manager.beginSendObject(manager.getStorageRoot(mainMtpStorage.getStorageId()), + "newFile", MtpConstants.FORMAT_UNDEFINED); + Assert.assertEquals(id, 1); + MtpStorageManager.MtpObject obj = manager.getObject(id); + + File newFile = createNewFile(mainStorageDir, "newFile"); + Assert.assertTrue(newFile.delete()); + manager.flushEvents(); + Assert.assertTrue(manager.endSendObject(obj, false)); + Assert.assertEquals(objectsRemoved.size(), 0); + Assert.assertEquals(objectsAdded.size(), 0); + Assert.assertTrue(manager.checkConsistency()); + } + + @Test + @SmallTest + public void testSendObjectFailedAdded() { + logMethodName(); + Stream<MtpStorageManager.MtpObject> stream = manager.getObjects(0xFFFFFFFF, 0, + mainMtpStorage.getStorageId()); + int id = manager.beginSendObject(manager.getStorageRoot(mainMtpStorage.getStorageId()), + "newFile", MtpConstants.FORMAT_UNDEFINED); + Assert.assertEquals(id, 1); + MtpStorageManager.MtpObject obj = manager.getObject(id); + + File newDir = createNewDir(mainStorageDir, "newFile"); + manager.flushEvents(); + Assert.assertTrue(manager.endSendObject(obj, false)); + Assert.assertNotEquals(objectsAdded.get(0).intValue(), id); + Assert.assertNull(manager.getObject(id)); + Assert.assertEquals(manager.getObject(objectsAdded.get(0)).getPath().toString(), + newDir.getPath()); + Assert.assertTrue(manager.checkConsistency()); + + // Expect events in new dir + createNewFile(newDir); + manager.flushEvents(); + Assert.assertEquals(objectsAdded.size(), 2); + Assert.assertTrue(manager.checkConsistency()); + } + + @Test + @SmallTest + public void testRemoveObjectSuccess() { + logMethodName(); + File newFile = createNewFile(mainStorageDir); + MtpStorageManager.MtpObject obj = manager.getObjects(0xFFFFFFFF, 0, + mainMtpStorage.getStorageId()).findFirst().get(); + Assert.assertTrue(manager.beginRemoveObject(obj)); + + Assert.assertTrue(newFile.delete()); + manager.flushEvents(); + Assert.assertTrue(manager.endRemoveObject(obj, true)); + Assert.assertEquals(objectsRemoved.size(), 0); + Assert.assertNull(manager.getObject(obj.getId())); + Assert.assertTrue(manager.checkConsistency()); + } + + @Test + @SmallTest + public void testRemoveObjectDelayed() { + logMethodName(); + File newFile = createNewFile(mainStorageDir); + MtpStorageManager.MtpObject obj = manager.getObjects(0xFFFFFFFF, 0, + mainMtpStorage.getStorageId()).findFirst().get(); + Assert.assertTrue(manager.beginRemoveObject(obj)); + + Assert.assertTrue(manager.endRemoveObject(obj, true)); + Assert.assertTrue(newFile.delete()); + manager.flushEvents(); + Assert.assertEquals(objectsRemoved.size(), 0); + Assert.assertNull(manager.getObject(obj.getId())); + Assert.assertTrue(manager.checkConsistency()); + } + + @Test + @SmallTest + public void testRemoveObjectDir() { + logMethodName(); + File newDir = createNewDir(mainStorageDir); + createNewFile(createNewDir(newDir)); + MtpStorageManager.MtpObject obj = manager.getObjects(0xFFFFFFFF, 0, + mainMtpStorage.getStorageId()).findFirst().get(); + manager.getObjects(obj.getId(), 0, mainMtpStorage.getStorageId()); + Assert.assertTrue(manager.beginRemoveObject(obj)); + + createNewFile(newDir); + Assert.assertTrue(FileUtils.deleteContentsAndDir(newDir)); + manager.flushEvents(); + Assert.assertTrue(manager.endRemoveObject(obj, true)); + Assert.assertEquals(objectsAdded.size(), 1); + Assert.assertEquals(objectsRemoved.size(), 1); + Assert.assertEquals(manager.getObjects(0, 0, mainMtpStorage.getStorageId()).count(), 0); + Assert.assertNull(manager.getObject(obj.getId())); + Assert.assertTrue(manager.checkConsistency()); + } + + @Test + @SmallTest + public void testRemoveObjectDirDelayed() { + logMethodName(); + File newDir = createNewDir(mainStorageDir); + createNewFile(createNewDir(newDir)); + MtpStorageManager.MtpObject obj = manager.getObjects(0xFFFFFFFF, 0, + mainMtpStorage.getStorageId()).findFirst().get(); + Assert.assertTrue(manager.beginRemoveObject(obj)); + + Assert.assertTrue(manager.endRemoveObject(obj, true)); + Assert.assertTrue(FileUtils.deleteContentsAndDir(newDir)); + manager.flushEvents(); + Assert.assertEquals(objectsRemoved.size(), 0); + Assert.assertEquals(manager.getObjects(0, 0, mainMtpStorage.getStorageId()).count(), 0); + Assert.assertNull(manager.getObject(obj.getId())); + Assert.assertTrue(manager.checkConsistency()); + } + + @Test + @SmallTest + public void testRemoveObjectSuccessAdded() { + logMethodName(); + File newFile = createNewFile(mainStorageDir); + MtpStorageManager.MtpObject obj = manager.getObjects(0xFFFFFFFF, 0, + mainMtpStorage.getStorageId()).findFirst().get(); + int id = obj.getId(); + Assert.assertTrue(manager.beginRemoveObject(obj)); + + Assert.assertTrue(newFile.delete()); + createNewFile(mainStorageDir, newFile.getName()); + manager.flushEvents(); + Assert.assertTrue(manager.endRemoveObject(obj, true)); + Assert.assertEquals(objectsRemoved.size(), 0); + Assert.assertEquals(objectsAdded.size(), 1); + Assert.assertNull(manager.getObject(id)); + Assert.assertNotEquals(objectsAdded.get(0).intValue(), id); + Assert.assertTrue(manager.checkConsistency()); + } + + @Test + @SmallTest + public void testRemoveObjectFailed() { + logMethodName(); + File newFile = createNewFile(mainStorageDir); + MtpStorageManager.MtpObject obj = manager.getObjects(0xFFFFFFFF, 0, + mainMtpStorage.getStorageId()).findFirst().get(); + Assert.assertTrue(manager.beginRemoveObject(obj)); + + Assert.assertTrue(manager.endRemoveObject(obj, false)); + Assert.assertEquals(manager.getObject(obj.getId()), obj); + Assert.assertTrue(manager.checkConsistency()); + } + + @Test + @SmallTest + public void testRemoveObjectFailedDir() { + logMethodName(); + File newDir = createNewDir(mainStorageDir); + MtpStorageManager.MtpObject obj = manager.getObjects(0xFFFFFFFF, 0, + mainMtpStorage.getStorageId()).findFirst().get(); + manager.getObjects(obj.getId(), 0, mainMtpStorage.getStorageId()); + Assert.assertTrue(manager.beginRemoveObject(obj)); + + createNewFile(newDir); + manager.flushEvents(); + Assert.assertTrue(manager.endRemoveObject(obj, false)); + Assert.assertEquals(manager.getObject(obj.getId()), obj); + Assert.assertEquals(objectsAdded.size(), 1); + Assert.assertTrue(manager.checkConsistency()); + } + + @Test + @SmallTest + public void testRemoveObjectFailedRemoved() { + logMethodName(); + File newFile = createNewFile(mainStorageDir); + MtpStorageManager.MtpObject obj = manager.getObjects(0xFFFFFFFF, 0, + mainMtpStorage.getStorageId()).findFirst().get(); + Assert.assertTrue(manager.beginRemoveObject(obj)); + + Assert.assertTrue(newFile.delete()); + manager.flushEvents(); + Assert.assertTrue(manager.endRemoveObject(obj, false)); + Assert.assertEquals(objectsRemoved.size(), 1); + Assert.assertEquals(objectsRemoved.get(0).intValue(), obj.getId()); + Assert.assertNull(manager.getObject(obj.getId())); + Assert.assertTrue(manager.checkConsistency()); + } + + @Test + @SmallTest + public void testCopyObjectSuccess() { + logMethodName(); + File newFile = createNewFile(mainStorageDir); + File newDir = createNewDir(mainStorageDir); + MtpStorageManager.MtpObject dirObj = manager.getObjects(0xFFFFFFFF, 0, + mainMtpStorage.getStorageId()) + .filter(MtpStorageManager.MtpObject::isDir).findFirst().get(); + MtpStorageManager.MtpObject fileObj = manager.getObjects(0xFFFFFFFF, 0, + mainMtpStorage.getStorageId()) + .filter(o -> !o.isDir()).findFirst().get(); + + int id = manager.beginCopyObject(fileObj, dirObj); + Assert.assertNotEquals(id, -1); + createNewFile(newDir, newFile.getName()); + manager.flushEvents(); + MtpStorageManager.MtpObject obj = manager.getObject(id); + Assert.assertTrue(manager.endCopyObject(obj, true)); + Assert.assertEquals(objectsAdded.size(), 0); + Assert.assertTrue(manager.checkConsistency()); + } + + @Test + @SmallTest + public void testCopyObjectSuccessRecursive() { + logMethodName(); + File newDirFrom = createNewDir(mainStorageDir); + File newDirFrom1 = createNewDir(newDirFrom); + File newDirFrom2 = createNewFile(newDirFrom1); + File delayedFile = createNewFile(newDirFrom); + File deletedFile = createNewFile(newDirFrom); + File newDirTo = createNewDir(mainStorageDir); + MtpStorageManager.MtpObject toObj = manager.getObjects(0xFFFFFFFF, 0, + mainMtpStorage.getStorageId()) + .filter(o -> o.getName().equals(newDirTo.getName())).findFirst().get(); + MtpStorageManager.MtpObject fromObj = manager.getObjects(0xFFFFFFFF, 0, + mainMtpStorage.getStorageId()) + .filter(o -> o.getName().equals(newDirFrom.getName())).findFirst().get(); + + manager.getObjects(fromObj.getId(), 0, mainMtpStorage.getStorageId()); + int id = manager.beginCopyObject(fromObj, toObj); + Assert.assertNotEquals(id, -1); + File copiedDir = createNewDir(newDirTo, newDirFrom.getName()); + File copiedDir1 = createNewDir(copiedDir, newDirFrom1.getName()); + createNewFile(copiedDir1, newDirFrom2.getName()); + createNewFile(copiedDir, "extraFile"); + File toDelete = createNewFile(copiedDir, deletedFile.getName()); + manager.flushEvents(); + Assert.assertTrue(toDelete.delete()); + manager.flushEvents(); + MtpStorageManager.MtpObject obj = manager.getObject(id); + Assert.assertTrue(manager.endCopyObject(obj, true)); + Assert.assertEquals(objectsAdded.size(), 1); + Assert.assertEquals(objectsRemoved.size(), 1); + + createNewFile(copiedDir, delayedFile.getName()); + manager.flushEvents(); + Assert.assertTrue(manager.checkConsistency()); + + // Expect events in the visited dir, but not the unvisited dir. + createNewFile(copiedDir); + createNewFile(copiedDir1); + manager.flushEvents(); + Assert.assertEquals(objectsAdded.size(), 2); + Assert.assertEquals(objectsAdded.size(), 2); + + // Number of files/dirs created, minus the one that was deleted. + Assert.assertEquals(manager.getObjects(0, 0, mainMtpStorage.getStorageId()).count(), 13); + Assert.assertTrue(manager.checkConsistency()); + } + + @Test + @SmallTest + public void testCopyObjectFailed() { + logMethodName(); + File newFile = createNewFile(mainStorageDir); + File newDir = createNewDir(mainStorageDir); + MtpStorageManager.MtpObject dirObj = manager.getObjects(0xFFFFFFFF, 0, + mainMtpStorage.getStorageId()) + .filter(MtpStorageManager.MtpObject::isDir).findFirst().get(); + MtpStorageManager.MtpObject fileObj = manager.getObjects(0xFFFFFFFF, 0, + mainMtpStorage.getStorageId()) + .filter(o -> !o.isDir()).findFirst().get(); + + int id = manager.beginCopyObject(fileObj, dirObj); + Assert.assertNotEquals(id, -1); + manager.flushEvents(); + MtpStorageManager.MtpObject obj = manager.getObject(id); + Assert.assertTrue(manager.endCopyObject(obj, false)); + Assert.assertEquals(objectsAdded.size(), 0); + Assert.assertTrue(manager.checkConsistency()); + } + + @Test + @SmallTest + public void testCopyObjectFailedAdded() { + logMethodName(); + File newFile = createNewFile(mainStorageDir); + File newDir = createNewDir(mainStorageDir); + MtpStorageManager.MtpObject dirObj = manager.getObjects(0xFFFFFFFF, 0, + mainMtpStorage.getStorageId()) + .filter(MtpStorageManager.MtpObject::isDir).findFirst().get(); + MtpStorageManager.MtpObject fileObj = manager.getObjects(0xFFFFFFFF, 0, + mainMtpStorage.getStorageId()) + .filter(o -> !o.isDir()).findFirst().get(); + + int id = manager.beginCopyObject(fileObj, dirObj); + Assert.assertNotEquals(id, -1); + File addedDir = createNewDir(newDir, newFile.getName()); + manager.flushEvents(); + MtpStorageManager.MtpObject obj = manager.getObject(id); + Assert.assertTrue(manager.endCopyObject(obj, false)); + Assert.assertEquals(objectsAdded.size(), 1); + Assert.assertNotEquals(objectsAdded.get(0).intValue(), id); + Assert.assertTrue(manager.checkConsistency()); + + // Expect events in new dir + createNewFile(addedDir); + manager.flushEvents(); + Assert.assertEquals(objectsAdded.size(), 2); + Assert.assertTrue(manager.checkConsistency()); + } + + @Test + @SmallTest + public void testCopyObjectFailedDeleted() { + logMethodName(); + File newFile = createNewFile(mainStorageDir); + File newDir = createNewDir(mainStorageDir); + MtpStorageManager.MtpObject dirObj = manager.getObjects(0xFFFFFFFF, 0, + mainMtpStorage.getStorageId()) + .filter(MtpStorageManager.MtpObject::isDir).findFirst().get(); + MtpStorageManager.MtpObject fileObj = manager.getObjects(0xFFFFFFFF, 0, + mainMtpStorage.getStorageId()) + .filter(o -> !o.isDir()).findFirst().get(); + + int id = manager.beginCopyObject(fileObj, dirObj); + Assert.assertNotEquals(id, -1); + Assert.assertTrue(createNewFile(newDir, newFile.getName()).delete()); + manager.flushEvents(); + MtpStorageManager.MtpObject obj = manager.getObject(id); + Assert.assertTrue(manager.endCopyObject(obj, false)); + Assert.assertEquals(objectsAdded.size(), 0); + Assert.assertTrue(manager.checkConsistency()); + } + + @Test + @SmallTest + public void testRenameObjectSuccess() { + logMethodName(); + File newFile = createNewFile(mainStorageDir); + MtpStorageManager.MtpObject obj = manager.getObjects(0xFFFFFFFF, 0, + mainMtpStorage.getStorageId()).findFirst().get(); + Assert.assertTrue(manager.beginRenameObject(obj, "renamed")); + + File renamed = new File(mainStorageDir, "renamed"); + Assert.assertTrue(newFile.renameTo(renamed)); + manager.flushEvents(); + Assert.assertTrue(manager.endRenameObject(obj, newFile.getName(), true)); + + Assert.assertEquals(objectsAdded.size(), 0); + Assert.assertEquals(objectsRemoved.size(), 0); + Assert.assertEquals(obj.getPath().toString(), renamed.getPath()); + + Assert.assertTrue(manager.checkConsistency()); + } + + @Test + @SmallTest + public void testRenameObjectDirSuccess() { + logMethodName(); + File newDir = createNewDir(mainStorageDir); + MtpStorageManager.MtpObject obj = manager.getObjects(0xFFFFFFFF, 0, + mainMtpStorage.getStorageId()).findFirst().get(); + Assert.assertTrue(manager.beginRenameObject(obj, "renamed")); + + File renamed = new File(mainStorageDir, "renamed"); + Assert.assertTrue(newDir.renameTo(renamed)); + manager.flushEvents(); + Assert.assertTrue(manager.endRenameObject(obj, newDir.getName(), true)); + + Assert.assertEquals(objectsAdded.size(), 0); + Assert.assertEquals(objectsRemoved.size(), 0); + Assert.assertEquals(obj.getPath().toString(), renamed.getPath()); + + Assert.assertTrue(manager.checkConsistency()); + + // Don't expect events + createNewFile(renamed); + manager.flushEvents(); + Assert.assertEquals(objectsAdded.size(), 0); + Assert.assertTrue(manager.checkConsistency()); + } + + @Test + @SmallTest + public void testRenameObjectDirVisitedSuccess() { + logMethodName(); + File newDir = createNewDir(mainStorageDir); + MtpStorageManager.MtpObject obj = manager.getObjects(0xFFFFFFFF, 0, + mainMtpStorage.getStorageId()).findFirst().get(); + manager.getObjects(obj.getId(), 0, mainMtpStorage.getStorageId()); + Assert.assertTrue(manager.beginRenameObject(obj, "renamed")); + + File renamed = new File(mainStorageDir, "renamed"); + Assert.assertTrue(newDir.renameTo(renamed)); + manager.flushEvents(); + Assert.assertTrue(manager.endRenameObject(obj, newDir.getName(), true)); + + Assert.assertEquals(objectsAdded.size(), 0); + Assert.assertEquals(objectsRemoved.size(), 0); + Assert.assertEquals(obj.getPath().toString(), renamed.getPath()); + + Assert.assertTrue(manager.checkConsistency()); + + // Expect events since the dir was visited + createNewFile(renamed); + manager.flushEvents(); + Assert.assertEquals(objectsAdded.size(), 1); + Assert.assertTrue(manager.checkConsistency()); + } + + @Test + @SmallTest + public void testRenameObjectDelayed() { + logMethodName(); + File newFile = createNewFile(mainStorageDir); + MtpStorageManager.MtpObject obj = manager.getObjects(0xFFFFFFFF, 0, + mainMtpStorage.getStorageId()).findFirst().get(); + Assert.assertTrue(manager.beginRenameObject(obj, "renamed")); + + Assert.assertTrue(manager.endRenameObject(obj, newFile.getName(), true)); + File renamed = new File(mainStorageDir, "renamed"); + Assert.assertTrue(newFile.renameTo(renamed)); + manager.flushEvents(); + + Assert.assertEquals(objectsAdded.size(), 0); + Assert.assertEquals(objectsRemoved.size(), 0); + Assert.assertEquals(obj.getPath().toString(), renamed.getPath()); + + Assert.assertTrue(manager.checkConsistency()); + } + + @Test + @SmallTest + public void testRenameObjectDirVisitedDelayed() { + logMethodName(); + File newDir = createNewDir(mainStorageDir); + MtpStorageManager.MtpObject obj = manager.getObjects(0xFFFFFFFF, 0, + mainMtpStorage.getStorageId()).findFirst().get(); + manager.getObjects(obj.getId(), 0, mainMtpStorage.getStorageId()); + Assert.assertTrue(manager.beginRenameObject(obj, "renamed")); + + Assert.assertTrue(manager.endRenameObject(obj, newDir.getName(), true)); + File renamed = new File(mainStorageDir, "renamed"); + Assert.assertTrue(newDir.renameTo(renamed)); + manager.flushEvents(); + + Assert.assertEquals(objectsAdded.size(), 0); + Assert.assertEquals(objectsRemoved.size(), 0); + Assert.assertEquals(obj.getPath().toString(), renamed.getPath()); + + Assert.assertTrue(manager.checkConsistency()); + + // Expect events since the dir was visited + createNewFile(renamed); + manager.flushEvents(); + Assert.assertEquals(objectsAdded.size(), 1); + Assert.assertTrue(manager.checkConsistency()); + } + + @Test + @SmallTest + public void testRenameObjectFailed() { + logMethodName(); + File newFile = createNewFile(mainStorageDir); + MtpStorageManager.MtpObject obj = manager.getObjects(0xFFFFFFFF, 0, + mainMtpStorage.getStorageId()).findFirst().get(); + Assert.assertTrue(manager.beginRenameObject(obj, "renamed")); + + Assert.assertTrue(manager.endRenameObject(obj, newFile.getName(), false)); + + Assert.assertEquals(objectsAdded.size(), 0); + Assert.assertEquals(objectsRemoved.size(), 0); + + Assert.assertTrue(manager.checkConsistency()); + } + + @Test + @SmallTest + public void testRenameObjectFailedOldRemoved() { + logMethodName(); + File newFile = createNewFile(mainStorageDir); + MtpStorageManager.MtpObject obj = manager.getObjects(0xFFFFFFFF, 0, + mainMtpStorage.getStorageId()).findFirst().get(); + Assert.assertTrue(manager.beginRenameObject(obj, "renamed")); + + Assert.assertTrue(newFile.delete()); + manager.flushEvents(); + Assert.assertTrue(manager.endRenameObject(obj, newFile.getName(), false)); + + Assert.assertEquals(objectsAdded.size(), 0); + Assert.assertEquals(objectsRemoved.size(), 1); + + Assert.assertTrue(manager.checkConsistency()); + } + + @Test + @SmallTest + public void testRenameObjectFailedNewAdded() { + logMethodName(); + File newFile = createNewFile(mainStorageDir); + MtpStorageManager.MtpObject obj = manager.getObjects(0xFFFFFFFF, 0, + mainMtpStorage.getStorageId()).findFirst().get(); + Assert.assertTrue(manager.beginRenameObject(obj, "renamed")); + + createNewFile(mainStorageDir, "renamed"); + manager.flushEvents(); + Assert.assertTrue(manager.endRenameObject(obj, newFile.getName(), false)); + + Assert.assertEquals(objectsAdded.size(), 1); + Assert.assertEquals(objectsRemoved.size(), 0); + + Assert.assertTrue(manager.checkConsistency()); + } + + @Test + @SmallTest + public void testMoveObjectSuccess() { + logMethodName(); + File newFile = createNewFile(mainStorageDir); + File dir = createNewDir(mainStorageDir); + MtpStorageManager.MtpObject dirObj = manager.getObjects(0xFFFFFFFF, 0, + mainMtpStorage.getStorageId()) + .filter(MtpStorageManager.MtpObject::isDir).findFirst().get(); + MtpStorageManager.MtpObject fileObj = manager.getObjects(0xFFFFFFFF, 0, + mainMtpStorage.getStorageId()) + .filter(o -> !o.isDir()).findFirst().get(); + Assert.assertTrue(manager.beginMoveObject(fileObj, dirObj)); + + File moved = new File(dir, newFile.getName()); + Assert.assertTrue(newFile.renameTo(moved)); + manager.flushEvents(); + Assert.assertTrue(manager.endMoveObject( + manager.getStorageRoot(mainMtpStorage.getStorageId()), + dirObj, newFile.getName(), true)); + + Assert.assertEquals(objectsAdded.size(), 0); + Assert.assertEquals(objectsRemoved.size(), 0); + Assert.assertEquals(fileObj.getPath().toString(), moved.getPath()); + + Assert.assertTrue(manager.checkConsistency()); + } + + @Test + @SmallTest + public void testMoveObjectDirSuccess() { + logMethodName(); + File newDir = createNewDir(mainStorageDir); + File movedDir = createNewDir(mainStorageDir); + MtpStorageManager.MtpObject dirObj = manager.getObjects(0xFFFFFFFF, 0, + mainMtpStorage.getStorageId()) + .filter(o -> o.getName().equals(newDir.getName())).findFirst().get(); + MtpStorageManager.MtpObject movedObj = manager.getObjects(0xFFFFFFFF, 0, + mainMtpStorage.getStorageId()) + .filter(o -> o.getName().equals(movedDir.getName())).findFirst().get(); + Assert.assertTrue(manager.beginMoveObject(movedObj, dirObj)); + + File renamed = new File(newDir, movedDir.getName()); + Assert.assertTrue(movedDir.renameTo(renamed)); + manager.flushEvents(); + Assert.assertTrue(manager.endMoveObject( + manager.getStorageRoot(mainMtpStorage.getStorageId()), + dirObj, movedDir.getName(), true)); + + Assert.assertEquals(objectsAdded.size(), 0); + Assert.assertEquals(objectsRemoved.size(), 0); + Assert.assertEquals(movedObj.getPath().toString(), renamed.getPath()); + + Assert.assertTrue(manager.checkConsistency()); + + // Don't expect events + createNewFile(renamed); + manager.flushEvents(); + Assert.assertEquals(objectsAdded.size(), 0); + Assert.assertTrue(manager.checkConsistency()); + } + + @Test + @SmallTest + public void testMoveObjectDirVisitedSuccess() { + logMethodName(); + File newDir = createNewDir(mainStorageDir); + File movedDir = createNewDir(mainStorageDir); + MtpStorageManager.MtpObject dirObj = manager.getObjects(0xFFFFFFFF, 0, + mainMtpStorage.getStorageId()) + .filter(o -> o.getName().equals(newDir.getName())).findFirst().get(); + MtpStorageManager.MtpObject movedObj = manager.getObjects(0xFFFFFFFF, 0, + mainMtpStorage.getStorageId()) + .filter(o -> o.getName().equals(movedDir.getName())).findFirst().get(); + manager.getObjects(movedObj.getId(), 0, mainMtpStorage.getStorageId()); + Assert.assertTrue(manager.beginMoveObject(movedObj, dirObj)); + + File renamed = new File(newDir, movedDir.getName()); + Assert.assertTrue(movedDir.renameTo(renamed)); + manager.flushEvents(); + Assert.assertTrue(manager.endMoveObject( + manager.getStorageRoot(mainMtpStorage.getStorageId()), + dirObj, movedDir.getName(), true)); + + Assert.assertEquals(objectsAdded.size(), 0); + Assert.assertEquals(objectsRemoved.size(), 0); + Assert.assertEquals(movedObj.getPath().toString(), renamed.getPath()); + + Assert.assertTrue(manager.checkConsistency()); + + // Expect events since the dir was visited + createNewFile(renamed); + manager.flushEvents(); + Assert.assertEquals(objectsAdded.size(), 1); + Assert.assertTrue(manager.checkConsistency()); + } + + @Test + @SmallTest + public void testMoveObjectDelayed() { + logMethodName(); + File newFile = createNewFile(mainStorageDir); + File dir = createNewDir(mainStorageDir); + MtpStorageManager.MtpObject dirObj = manager.getObjects(0xFFFFFFFF, 0, + mainMtpStorage.getStorageId()) + .filter(MtpStorageManager.MtpObject::isDir).findFirst().get(); + MtpStorageManager.MtpObject fileObj = manager.getObjects(0xFFFFFFFF, 0, + mainMtpStorage.getStorageId()) + .filter(o -> !o.isDir()).findFirst().get(); + Assert.assertTrue(manager.beginMoveObject(fileObj, dirObj)); + + Assert.assertTrue(manager.endMoveObject( + manager.getStorageRoot(mainMtpStorage.getStorageId()), + dirObj, newFile.getName(), true)); + + File moved = new File(dir, newFile.getName()); + Assert.assertTrue(newFile.renameTo(moved)); + manager.flushEvents(); + + Assert.assertEquals(objectsAdded.size(), 0); + Assert.assertEquals(objectsRemoved.size(), 0); + Assert.assertEquals(fileObj.getPath().toString(), moved.getPath()); + + Assert.assertTrue(manager.checkConsistency()); + } + + @Test + @SmallTest + public void testMoveObjectDirVisitedDelayed() { + logMethodName(); + File newDir = createNewDir(mainStorageDir); + File movedDir = createNewDir(mainStorageDir); + MtpStorageManager.MtpObject dirObj = manager.getObjects(0xFFFFFFFF, 0, + mainMtpStorage.getStorageId()) + .filter(o -> o.getName().equals(newDir.getName())).findFirst().get(); + MtpStorageManager.MtpObject movedObj = manager.getObjects(0xFFFFFFFF, 0, + mainMtpStorage.getStorageId()) + .filter(o -> o.getName().equals(movedDir.getName())).findFirst().get(); + manager.getObjects(movedObj.getId(), 0, mainMtpStorage.getStorageId()); + Assert.assertTrue(manager.beginMoveObject(movedObj, dirObj)); + + Assert.assertTrue(manager.endMoveObject( + manager.getStorageRoot(mainMtpStorage.getStorageId()), + dirObj, movedDir.getName(), true)); + + File renamed = new File(newDir, movedDir.getName()); + Assert.assertTrue(movedDir.renameTo(renamed)); + manager.flushEvents(); + + Assert.assertEquals(objectsAdded.size(), 0); + Assert.assertEquals(objectsRemoved.size(), 0); + Assert.assertEquals(movedObj.getPath().toString(), renamed.getPath()); + + Assert.assertTrue(manager.checkConsistency()); + + // Expect events since the dir was visited + createNewFile(renamed); + manager.flushEvents(); + Assert.assertEquals(objectsAdded.size(), 1); + Assert.assertTrue(manager.checkConsistency()); + } + + @Test + @SmallTest + public void testMoveObjectFailed() { + logMethodName(); + File newFile = createNewFile(mainStorageDir); + File dir = createNewDir(mainStorageDir); + MtpStorageManager.MtpObject dirObj = manager.getObjects(0xFFFFFFFF, 0, + mainMtpStorage.getStorageId()) + .filter(MtpStorageManager.MtpObject::isDir).findFirst().get(); + MtpStorageManager.MtpObject fileObj = manager.getObjects(0xFFFFFFFF, 0, + mainMtpStorage.getStorageId()) + .filter(o -> !o.isDir()).findFirst().get(); + Assert.assertTrue(manager.beginMoveObject(fileObj, dirObj)); + + Assert.assertTrue(manager.endMoveObject( + manager.getStorageRoot(mainMtpStorage.getStorageId()), + dirObj, newFile.getName(), false)); + + Assert.assertEquals(objectsAdded.size(), 0); + Assert.assertEquals(objectsRemoved.size(), 0); + + Assert.assertTrue(manager.checkConsistency()); + } + + @Test + @SmallTest + public void testMoveObjectFailedOldRemoved() { + logMethodName(); + File newFile = createNewFile(mainStorageDir); + File dir = createNewDir(mainStorageDir); + MtpStorageManager.MtpObject dirObj = manager.getObjects(0xFFFFFFFF, 0, + mainMtpStorage.getStorageId()) + .filter(MtpStorageManager.MtpObject::isDir).findFirst().get(); + MtpStorageManager.MtpObject fileObj = manager.getObjects(0xFFFFFFFF, 0, + mainMtpStorage.getStorageId()) + .filter(o -> !o.isDir()).findFirst().get(); + Assert.assertTrue(manager.beginMoveObject(fileObj, dirObj)); + + Assert.assertTrue(newFile.delete()); + manager.flushEvents(); + Assert.assertTrue(manager.endMoveObject( + manager.getStorageRoot(mainMtpStorage.getStorageId()), + dirObj, newFile.getName(), false)); + + Assert.assertEquals(objectsAdded.size(), 0); + Assert.assertEquals(objectsRemoved.size(), 1); + + Assert.assertTrue(manager.checkConsistency()); + } + + @Test + @SmallTest + public void testMoveObjectFailedNewAdded() { + logMethodName(); + File newFile = createNewFile(mainStorageDir); + File dir = createNewDir(mainStorageDir); + MtpStorageManager.MtpObject dirObj = manager.getObjects(0xFFFFFFFF, 0, + mainMtpStorage.getStorageId()) + .filter(MtpStorageManager.MtpObject::isDir).findFirst().get(); + MtpStorageManager.MtpObject fileObj = manager.getObjects(0xFFFFFFFF, 0, + mainMtpStorage.getStorageId()) + .filter(o -> !o.isDir()).findFirst().get(); + Assert.assertTrue(manager.beginMoveObject(fileObj, dirObj)); + + createNewFile(dir, newFile.getName()); + manager.flushEvents(); + Assert.assertTrue(manager.endMoveObject( + manager.getStorageRoot(mainMtpStorage.getStorageId()), + dirObj, newFile.getName(), false)); + + Assert.assertEquals(objectsAdded.size(), 1); + Assert.assertEquals(objectsRemoved.size(), 0); + + Assert.assertTrue(manager.checkConsistency()); + } + + @Test + @SmallTest + public void testMoveObjectXStorageSuccess() { + logMethodName(); + File newFile = createNewFile(mainStorageDir); + MtpStorageManager.MtpObject fileObj = manager.getObjects(0xFFFFFFFF, 0, + mainMtpStorage.getStorageId()).findFirst().get(); + Assert.assertTrue(manager.beginMoveObject(fileObj, + manager.getStorageRoot(secondaryMtpStorage.getStorageId()))); + + Assert.assertTrue(newFile.delete()); + File moved = createNewFile(secondaryStorageDir, newFile.getName()); + manager.flushEvents(); + Assert.assertTrue(manager.endMoveObject( + manager.getStorageRoot(mainMtpStorage.getStorageId()), + manager.getStorageRoot(secondaryMtpStorage.getStorageId()), + newFile.getName(), true)); + + Assert.assertEquals(objectsAdded.size(), 0); + Assert.assertEquals(objectsRemoved.size(), 0); + Assert.assertEquals(manager.getObject(fileObj.getId()).getPath().toString(), + moved.getPath()); + + Assert.assertTrue(manager.checkConsistency()); + } + + @Test + @SmallTest + public void testMoveObjectXStorageDirSuccess() { + logMethodName(); + File movedDir = createNewDir(mainStorageDir); + MtpStorageManager.MtpObject movedObj = manager.getObjects(0xFFFFFFFF, 0, + mainMtpStorage.getStorageId()).findFirst().get(); + Assert.assertTrue(manager.beginMoveObject(movedObj, + manager.getStorageRoot(secondaryMtpStorage.getStorageId()))); + + Assert.assertTrue(movedDir.delete()); + File moved = createNewDir(secondaryStorageDir, movedDir.getName()); + manager.flushEvents(); + Assert.assertTrue(manager.endMoveObject( + manager.getStorageRoot(mainMtpStorage.getStorageId()), + manager.getStorageRoot(secondaryMtpStorage.getStorageId()), + movedDir.getName(), true)); + + Assert.assertEquals(objectsAdded.size(), 0); + Assert.assertEquals(objectsRemoved.size(), 0); + Assert.assertEquals(manager.getObject(movedObj.getId()).getPath().toString(), + moved.getPath()); + + Assert.assertTrue(manager.checkConsistency()); + + // Don't expect events + createNewFile(moved); + manager.flushEvents(); + Assert.assertEquals(objectsAdded.size(), 0); + Assert.assertTrue(manager.checkConsistency()); + } + + @Test + @SmallTest + public void testMoveObjectXStorageDirVisitedSuccess() { + logMethodName(); + File movedDir = createNewDir(mainStorageDir); + MtpStorageManager.MtpObject movedObj = manager.getObjects(0xFFFFFFFF, 0, + mainMtpStorage.getStorageId()).findFirst().get(); + manager.getObjects(movedObj.getId(), 0, mainMtpStorage.getStorageId()); + Assert.assertTrue(manager.beginMoveObject(movedObj, + manager.getStorageRoot(secondaryMtpStorage.getStorageId()))); + + Assert.assertTrue(movedDir.delete()); + File moved = createNewDir(secondaryStorageDir, movedDir.getName()); + manager.flushEvents(); + Assert.assertTrue(manager.endMoveObject( + manager.getStorageRoot(mainMtpStorage.getStorageId()), + manager.getStorageRoot(secondaryMtpStorage.getStorageId()), + movedDir.getName(), true)); + + Assert.assertEquals(objectsAdded.size(), 0); + Assert.assertEquals(objectsRemoved.size(), 0); + Assert.assertEquals(manager.getObject(movedObj.getId()).getPath().toString(), + moved.getPath()); + + Assert.assertTrue(manager.checkConsistency()); + + // Expect events since the dir was visited + createNewFile(moved); + manager.flushEvents(); + Assert.assertEquals(objectsAdded.size(), 1); + Assert.assertTrue(manager.checkConsistency()); + } + + @Test + @SmallTest + public void testMoveObjectXStorageDelayed() { + logMethodName(); + File movedFile = createNewFile(mainStorageDir); + MtpStorageManager.MtpObject movedObj = manager.getObjects(0xFFFFFFFF, 0, + mainMtpStorage.getStorageId()).findFirst().get(); + Assert.assertTrue(manager.beginMoveObject(movedObj, + manager.getStorageRoot(secondaryMtpStorage.getStorageId()))); + + Assert.assertTrue(manager.endMoveObject( + manager.getStorageRoot(mainMtpStorage.getStorageId()), + manager.getStorageRoot(secondaryMtpStorage.getStorageId()), + movedFile.getName(), true)); + + Assert.assertTrue(movedFile.delete()); + File moved = createNewFile(secondaryStorageDir, movedFile.getName()); + manager.flushEvents(); + + Assert.assertEquals(objectsAdded.size(), 0); + Assert.assertEquals(objectsRemoved.size(), 0); + Assert.assertEquals(manager.getObject(movedObj.getId()).getPath().toString(), + moved.getPath()); + + Assert.assertTrue(manager.checkConsistency()); + } + + @Test + @SmallTest + public void testMoveObjectXStorageDirVisitedDelayed() { + logMethodName(); + File movedDir = createNewDir(mainStorageDir); + MtpStorageManager.MtpObject movedObj = manager.getObjects(0xFFFFFFFF, 0, + mainMtpStorage.getStorageId()).findFirst().get(); + manager.getObjects(movedObj.getId(), 0, mainMtpStorage.getStorageId()); + Assert.assertTrue(manager.beginMoveObject(movedObj, + manager.getStorageRoot(secondaryMtpStorage.getStorageId()))); + + Assert.assertTrue(manager.endMoveObject( + manager.getStorageRoot(mainMtpStorage.getStorageId()), + manager.getStorageRoot(secondaryMtpStorage.getStorageId()), + movedDir.getName(), true)); + + Assert.assertTrue(movedDir.delete()); + File moved = createNewDir(secondaryStorageDir, movedDir.getName()); + manager.flushEvents(); + + Assert.assertEquals(objectsAdded.size(), 0); + Assert.assertEquals(objectsRemoved.size(), 0); + Assert.assertEquals(manager.getObject(movedObj.getId()).getPath().toString(), + moved.getPath()); + + Assert.assertTrue(manager.checkConsistency()); + + // Expect events since the dir was visited + createNewFile(moved); + manager.flushEvents(); + Assert.assertEquals(objectsAdded.size(), 1); + Assert.assertTrue(manager.checkConsistency()); + } + + @Test + @SmallTest + public void testMoveObjectXStorageFailed() { + logMethodName(); + File newFile = createNewFile(mainStorageDir); + MtpStorageManager.MtpObject fileObj = manager.getObjects(0xFFFFFFFF, 0, + mainMtpStorage.getStorageId()).findFirst().get(); + Assert.assertTrue(manager.beginMoveObject(fileObj, + manager.getStorageRoot(secondaryMtpStorage.getStorageId()))); + + Assert.assertTrue(manager.endMoveObject( + manager.getStorageRoot(mainMtpStorage.getStorageId()), + manager.getStorageRoot(secondaryMtpStorage.getStorageId()), + newFile.getName(), false)); + + Assert.assertEquals(objectsAdded.size(), 0); + Assert.assertEquals(objectsRemoved.size(), 0); + + Assert.assertTrue(manager.checkConsistency()); + } + + @Test + @SmallTest + public void testMoveObjectXStorageFailedOldRemoved() { + logMethodName(); + File newFile = createNewFile(mainStorageDir); + MtpStorageManager.MtpObject fileObj = manager.getObjects(0xFFFFFFFF, 0, + mainMtpStorage.getStorageId()).findFirst().get(); + Assert.assertTrue(manager.beginMoveObject(fileObj, + manager.getStorageRoot(secondaryMtpStorage.getStorageId()))); + + Assert.assertTrue(newFile.delete()); + manager.flushEvents(); + Assert.assertTrue(manager.endMoveObject( + manager.getStorageRoot(mainMtpStorage.getStorageId()), + manager.getStorageRoot(secondaryMtpStorage.getStorageId()), + newFile.getName(), false)); + + Assert.assertEquals(objectsAdded.size(), 0); + Assert.assertEquals(objectsRemoved.size(), 1); + + Assert.assertTrue(manager.checkConsistency()); + } + + @Test + @SmallTest + public void testMoveObjectXStorageFailedNewAdded() { + logMethodName(); + File newFile = createNewFile(mainStorageDir); + MtpStorageManager.MtpObject fileObj = manager.getObjects(0xFFFFFFFF, 0, + mainMtpStorage.getStorageId()).findFirst().get(); + Assert.assertTrue(manager.beginMoveObject(fileObj, + manager.getStorageRoot(secondaryMtpStorage.getStorageId()))); + + createNewFile(secondaryStorageDir, newFile.getName()); + manager.flushEvents(); + Assert.assertTrue(manager.endMoveObject( + manager.getStorageRoot(mainMtpStorage.getStorageId()), + manager.getStorageRoot(secondaryMtpStorage.getStorageId()), + newFile.getName(), false)); + + Assert.assertEquals(objectsAdded.size(), 1); + Assert.assertEquals(objectsRemoved.size(), 0); + + Assert.assertTrue(manager.checkConsistency()); + } +}
\ No newline at end of file diff --git a/packages/InputDevices/res/values-bn/strings.xml b/packages/InputDevices/res/values-bn/strings.xml index 5f8877afa6c3..a61e6cebc01b 100644 --- a/packages/InputDevices/res/values-bn/strings.xml +++ b/packages/InputDevices/res/values-bn/strings.xml @@ -42,6 +42,5 @@ <string name="keyboard_layout_spanish_latin" msgid="5690539836069535697">"স্প্যানিশ (ল্যাটিন)"</string> <string name="keyboard_layout_latvian" msgid="4405417142306250595">"লাটভিও"</string> <string name="keyboard_layout_persian" msgid="3920643161015888527">"ফার্সী"</string> - <!-- no translation found for keyboard_layout_azerbaijani (7315895417176467567) --> - <skip /> + <string name="keyboard_layout_azerbaijani" msgid="7315895417176467567">"আজারবাইজানি"</string> </resources> diff --git a/packages/InputDevices/res/values-gu/strings.xml b/packages/InputDevices/res/values-gu/strings.xml index d11df01988a7..915f1b674f3c 100644 --- a/packages/InputDevices/res/values-gu/strings.xml +++ b/packages/InputDevices/res/values-gu/strings.xml @@ -42,6 +42,5 @@ <string name="keyboard_layout_spanish_latin" msgid="5690539836069535697">"સ્પેનિશ (લેટિન)"</string> <string name="keyboard_layout_latvian" msgid="4405417142306250595">"લાતવિયન"</string> <string name="keyboard_layout_persian" msgid="3920643161015888527">"પર્શિયન"</string> - <!-- no translation found for keyboard_layout_azerbaijani (7315895417176467567) --> - <skip /> + <string name="keyboard_layout_azerbaijani" msgid="7315895417176467567">"અઝરબૈજાની"</string> </resources> diff --git a/packages/InputDevices/res/values-kn/strings.xml b/packages/InputDevices/res/values-kn/strings.xml index 2e6f89258124..8f2b51af5505 100644 --- a/packages/InputDevices/res/values-kn/strings.xml +++ b/packages/InputDevices/res/values-kn/strings.xml @@ -42,6 +42,5 @@ <string name="keyboard_layout_spanish_latin" msgid="5690539836069535697">"ಸ್ಪ್ಯಾನಿಶ್ (ಲ್ಯಾಟಿನ್)"</string> <string name="keyboard_layout_latvian" msgid="4405417142306250595">"ಲ್ಯಾಟ್ವಿಯನ್"</string> <string name="keyboard_layout_persian" msgid="3920643161015888527">"ಪರ್ಶಿಯನ್"</string> - <!-- no translation found for keyboard_layout_azerbaijani (7315895417176467567) --> - <skip /> + <string name="keyboard_layout_azerbaijani" msgid="7315895417176467567">"ಅಜೆರ್ಬೈಜಾನಿ"</string> </resources> diff --git a/packages/InputDevices/res/values-ml/strings.xml b/packages/InputDevices/res/values-ml/strings.xml index dfd9754f0991..d346d9f919f7 100644 --- a/packages/InputDevices/res/values-ml/strings.xml +++ b/packages/InputDevices/res/values-ml/strings.xml @@ -42,6 +42,5 @@ <string name="keyboard_layout_spanish_latin" msgid="5690539836069535697">"സ്പാനിഷ് (ലാറ്റിൻ)"</string> <string name="keyboard_layout_latvian" msgid="4405417142306250595">"ലാറ്റ്വിയന്"</string> <string name="keyboard_layout_persian" msgid="3920643161015888527">"പേര്ഷ്യന്"</string> - <!-- no translation found for keyboard_layout_azerbaijani (7315895417176467567) --> - <skip /> + <string name="keyboard_layout_azerbaijani" msgid="7315895417176467567">"അസര്ബൈജാനി"</string> </resources> diff --git a/packages/InputDevices/res/values-pa/strings.xml b/packages/InputDevices/res/values-pa/strings.xml index 1cf6a2ee065f..f707730c69ef 100644 --- a/packages/InputDevices/res/values-pa/strings.xml +++ b/packages/InputDevices/res/values-pa/strings.xml @@ -42,6 +42,5 @@ <string name="keyboard_layout_spanish_latin" msgid="5690539836069535697">"ਸਪੇਨੀ (ਲਾਤੀਨੀ)"</string> <string name="keyboard_layout_latvian" msgid="4405417142306250595">"ਲਾਤਵੀਅਨ"</string> <string name="keyboard_layout_persian" msgid="3920643161015888527">"ਫ਼ਾਰਸੀ"</string> - <!-- no translation found for keyboard_layout_azerbaijani (7315895417176467567) --> - <skip /> + <string name="keyboard_layout_azerbaijani" msgid="7315895417176467567">"ਅਜ਼ੇਰਬੈਜਾਨੀ"</string> </resources> diff --git a/packages/InputDevices/res/values-ta/strings.xml b/packages/InputDevices/res/values-ta/strings.xml index ebee5c1425f4..a4d07ac0d898 100644 --- a/packages/InputDevices/res/values-ta/strings.xml +++ b/packages/InputDevices/res/values-ta/strings.xml @@ -42,6 +42,5 @@ <string name="keyboard_layout_spanish_latin" msgid="5690539836069535697">"ஸ்பானிஷ் (லத்தீன்)"</string> <string name="keyboard_layout_latvian" msgid="4405417142306250595">"லத்வியன்"</string> <string name="keyboard_layout_persian" msgid="3920643161015888527">"பெர்சியன்"</string> - <!-- no translation found for keyboard_layout_azerbaijani (7315895417176467567) --> - <skip /> + <string name="keyboard_layout_azerbaijani" msgid="7315895417176467567">"அஜர்பைஜானி"</string> </resources> diff --git a/packages/InputDevices/res/values-te/strings.xml b/packages/InputDevices/res/values-te/strings.xml index 141bb9528198..f7cce96abf71 100644 --- a/packages/InputDevices/res/values-te/strings.xml +++ b/packages/InputDevices/res/values-te/strings.xml @@ -42,6 +42,5 @@ <string name="keyboard_layout_spanish_latin" msgid="5690539836069535697">"స్పానిష్ (లాటిన్)"</string> <string name="keyboard_layout_latvian" msgid="4405417142306250595">"లాత్వియన్"</string> <string name="keyboard_layout_persian" msgid="3920643161015888527">"పర్షియన్"</string> - <!-- no translation found for keyboard_layout_azerbaijani (7315895417176467567) --> - <skip /> + <string name="keyboard_layout_azerbaijani" msgid="7315895417176467567">"అజర్బైజాన్"</string> </resources> diff --git a/packages/InputDevices/res/values-ur/strings.xml b/packages/InputDevices/res/values-ur/strings.xml index 71ce1ccb02d3..ab95bd56c009 100644 --- a/packages/InputDevices/res/values-ur/strings.xml +++ b/packages/InputDevices/res/values-ur/strings.xml @@ -42,6 +42,5 @@ <string name="keyboard_layout_spanish_latin" msgid="5690539836069535697">"ہسپانوی (لاطینی)"</string> <string name="keyboard_layout_latvian" msgid="4405417142306250595">"لاتویائی"</string> <string name="keyboard_layout_persian" msgid="3920643161015888527">"فارسی"</string> - <!-- no translation found for keyboard_layout_azerbaijani (7315895417176467567) --> - <skip /> + <string name="keyboard_layout_azerbaijani" msgid="7315895417176467567">"آزربائیجانی"</string> </resources> diff --git a/packages/InputDevices/res/values-uz/strings.xml b/packages/InputDevices/res/values-uz/strings.xml index e82046966c62..d6f7b2bcdc3a 100644 --- a/packages/InputDevices/res/values-uz/strings.xml +++ b/packages/InputDevices/res/values-uz/strings.xml @@ -42,5 +42,5 @@ <string name="keyboard_layout_spanish_latin" msgid="5690539836069535697">"Ispan (lotin)"</string> <string name="keyboard_layout_latvian" msgid="4405417142306250595">"Latish"</string> <string name="keyboard_layout_persian" msgid="3920643161015888527">"Fors"</string> - <string name="keyboard_layout_azerbaijani" msgid="7315895417176467567">"ozarbayjon"</string> + <string name="keyboard_layout_azerbaijani" msgid="7315895417176467567">"Ozarbayjon"</string> </resources> diff --git a/packages/SettingsLib/res/values-eu/strings.xml b/packages/SettingsLib/res/values-eu/strings.xml index 57d1657bd5e4..22e9d35d87ba 100644 --- a/packages/SettingsLib/res/values-eu/strings.xml +++ b/packages/SettingsLib/res/values-eu/strings.xml @@ -353,7 +353,7 @@ <string name="daltonizer_mode_deuteranomaly" msgid="5475532989673586329">"Daltonismoa (gorri-berdeak)"</string> <string name="daltonizer_mode_protanomaly" msgid="8424148009038666065">"Protanopia (gorri-berdeak)"</string> <string name="daltonizer_mode_tritanomaly" msgid="481725854987912389">"Tritanopia (urdin-horia)"</string> - <string name="accessibility_display_daltonizer_preference_title" msgid="5800761362678707872">"Kolore-zuzenketa"</string> + <string name="accessibility_display_daltonizer_preference_title" msgid="5800761362678707872">"Kolorearen zuzenketa"</string> <string name="accessibility_display_daltonizer_preference_subtitle" msgid="3484969015295282911">"Eginbidea esperimentala da eta eragina izan dezake funtzionamenduan."</string> <string name="daltonizer_type_overridden" msgid="3116947244410245916">"<xliff:g id="TITLE">%1$s</xliff:g> hobespena gainjarri zaio"</string> <string name="power_remaining_duration_only" msgid="845431008899029842">"<xliff:g id="TIME">^1</xliff:g> inguru gelditzen dira"</string> diff --git a/packages/SettingsLib/res/values-gl/strings.xml b/packages/SettingsLib/res/values-gl/strings.xml index f5cf5f610ec5..47d840881a3a 100644 --- a/packages/SettingsLib/res/values-gl/strings.xml +++ b/packages/SettingsLib/res/values-gl/strings.xml @@ -401,7 +401,7 @@ <string name="active_input_method_subtypes" msgid="3596398805424733238">"Métodos de entrada activos"</string> <string name="use_system_language_to_select_input_method_subtypes" msgid="5747329075020379587">"Usar idiomas do sistema"</string> <string name="failed_to_open_app_settings_toast" msgid="1251067459298072462">"Non se puido abrir a configuración de <xliff:g id="SPELL_APPLICATION_NAME">%1$s</xliff:g>"</string> - <string name="ime_security_warning" msgid="4135828934735934248">"É posible que este método de entrada poida recompilar todo o texto que escribas, incluídos os datos persoais como os contrasinais e os números de tarxetas de crédito. Provén da aplicación <xliff:g id="IME_APPLICATION_NAME">%1$s</xliff:g>. Queres usar este método de entrada?"</string> + <string name="ime_security_warning" msgid="4135828934735934248">"Este método de introdución de texto pode recompilar todo o que escribas, incluídos os datos persoais como os contrasinais e os números de tarxetas de crédito. Provén da aplicación <xliff:g id="IME_APPLICATION_NAME">%1$s</xliff:g>. Queres usar este método de introdución de texto?"</string> <string name="direct_boot_unaware_dialog_message" msgid="7870273558547549125">"Nota: Tras un reinicio, non se pode iniciar esta aplicación ata que desbloquees o teléfono"</string> <string name="ims_reg_title" msgid="7609782759207241443">"Estado de rexistro de IMS"</string> <string name="ims_reg_status_registered" msgid="933003316932739188">"Rexistrado"</string> diff --git a/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java b/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java index fa2499f6c144..5c73d5485e2a 100644 --- a/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java +++ b/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java @@ -21,6 +21,9 @@ import android.app.AppGlobals; import android.app.Application; import android.app.usage.StorageStats; import android.app.usage.StorageStatsManager; +import android.arch.lifecycle.Lifecycle; +import android.arch.lifecycle.LifecycleObserver; +import android.arch.lifecycle.OnLifecycleEvent; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; @@ -52,11 +55,6 @@ import android.util.SparseArray; import com.android.internal.R; import com.android.internal.util.ArrayUtils; -import com.android.settingslib.core.lifecycle.Lifecycle; -import com.android.settingslib.core.lifecycle.LifecycleObserver; -import com.android.settingslib.core.lifecycle.events.OnDestroy; -import com.android.settingslib.core.lifecycle.events.OnPause; -import com.android.settingslib.core.lifecycle.events.OnResume; import java.io.File; import java.io.IOException; @@ -595,7 +593,7 @@ public class ApplicationsState { .replaceAll("").toLowerCase(); } - public class Session implements LifecycleObserver, OnPause, OnResume, OnDestroy { + public class Session implements LifecycleObserver { final Callbacks mCallbacks; boolean mResumed; @@ -621,6 +619,7 @@ public class ApplicationsState { } } + @OnLifecycleEvent(Lifecycle.Event.ON_RESUME) public void onResume() { if (DEBUG_LOCKING) Log.v(TAG, "resume about to acquire lock..."); synchronized (mEntriesMap) { @@ -633,6 +632,7 @@ public class ApplicationsState { if (DEBUG_LOCKING) Log.v(TAG, "...resume releasing lock"); } + @OnLifecycleEvent(Lifecycle.Event.ON_PAUSE) public void onPause() { if (DEBUG_LOCKING) Log.v(TAG, "pause about to acquire lock..."); synchronized (mEntriesMap) { @@ -752,6 +752,7 @@ public class ApplicationsState { Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); } + @OnLifecycleEvent(Lifecycle.Event.ON_DESTROY) public void onDestroy() { if (!mHasLifecycle) { // TODO: Legacy, remove this later once all usages are switched to Lifecycle diff --git a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/PowerWhitelistBackend.java b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/PowerWhitelistBackend.java new file mode 100644 index 000000000000..2b6d09f9b72e --- /dev/null +++ b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/PowerWhitelistBackend.java @@ -0,0 +1,108 @@ +/* + * 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.settingslib.fuelgauge; + +import android.os.IDeviceIdleController; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.support.annotation.VisibleForTesting; +import android.util.ArraySet; +import android.util.Log; + +/** + * Handles getting/changing the whitelist for the exceptions to battery saving features. + */ +public class PowerWhitelistBackend { + + private static final String TAG = "PowerWhitelistBackend"; + + private static final String DEVICE_IDLE_SERVICE = "deviceidle"; + + private static PowerWhitelistBackend sInstance; + + private final IDeviceIdleController mDeviceIdleService; + private final ArraySet<String> mWhitelistedApps = new ArraySet<>(); + private final ArraySet<String> mSysWhitelistedApps = new ArraySet<>(); + + public PowerWhitelistBackend() { + mDeviceIdleService = IDeviceIdleController.Stub.asInterface( + ServiceManager.getService(DEVICE_IDLE_SERVICE)); + refreshList(); + } + + @VisibleForTesting + PowerWhitelistBackend(IDeviceIdleController deviceIdleService) { + mDeviceIdleService = deviceIdleService; + refreshList(); + } + + public int getWhitelistSize() { + return mWhitelistedApps.size(); + } + + public boolean isSysWhitelisted(String pkg) { + return mSysWhitelistedApps.contains(pkg); + } + + public boolean isWhitelisted(String pkg) { + return mWhitelistedApps.contains(pkg); + } + + public void addApp(String pkg) { + try { + mDeviceIdleService.addPowerSaveWhitelistApp(pkg); + mWhitelistedApps.add(pkg); + } catch (RemoteException e) { + Log.w(TAG, "Unable to reach IDeviceIdleController", e); + } + } + + public void removeApp(String pkg) { + try { + mDeviceIdleService.removePowerSaveWhitelistApp(pkg); + mWhitelistedApps.remove(pkg); + } catch (RemoteException e) { + Log.w(TAG, "Unable to reach IDeviceIdleController", e); + } + } + + @VisibleForTesting + public void refreshList() { + mSysWhitelistedApps.clear(); + mWhitelistedApps.clear(); + try { + String[] whitelistedApps = mDeviceIdleService.getFullPowerWhitelist(); + for (String app : whitelistedApps) { + mWhitelistedApps.add(app); + } + String[] sysWhitelistedApps = mDeviceIdleService.getSystemPowerWhitelist(); + for (String app : sysWhitelistedApps) { + mSysWhitelistedApps.add(app); + } + } catch (RemoteException e) { + Log.w(TAG, "Unable to reach IDeviceIdleController", e); + } + } + + public static PowerWhitelistBackend getInstance() { + if (sInstance == null) { + sInstance = new PowerWhitelistBackend(); + } + return sInstance; + } + +} diff --git a/packages/SettingsLib/tests/robotests/Android.mk b/packages/SettingsLib/tests/robotests/Android.mk index 273802754bbb..02a49738e6a1 100644 --- a/packages/SettingsLib/tests/robotests/Android.mk +++ b/packages/SettingsLib/tests/robotests/Android.mk @@ -49,7 +49,7 @@ LOCAL_STATIC_JAVA_LIBRARIES := \ LOCAL_JAVA_LIBRARIES := \ junit \ - platform-robolectric-3.4.2-prebuilt + platform-robolectric-3.5.1-prebuilt LOCAL_INSTRUMENTATION_FOR := SettingsLibShell LOCAL_MODULE := SettingsLibRoboTests @@ -74,4 +74,4 @@ LOCAL_TEST_PACKAGE := SettingsLibShell LOCAL_ROBOTEST_TIMEOUT := 36000 -include prebuilts/misc/common/robolectric/3.4.2/run_robotests.mk +include prebuilts/misc/common/robolectric/3.5.1/run_robotests.mk diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/SettingsLibRobolectricTestRunner.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/SettingsLibRobolectricTestRunner.java index 698e44247e73..df850bee13df 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/SettingsLibRobolectricTestRunner.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/SettingsLibRobolectricTestRunner.java @@ -38,32 +38,25 @@ public class SettingsLibRobolectricTestRunner extends RobolectricTestRunner { final String resDir = appRoot + "/tests/robotests/res"; final String assetsDir = appRoot + config.assetDir(); - final AndroidManifest manifest = new AndroidManifest(Fs.fileFromPath(manifestPath), - Fs.fileFromPath(resDir), Fs.fileFromPath(assetsDir)) { + return new AndroidManifest(Fs.fileFromPath(manifestPath), Fs.fileFromPath(resDir), + Fs.fileFromPath(assetsDir), "com.android.settingslib") { @Override public List<ResourcePath> getIncludedResourcePaths() { List<ResourcePath> paths = super.getIncludedResourcePaths(); - SettingsLibRobolectricTestRunner.getIncludedResourcePaths(getPackageName(), paths); + paths.add(new ResourcePath( + null, + Fs.fileFromPath("./frameworks/base/packages/SettingsLib/res"), + null)); + paths.add(new ResourcePath( + null, + Fs.fileFromPath("./frameworks/base/core/res/res"), + null)); + paths.add(new ResourcePath( + null, + Fs.fileFromPath("./frameworks/support/v7/appcompat/res"), + null)); return paths; } }; - manifest.setPackageName("com.android.settingslib"); - return manifest; } - - static void getIncludedResourcePaths(String packageName, List<ResourcePath> paths) { - paths.add(new ResourcePath( - null, - Fs.fileFromPath("./frameworks/base/packages/SettingsLib/res"), - null)); - paths.add(new ResourcePath( - null, - Fs.fileFromPath("./frameworks/base/core/res/res"), - null)); - paths.add(new ResourcePath( - null, - Fs.fileFromPath("./frameworks/support/v7/appcompat/res"), - null)); - } - } diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/fuelgauge/PowerWhitelistBackendTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/fuelgauge/PowerWhitelistBackendTest.java new file mode 100644 index 000000000000..fc0019d09c41 --- /dev/null +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/fuelgauge/PowerWhitelistBackendTest.java @@ -0,0 +1,97 @@ +/* + * 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.settingslib.fuelgauge; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.atLeastOnce; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.verify; + +import android.os.IDeviceIdleController; + +import com.android.settingslib.SettingsLibRobolectricTestRunner; +import com.android.settingslib.TestConfig; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.annotation.Config; + +@RunWith(SettingsLibRobolectricTestRunner.class) +@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION) +public class PowerWhitelistBackendTest { + + private static final String PACKAGE_ONE = "com.example.packageone"; + private static final String PACKAGE_TWO = "com.example.packagetwo"; + + private PowerWhitelistBackend mPowerWhitelistBackend; + @Mock + private IDeviceIdleController mDeviceIdleService; + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + doReturn(new String[] {}).when(mDeviceIdleService).getFullPowerWhitelist(); + doReturn(new String[] {}).when(mDeviceIdleService).getSystemPowerWhitelist(); + doNothing().when(mDeviceIdleService).addPowerSaveWhitelistApp(anyString()); + doNothing().when(mDeviceIdleService).removePowerSaveWhitelistApp(anyString()); + mPowerWhitelistBackend = new PowerWhitelistBackend(mDeviceIdleService); + } + + @Test + public void testIsWhitelisted() throws Exception { + doReturn(new String[] {PACKAGE_ONE}).when(mDeviceIdleService).getFullPowerWhitelist(); + mPowerWhitelistBackend.refreshList(); + + assertThat(mPowerWhitelistBackend.isWhitelisted(PACKAGE_ONE)).isTrue(); + assertThat(mPowerWhitelistBackend.isWhitelisted(PACKAGE_TWO)).isFalse(); + + mPowerWhitelistBackend.addApp(PACKAGE_TWO); + + verify(mDeviceIdleService, atLeastOnce()).addPowerSaveWhitelistApp(PACKAGE_TWO); + assertThat(mPowerWhitelistBackend.isWhitelisted(PACKAGE_ONE)).isTrue(); + assertThat(mPowerWhitelistBackend.isWhitelisted(PACKAGE_TWO)).isTrue(); + + mPowerWhitelistBackend.removeApp(PACKAGE_TWO); + + verify(mDeviceIdleService, atLeastOnce()).removePowerSaveWhitelistApp(PACKAGE_TWO); + assertThat(mPowerWhitelistBackend.isWhitelisted(PACKAGE_ONE)).isTrue(); + assertThat(mPowerWhitelistBackend.isWhitelisted(PACKAGE_TWO)).isFalse(); + + mPowerWhitelistBackend.removeApp(PACKAGE_ONE); + + verify(mDeviceIdleService, atLeastOnce()).removePowerSaveWhitelistApp(PACKAGE_ONE); + assertThat(mPowerWhitelistBackend.isWhitelisted(PACKAGE_ONE)).isFalse(); + assertThat(mPowerWhitelistBackend.isWhitelisted(PACKAGE_TWO)).isFalse(); + } + + @Test + public void testIsSystemWhitelisted() throws Exception { + doReturn(new String[] {PACKAGE_ONE}).when(mDeviceIdleService).getSystemPowerWhitelist(); + mPowerWhitelistBackend.refreshList(); + + assertThat(mPowerWhitelistBackend.isSysWhitelisted(PACKAGE_ONE)).isTrue(); + assertThat(mPowerWhitelistBackend.isSysWhitelisted(PACKAGE_TWO)).isFalse(); + assertThat(mPowerWhitelistBackend.isWhitelisted(PACKAGE_ONE)).isFalse(); + + } +} diff --git a/packages/SystemUI/res-keyguard/values-bn/strings.xml b/packages/SystemUI/res-keyguard/values-bn/strings.xml index 5df39895f501..791f5e9626fb 100644 --- a/packages/SystemUI/res-keyguard/values-bn/strings.xml +++ b/packages/SystemUI/res-keyguard/values-bn/strings.xml @@ -135,6 +135,12 @@ <item quantity="other">ডিভাইসটি <xliff:g id="NUMBER_1">%d</xliff:g> ঘন্টা ধরে আনলক করা হয় নি। পাসওয়ার্ড নিশ্চিত করুন।</item> </plurals> <string name="fingerprint_not_recognized" msgid="348813995267914625">"স্বীকৃত নয়"</string> - <!-- no translation found for kg_password_default_pin_message (6203676909479972943) --> - <!-- no translation found for kg_password_default_puk_message (8744416410184198352) --> + <plurals name="kg_password_default_pin_message" formatted="false" msgid="6203676909479972943"> + <item quantity="one">সিমের পিন লিখুন। আপনি আর <xliff:g id="NUMBER_1">%d</xliff:g> বার চেষ্টা করতে পারবেন।</item> + <item quantity="other">সিমের পিন লিখুন। আপনি আর <xliff:g id="NUMBER_1">%d</xliff:g> বার চেষ্টা করতে পারবেন।</item> + </plurals> + <plurals name="kg_password_default_puk_message" formatted="false" msgid="8744416410184198352"> + <item quantity="one">সিম অক্ষম করা হয়েছে। চালিয়ে যেতে PUK কোড লিখুন। আপনি আর <xliff:g id="_NUMBER_1">%d</xliff:g> বার চেষ্টা করতে পারবেন, তারপরে এই সিমটি আর একেবারেই ব্যবহার করা যাবে না। বিশদে জানতে পরিষেবা প্রদানকারীর সাথে যোগাযোগ করুন।</item> + <item quantity="other">সিম অক্ষম করা হয়েছে। চালিয়ে যেতে PUK কোড লিখুন। আপনি আর <xliff:g id="_NUMBER_1">%d</xliff:g> বার চেষ্টা করতে পারবেন, তারপরে এই সিমটি আর একেবারেই ব্যবহার করা যাবে না। বিশদে জানতে পরিষেবা প্রদানকারীর সাথে যোগাযোগ করুন।</item> + </plurals> </resources> diff --git a/packages/SystemUI/res-keyguard/values-gu/strings.xml b/packages/SystemUI/res-keyguard/values-gu/strings.xml index a6ee9a65f424..c62bab861686 100644 --- a/packages/SystemUI/res-keyguard/values-gu/strings.xml +++ b/packages/SystemUI/res-keyguard/values-gu/strings.xml @@ -135,6 +135,12 @@ <item quantity="other">ઉપકરણને <xliff:g id="NUMBER_1">%d</xliff:g> કલાક માટે અનલૉક કરવામાં આવ્યું નથી. પાસવર્ડની પુષ્ટિ કરો.</item> </plurals> <string name="fingerprint_not_recognized" msgid="348813995267914625">"ઓળખાયેલ નથી"</string> - <!-- no translation found for kg_password_default_pin_message (6203676909479972943) --> - <!-- no translation found for kg_password_default_puk_message (8744416410184198352) --> + <plurals name="kg_password_default_pin_message" formatted="false" msgid="6203676909479972943"> + <item quantity="one">સિમ પિન દાખલ કરો, તમારી પાસે <xliff:g id="NUMBER_1">%d</xliff:g> પ્રયાસ બાકી છે.</item> + <item quantity="other">સિમ પિન દાખલ કરો, તમારી પાસે <xliff:g id="NUMBER_1">%d</xliff:g> પ્રયાસો બાકી છે.</item> + </plurals> + <plurals name="kg_password_default_puk_message" formatted="false" msgid="8744416410184198352"> + <item quantity="one">સિમ હવે બંધ કરેલ છે. ચાલુ રાખવા માટે PUK કોડ દાખલ કરો. સિમ કાયમીરૂપે બિનઉપયોગી બની જાય એ પહેલાં તમારી પાસે <xliff:g id="_NUMBER_1">%d</xliff:g> પ્રયાસ બાકી છે. વિગતો માટે કૅરિઅરનો સંપર્ક કરો.</item> + <item quantity="other">સિમ હવે બંધ કરેલ છે. ચાલુ રાખવા માટે PUK કોડ દાખલ કરો. સિમ કાયમીરૂપે બિનઉપયોગી બની જાય એ પહેલાં તમારી પાસે <xliff:g id="_NUMBER_1">%d</xliff:g> પ્રયાસો બાકી છે. વિગતો માટે કૅરિઅરનો સંપર્ક કરો.</item> + </plurals> </resources> diff --git a/packages/SystemUI/res-keyguard/values-kn/strings.xml b/packages/SystemUI/res-keyguard/values-kn/strings.xml index 2ee30e9998f1..2c291124aa93 100644 --- a/packages/SystemUI/res-keyguard/values-kn/strings.xml +++ b/packages/SystemUI/res-keyguard/values-kn/strings.xml @@ -135,6 +135,12 @@ <item quantity="other">ಸಾಧನವನ್ನು <xliff:g id="NUMBER_1">%d</xliff:g> ಗಂಟೆಗಳವರೆಗೆ ಅನ್ಲಾಕ್ ಮಾಡಿರಲಿಲ್ಲ. ಪಾಸ್ವರ್ಡ್ ಖಚಿತಪಡಿಸಿ.</item> </plurals> <string name="fingerprint_not_recognized" msgid="348813995267914625">"ಗುರುತಿಸಲಾಗಿಲ್ಲ"</string> - <!-- no translation found for kg_password_default_pin_message (6203676909479972943) --> - <!-- no translation found for kg_password_default_puk_message (8744416410184198352) --> + <plurals name="kg_password_default_pin_message" formatted="false" msgid="6203676909479972943"> + <item quantity="one">ಸಿಮ್ ಪಿನ್ ನಮೂದಿಸಿ, ನಿಮ್ಮಲ್ಲಿ <xliff:g id="NUMBER_1">%d</xliff:g> ಪ್ರಯತ್ನಗಳು ಬಾಕಿ ಉಳಿದಿವೆ.</item> + <item quantity="other">ಸಿಮ್ ಪಿನ್ ನಮೂದಿಸಿ, ನಿಮ್ಮಲ್ಲಿ <xliff:g id="NUMBER_1">%d</xliff:g> ಪ್ರಯತ್ನಗಳು ಬಾಕಿ ಉಳಿದಿವೆ.</item> + </plurals> + <plurals name="kg_password_default_puk_message" formatted="false" msgid="8744416410184198352"> + <item quantity="one">ಸಿಮ್ ಅನ್ನು ಈಗ ನಿಷ್ಕ್ರಿಯಗೊಳಿಸಲಾಗಿದೆ. ಮುಂದುವರಿಸಲು PUK ಕೋಡ್ ನಮೂದಿಸಿ. ಸಿಮ್ ಶಾಶ್ವತವಾಗಿ ನಿಷ್ಪ್ರಯೋಜಕವಾಗುವ ಮುನ್ನ ನಿಮ್ಮಲ್ಲಿ <xliff:g id="_NUMBER_1">%d</xliff:g> ಪ್ರಯತ್ನಗಳು ಬಾಕಿ ಉಳಿದಿವೆ. ವಿವರಗಳಿಗಾಗಿ ವಾಹಕವನ್ನು ಸಂಪರ್ಕಿಸಿ.</item> + <item quantity="other">ಸಿಮ್ ಅನ್ನು ಈಗ ನಿಷ್ಕ್ರಿಯಗೊಳಿಸಲಾಗಿದೆ. ಮುಂದುವರಿಸಲು PUK ಕೋಡ್ ನಮೂದಿಸಿ. ಸಿಮ್ ಶಾಶ್ವತವಾಗಿ ನಿಷ್ಪ್ರಯೋಜಕವಾಗುವ ಮುನ್ನ ನಿಮ್ಮಲ್ಲಿ <xliff:g id="_NUMBER_1">%d</xliff:g> ಪ್ರಯತ್ನಗಳು ಬಾಕಿ ಉಳಿದಿವೆ. ವಿವರಗಳಿಗಾಗಿ ವಾಹಕವನ್ನು ಸಂಪರ್ಕಿಸಿ.</item> + </plurals> </resources> diff --git a/packages/SystemUI/res-keyguard/values-ky/strings.xml b/packages/SystemUI/res-keyguard/values-ky/strings.xml index 054ce7c01386..a4f5b7dcaf6a 100644 --- a/packages/SystemUI/res-keyguard/values-ky/strings.xml +++ b/packages/SystemUI/res-keyguard/values-ky/strings.xml @@ -141,6 +141,6 @@ </plurals> <plurals name="kg_password_default_puk_message" formatted="false" msgid="8744416410184198352"> <item quantity="other">SIM-карта азыр жарактан чыкты. Улантуу үчүн PUK-кодду киргизиңиз. SIM-картанын биротоло жарактан чыгарына <xliff:g id="_NUMBER_1">%d</xliff:g> аракет калды. Чоо-жайын билүү үчүн байланыш операторуна кайрылыңыз.</item> - <item quantity="one">SIM-карта азыр жарактан чыкты. Улантуу үчүн PUK-кодду киргизиңиз. SIM-картанын биротоло жарактан чыгарына <xliff:g id="_NUMBER_0">%d</xliff:g> аракет калды. Чоо-жайын билүү үчүн байланыш операторуна кайрылыңыз.</item> + <item quantity="one">SIM-карта азыр жарактан чыкты. Улантуу үчүн PUK-кодду киргизиңиз. SIM-картанын биротоло жарактан чыгаарына <xliff:g id="_NUMBER_0">%d</xliff:g> аракет калды. Чоо-жайын билүү үчүн байланыш операторуна кайрылыңыз.</item> </plurals> </resources> diff --git a/packages/SystemUI/res-keyguard/values-ml/strings.xml b/packages/SystemUI/res-keyguard/values-ml/strings.xml index 7f0e9578e816..d62537dcd65c 100644 --- a/packages/SystemUI/res-keyguard/values-ml/strings.xml +++ b/packages/SystemUI/res-keyguard/values-ml/strings.xml @@ -135,6 +135,12 @@ <item quantity="one">ഉപകരണം <xliff:g id="NUMBER_0">%d</xliff:g> മണിക്കൂറായി അൺലോക്ക് ചെയ്തിട്ടില്ല. പാസ്വേഡ് സ്ഥിരീകരിക്കുക.</item> </plurals> <string name="fingerprint_not_recognized" msgid="348813995267914625">"തിരിച്ചറിഞ്ഞില്ല"</string> - <!-- no translation found for kg_password_default_pin_message (6203676909479972943) --> - <!-- no translation found for kg_password_default_puk_message (8744416410184198352) --> + <plurals name="kg_password_default_pin_message" formatted="false" msgid="6203676909479972943"> + <item quantity="other">സിം പിൻ നൽകുക, <xliff:g id="NUMBER_1">%d</xliff:g> ശ്രമങ്ങൾ കൂടി ശേഷിക്കുന്നു.</item> + <item quantity="one">സിം പിൻ നൽകുക, ഉപകരണം അൺലോക്ക് ചെയ്യാൻ കാരിയറുമായി ബന്ധപ്പെടേണ്ടിവരുന്നതിന് മുമ്പ് <xliff:g id="NUMBER_0">%d</xliff:g> ശ്രമം കൂടി ശേഷിക്കുന്നു.</item> + </plurals> + <plurals name="kg_password_default_puk_message" formatted="false" msgid="8744416410184198352"> + <item quantity="other">സിം ഇപ്പോൾ പ്രവർത്തനരഹിതമാക്കി. തുടരുന്നതിന് PUK കോഡ് നൽകുക. സിം ശാശ്വതമായി ഉപയോഗശൂന്യമാകുന്നതിന് മുമ്പായി <xliff:g id="_NUMBER_1">%d</xliff:g> ശ്രമങ്ങൾ കൂടി ശേഷിക്കുന്നു. വിശദാംശങ്ങൾക്ക് കാരിയറുമായി ബന്ധപ്പെടുക.</item> + <item quantity="one">സിം ഇപ്പോൾ പ്രവർത്തനരഹിതമാക്കി. തുടരുന്നതിന് PUK കോഡ് നൽകുക. സിം ശാശ്വതമായി ഉപയോഗശൂന്യമാകുന്നതിന് മുമ്പായി <xliff:g id="_NUMBER_0">%d</xliff:g> ശ്രമം കൂടി ശേഷിക്കുന്നു. വിശദാംശങ്ങൾക്ക് കാരിയറുമായി ബന്ധപ്പെടുക.</item> + </plurals> </resources> diff --git a/packages/SystemUI/res-keyguard/values-pa/strings.xml b/packages/SystemUI/res-keyguard/values-pa/strings.xml index dc0e53bae184..d5d27cacf525 100644 --- a/packages/SystemUI/res-keyguard/values-pa/strings.xml +++ b/packages/SystemUI/res-keyguard/values-pa/strings.xml @@ -135,6 +135,12 @@ <item quantity="other">ਡੀਵਾਈਸ <xliff:g id="NUMBER_1">%d</xliff:g> ਘੰਟਿਆਂ ਤੋਂ ਅਣਲਾਕ ਨਹੀਂ ਕੀਤਾ ਗਿਆ ਹੈ। ਪਾਸਵਰਡ ਦੀ ਪੁਸ਼ਟੀ ਕਰੋ</item> </plurals> <string name="fingerprint_not_recognized" msgid="348813995267914625">"ਪਛਾਣ ਨਹੀਂ ਹੋਈ"</string> - <!-- no translation found for kg_password_default_pin_message (6203676909479972943) --> - <!-- no translation found for kg_password_default_puk_message (8744416410184198352) --> + <plurals name="kg_password_default_pin_message" formatted="false" msgid="6203676909479972943"> + <item quantity="one">ਸਿਮ ਪਿੰਨ ਦਾਖਲ ਕਰੋ, ਤੁਹਾਡੇ ਕੋਲ <xliff:g id="NUMBER_1">%d</xliff:g> ਕੋਸ਼ਿਸ਼ ਬਾਕੀ ਹੈ।</item> + <item quantity="other">ਸਿਮ ਪਿੰਨ ਦਾਖਲ ਕਰੋ, ਤੁਹਾਡੇ ਕੋਲ <xliff:g id="NUMBER_1">%d</xliff:g> ਕੋਸ਼ਿਸ਼ਾਂ ਬਾਕੀ ਹਨ।</item> + </plurals> + <plurals name="kg_password_default_puk_message" formatted="false" msgid="8744416410184198352"> + <item quantity="one">ਸਿਮ ਹੁਣ ਬੰਦ ਹੋ ਗਿਆ ਹੈ। ਜਾਰੀ ਰੱਖਣ ਲਈ PUK ਕੋਡ ਦਾਖਲ ਕਰੋ। ਸਿਮ ਦੇ ਪੱਕੇ ਤੌਰ \'ਤੇ ਬੇਕਾਰ ਹੋ ਜਾਣ ਤੋਂ ਪਹਿਲਾਂ ਤੁਹਾਡੇ ਕੋਲ <xliff:g id="_NUMBER_1">%d</xliff:g> ਕੋਸ਼ਿਸ਼ ਬਾਕੀ ਹੈ। ਵੇਰਵਿਆਂ ਲਈ ਕੈਰੀਅਰ ਨੂੰ ਸੰਪਰਕ ਕਰੋ।</item> + <item quantity="other">ਸਿਮ ਹੁਣ ਬੰਦ ਹੋ ਗਿਆ ਹੈ। ਜਾਰੀ ਰੱਖਣ ਲਈ PUK ਕੋਡ ਦਾਖਲ ਕਰੋ। ਸਿਮ ਦੇ ਪੱਕੇ ਤੌਰ \'ਤੇ ਬੇਕਾਰ ਹੋ ਜਾਣ ਤੋਂ ਪਹਿਲਾਂ ਤੁਹਾਡੇ ਕੋਲ <xliff:g id="_NUMBER_1">%d</xliff:g> ਕੋਸ਼ਿਸ਼ਾਂ ਬਾਕੀ ਹਨ। ਵੇਰਵਿਆਂ ਲਈ ਕੈਰੀਅਰ ਨੂੰ ਸੰਪਰਕ ਕਰੋ।</item> + </plurals> </resources> diff --git a/packages/SystemUI/res-keyguard/values-ta/strings.xml b/packages/SystemUI/res-keyguard/values-ta/strings.xml index 727ea5beef46..2ce57a09875c 100644 --- a/packages/SystemUI/res-keyguard/values-ta/strings.xml +++ b/packages/SystemUI/res-keyguard/values-ta/strings.xml @@ -135,6 +135,12 @@ <item quantity="one"><xliff:g id="NUMBER_0">%d</xliff:g> மணிநேரமாகச் சாதனம் திறக்கப்படவில்லை. கடவுச்சொல்லை உறுதிப்படுத்தவும்.</item> </plurals> <string name="fingerprint_not_recognized" msgid="348813995267914625">"அடையாளங்காண முடியவில்லை"</string> - <!-- no translation found for kg_password_default_pin_message (6203676909479972943) --> - <!-- no translation found for kg_password_default_puk_message (8744416410184198352) --> + <plurals name="kg_password_default_pin_message" formatted="false" msgid="6203676909479972943"> + <item quantity="other">சிம் பின்னை உள்ளிடவும், மேலும் <xliff:g id="NUMBER_1">%d</xliff:g> முறை முயற்சிக்கலாம்.</item> + <item quantity="one">சிம் பின்னை உள்ளிடவும், நீங்கள் <xliff:g id="NUMBER_0">%d</xliff:g> முறை மட்டுமே முயற்சிக்க முடியுமென்பதால், அதற்கு முன்பு மொபைல் நிறுவனத்தைத் தொடர்பு கொண்டு சாதனத்தைத் திறக்க முயலவும்.</item> + </plurals> + <plurals name="kg_password_default_puk_message" formatted="false" msgid="8744416410184198352"> + <item quantity="other">சிம் தற்போது முடக்கப்பட்டுள்ளது. தொடர்வதற்கு, PUK குறியீட்டை உள்ளிடவும். நீங்கள் <xliff:g id="_NUMBER_1">%d</xliff:g> முறை மட்டுமே முயற்சிக்க முடியும். அதன்பிறகு சிம் நிரந்தரமாக முடக்கப்படும். விவரங்களுக்கு, மொபைல் நிறுவனத்தைத் தொடர்புகொள்ளவும்.</item> + <item quantity="one">சிம் தற்போது முடக்கப்பட்டுள்ளது. தொடர்வதற்கு, PUK குறியீட்டை உள்ளிடவும். நீங்கள் <xliff:g id="_NUMBER_0">%d</xliff:g> முறை மட்டுமே முயற்சிக்க முடியும். அதன்பிறகு சிம் நிரந்தரமாக முடக்கப்படும். விவரங்களுக்கு, மொபைல் நிறுவனத்தைத் தொடர்புகொள்ளவும்.</item> + </plurals> </resources> diff --git a/packages/SystemUI/res-keyguard/values-te/strings.xml b/packages/SystemUI/res-keyguard/values-te/strings.xml index ddc5928347cc..bdee4a3a0b66 100644 --- a/packages/SystemUI/res-keyguard/values-te/strings.xml +++ b/packages/SystemUI/res-keyguard/values-te/strings.xml @@ -135,6 +135,12 @@ <item quantity="one"><xliff:g id="NUMBER_0">%d</xliff:g> గంట పాటు పరికరాన్ని అన్లాక్ చేయలేదు. పాస్వర్డ్ని నమోదు చేయండి.</item> </plurals> <string name="fingerprint_not_recognized" msgid="348813995267914625">"గుర్తించలేదు"</string> - <!-- no translation found for kg_password_default_pin_message (6203676909479972943) --> - <!-- no translation found for kg_password_default_puk_message (8744416410184198352) --> + <plurals name="kg_password_default_pin_message" formatted="false" msgid="6203676909479972943"> + <item quantity="other">SIM పిన్ని నమోదు చేయండి, మీకు <xliff:g id="NUMBER_1">%d</xliff:g> ప్రయత్నలు మిగిలి ఉన్నాయి.</item> + <item quantity="one">SIM పిన్ని నమోదు చేయండి, మీరు మీ పరికరాన్ని అన్లాక్ చేయడానికి తప్పనిసరిగా మీ క్యారియర్ను సంప్రదించడానికి ముందు మీకు <xliff:g id="NUMBER_0">%d</xliff:g> ప్రయత్నం మిగిలి ఉంది.</item> + </plurals> + <plurals name="kg_password_default_puk_message" formatted="false" msgid="8744416410184198352"> + <item quantity="other">SIM ఇప్పుడు నిలిపివేయబడింది. PUK కోడ్ను నమోదు చేయండి. SIM శాశ్వతంగా నిరుపయోగం కాకుండా ఉండటానికి మీకు <xliff:g id="_NUMBER_1">%d</xliff:g> ప్రయత్నాలు మిగిలి ఉన్నాయి. వివరాల కోసం కారియర్ను సంప్రదించండి.</item> + <item quantity="one">SIM ఇప్పుడు నిలిపివేయబడింది. PUK కోడ్ను నమోదు చేయండి. SIM శాశ్వతంగా నిరుపయోగం కాకుండా ఉండటానికి మీకు <xliff:g id="_NUMBER_0">%d</xliff:g> ప్రయత్నం మిగిలి ఉంది వివరాల కోసం కారియర్ను సంప్రదించండి.</item> + </plurals> </resources> diff --git a/packages/SystemUI/res-keyguard/values-ur/strings.xml b/packages/SystemUI/res-keyguard/values-ur/strings.xml index 5440a87ff22f..cd99c9279890 100644 --- a/packages/SystemUI/res-keyguard/values-ur/strings.xml +++ b/packages/SystemUI/res-keyguard/values-ur/strings.xml @@ -135,6 +135,12 @@ <item quantity="one">آلہ <xliff:g id="NUMBER_0">%d</xliff:g> گھنٹہ سے غیر مقفل نہیں کیا گیا۔ پاسورڈ کی توثیق کریں۔</item> </plurals> <string name="fingerprint_not_recognized" msgid="348813995267914625">"تسلیم شدہ نہیں ہے"</string> - <!-- no translation found for kg_password_default_pin_message (6203676909479972943) --> - <!-- no translation found for kg_password_default_puk_message (8744416410184198352) --> + <plurals name="kg_password_default_pin_message" formatted="false" msgid="6203676909479972943"> + <item quantity="other">SIM کا PIN درج کریں، آپ کے پاس <xliff:g id="NUMBER_1">%d</xliff:g> کوششیں بچی ہیں۔</item> + <item quantity="one">SIM کا PIN درج کریں، آپ کے پاس <xliff:g id="NUMBER_0">%d</xliff:g> کوشش بچی ہے، اس کے بعد آپ کو اپنا آلہ غیر مقفل کرنے کیلئے اپنے کیریئر سے رابطہ کرنا ہوگا۔</item> + </plurals> + <plurals name="kg_password_default_puk_message" formatted="false" msgid="8744416410184198352"> + <item quantity="other">SIM اب غیر فعال ہے۔ جاری رکھنے کیلئے PUK کوڈ درج کریں۔ SIM کے مستقل طور پر ناقابل استعمال ہونے سے پہلے آپ کے پاس <xliff:g id="_NUMBER_1">%d</xliff:g> کوششیں بچی ہیں۔ تفصیلات کیلئے کیریئر سے رابطہ کریں۔</item> + <item quantity="one">SIM اب غیر فعال ہے۔ جاری رکھنے کیلئے PUK کوڈ درج کریں۔ SIM کے مستقل طور پر ناقابل استعمال ہونے سے پہلے آپ کے پاس <xliff:g id="_NUMBER_0">%d</xliff:g> کوشش بچی ہے۔ تفصیلات کیلئے کیریئر سے رابطہ کریں۔</item> + </plurals> </resources> diff --git a/packages/SystemUI/res-keyguard/values-uz/strings.xml b/packages/SystemUI/res-keyguard/values-uz/strings.xml index ee11cffd09d2..204bb8b0b737 100644 --- a/packages/SystemUI/res-keyguard/values-uz/strings.xml +++ b/packages/SystemUI/res-keyguard/values-uz/strings.xml @@ -140,7 +140,7 @@ <item quantity="one">SIM PIN kodini kiriting, qurilmani qulfdan chiqarish uchun sizda <xliff:g id="NUMBER_0">%d</xliff:g> ta urinish bor.</item> </plurals> <plurals name="kg_password_default_puk_message" formatted="false" msgid="8744416410184198352"> - <item quantity="other">SIM karta faolsizlantirildi. Davom etish uchun PUK kodni kiriting. Yana <xliff:g id="_NUMBER_1">%d</xliff:g> marta urinib ko‘rganingizdan keyin SIM kartadan umuman foydalanib bo‘lmaydi. Batafsil axborot olish uchun tarmoq operatoriga murojaat qiling.</item> - <item quantity="one">SIM karta faolsizlantirildi. Davom etish uchun PUK kodni kiriting. Yana <xliff:g id="_NUMBER_0">%d</xliff:g> marta urinib ko‘rganingizdan keyin SIM kartadan umuman foydalanib bo‘lmaydi. Batafsil axborot olish uchun tarmoq operatoriga murojaat qiling.</item> + <item quantity="other">SIM karta faolsizlantirildi. Davom etish uchun PUK kodni kiriting. Yana <xliff:g id="_NUMBER_1">%d</xliff:g> marta xato qilsangiz, SIM kartangiz butunlay qulflanadi. Batafsil axborot olish uchun tarmoq operatoriga murojaat qiling.</item> + <item quantity="one">SIM karta faolsizlantirildi. Davom etish uchun PUK kodni kiriting. Yana <xliff:g id="_NUMBER_0">%d</xliff:g> marta xato qilsangiz, SIM kartangiz butunlay qulflanadi. Batafsil axborot olish uchun tarmoq operatoriga murojaat qiling.</item> </plurals> </resources> diff --git a/packages/SystemUI/res/values-af/strings.xml b/packages/SystemUI/res/values-af/strings.xml index b9a228f7d538..15a7d4c5e339 100644 --- a/packages/SystemUI/res/values-af/strings.xml +++ b/packages/SystemUI/res/values-af/strings.xml @@ -492,6 +492,7 @@ <string name="stream_bluetooth_sco" msgid="2055645746402746292">"Bluetooth"</string> <string name="stream_dtmf" msgid="2447177903892477915">"Dubbele multitoonfrekwensie"</string> <string name="stream_accessibility" msgid="301136219144385106">"Toeganklikheid"</string> + <string name="ring_toggle_title" msgid="3281244519428819576">"Oproepe"</string> <string name="volume_ringer_status_normal" msgid="4273142424125855384">"Lui"</string> <string name="volume_ringer_status_vibrate" msgid="1825615171021346557">"Vibreer"</string> <string name="volume_ringer_status_silent" msgid="6896394161022916369">"Demp"</string> diff --git a/packages/SystemUI/res/values-am/strings.xml b/packages/SystemUI/res/values-am/strings.xml index 961ac4514477..ee9acae25e5e 100644 --- a/packages/SystemUI/res/values-am/strings.xml +++ b/packages/SystemUI/res/values-am/strings.xml @@ -492,6 +492,7 @@ <string name="stream_bluetooth_sco" msgid="2055645746402746292">"ብሉቱዝ"</string> <string name="stream_dtmf" msgid="2447177903892477915">"ድርብ ባለ በርካታ ቅላጼ ድግምግሞሽ"</string> <string name="stream_accessibility" msgid="301136219144385106">"ተደራሽነት"</string> + <string name="ring_toggle_title" msgid="3281244519428819576">"ጥሪዎች"</string> <string name="volume_ringer_status_normal" msgid="4273142424125855384">"ጥሪ"</string> <string name="volume_ringer_status_vibrate" msgid="1825615171021346557">"ንዘር"</string> <string name="volume_ringer_status_silent" msgid="6896394161022916369">"ድምጸ-ከል አድርግ"</string> diff --git a/packages/SystemUI/res/values-ar/strings.xml b/packages/SystemUI/res/values-ar/strings.xml index 8437705e3cd2..1d8cb4e0f767 100644 --- a/packages/SystemUI/res/values-ar/strings.xml +++ b/packages/SystemUI/res/values-ar/strings.xml @@ -500,6 +500,7 @@ <string name="stream_bluetooth_sco" msgid="2055645746402746292">"بلوتوث"</string> <string name="stream_dtmf" msgid="2447177903892477915">"تردد ثنائي متعدد النغمات"</string> <string name="stream_accessibility" msgid="301136219144385106">"إمكانية الوصول"</string> + <string name="ring_toggle_title" msgid="3281244519428819576">"المكالمات"</string> <string name="volume_ringer_status_normal" msgid="4273142424125855384">"استصدار رنين"</string> <string name="volume_ringer_status_vibrate" msgid="1825615171021346557">"اهتزاز"</string> <string name="volume_ringer_status_silent" msgid="6896394161022916369">"كتم الصوت"</string> diff --git a/packages/SystemUI/res/values-b+sr+Latn/strings.xml b/packages/SystemUI/res/values-b+sr+Latn/strings.xml index ae3854fb95af..fd42eb5491dd 100644 --- a/packages/SystemUI/res/values-b+sr+Latn/strings.xml +++ b/packages/SystemUI/res/values-b+sr+Latn/strings.xml @@ -494,6 +494,7 @@ <string name="stream_bluetooth_sco" msgid="2055645746402746292">"Bluetooth"</string> <string name="stream_dtmf" msgid="2447177903892477915">"Višestruka frekvencija dualnog tona"</string> <string name="stream_accessibility" msgid="301136219144385106">"Pristupačnost"</string> + <string name="ring_toggle_title" msgid="3281244519428819576">"Pozivi"</string> <string name="volume_ringer_status_normal" msgid="4273142424125855384">"Aktiviraj zvono"</string> <string name="volume_ringer_status_vibrate" msgid="1825615171021346557">"Vibriraj"</string> <string name="volume_ringer_status_silent" msgid="6896394161022916369">"Isključi zvuk"</string> diff --git a/packages/SystemUI/res/values-be/strings.xml b/packages/SystemUI/res/values-be/strings.xml index 3c9f83afc828..e82dbff0f1f5 100644 --- a/packages/SystemUI/res/values-be/strings.xml +++ b/packages/SystemUI/res/values-be/strings.xml @@ -498,6 +498,7 @@ <string name="stream_bluetooth_sco" msgid="2055645746402746292">"Bluetooth"</string> <string name="stream_dtmf" msgid="2447177903892477915">"Двухтанальны шматчастотны"</string> <string name="stream_accessibility" msgid="301136219144385106">"Спецыяльныя магчымасці"</string> + <string name="ring_toggle_title" msgid="3281244519428819576">"Выклікі"</string> <string name="volume_ringer_status_normal" msgid="4273142424125855384">"Званок"</string> <string name="volume_ringer_status_vibrate" msgid="1825615171021346557">"Вібрацыя"</string> <string name="volume_ringer_status_silent" msgid="6896394161022916369">"Гук выключаны"</string> diff --git a/packages/SystemUI/res/values-bg/strings.xml b/packages/SystemUI/res/values-bg/strings.xml index 1cd4c910c54d..ccdc44bd8a40 100644 --- a/packages/SystemUI/res/values-bg/strings.xml +++ b/packages/SystemUI/res/values-bg/strings.xml @@ -492,6 +492,7 @@ <string name="stream_bluetooth_sco" msgid="2055645746402746292">"Bluetooth"</string> <string name="stream_dtmf" msgid="2447177903892477915">"Тонално набиране"</string> <string name="stream_accessibility" msgid="301136219144385106">"Достъпност"</string> + <string name="ring_toggle_title" msgid="3281244519428819576">"Обаждания"</string> <string name="volume_ringer_status_normal" msgid="4273142424125855384">"Позвъняване"</string> <string name="volume_ringer_status_vibrate" msgid="1825615171021346557">"Вибриране"</string> <string name="volume_ringer_status_silent" msgid="6896394161022916369">"Без звук"</string> diff --git a/packages/SystemUI/res/values-bn/strings.xml b/packages/SystemUI/res/values-bn/strings.xml index c82352553f95..96028f70375f 100644 --- a/packages/SystemUI/res/values-bn/strings.xml +++ b/packages/SystemUI/res/values-bn/strings.xml @@ -492,12 +492,11 @@ <string name="stream_bluetooth_sco" msgid="2055645746402746292">"ব্লুটুথ"</string> <string name="stream_dtmf" msgid="2447177903892477915">"ডুয়েল মাল্টি টোন ফ্রিকোয়েন্সি"</string> <string name="stream_accessibility" msgid="301136219144385106">"অ্যাক্সেসযোগ্যতা"</string> - <!-- no translation found for volume_ringer_status_normal (4273142424125855384) --> - <skip /> - <!-- no translation found for volume_ringer_status_vibrate (1825615171021346557) --> - <skip /> - <!-- no translation found for volume_ringer_status_silent (6896394161022916369) --> + <!-- no translation found for ring_toggle_title (3281244519428819576) --> <skip /> + <string name="volume_ringer_status_normal" msgid="4273142424125855384">"রিং"</string> + <string name="volume_ringer_status_vibrate" msgid="1825615171021346557">"ভাইব্রেট"</string> + <string name="volume_ringer_status_silent" msgid="6896394161022916369">"মিউট"</string> <string name="volume_stream_content_description_unmute" msgid="4436631538779230857">"%1$s। সশব্দ করতে আলতো চাপুন।"</string> <string name="volume_stream_content_description_vibrate" msgid="1187944970457807498">"%1$s। কম্পন এ সেট করতে আলতো চাপুন। অ্যাক্সেসযোগ্যতার পরিষেবাগুলিকে নিঃশব্দ করা হতে পারে।"</string> <string name="volume_stream_content_description_mute" msgid="3625049841390467354">"%1$s। নিঃশব্দ করতে আলতো চাপুন। অ্যাক্সেসযোগ্যতার পরিষেবাগুলিকে নিঃশব্দ করা হতে পারে।"</string> diff --git a/packages/SystemUI/res/values-ca/strings.xml b/packages/SystemUI/res/values-ca/strings.xml index e87bb45247f5..dacf9d753dc1 100644 --- a/packages/SystemUI/res/values-ca/strings.xml +++ b/packages/SystemUI/res/values-ca/strings.xml @@ -492,6 +492,7 @@ <string name="stream_bluetooth_sco" msgid="2055645746402746292">"Bluetooth"</string> <string name="stream_dtmf" msgid="2447177903892477915">"Marcatge per tons"</string> <string name="stream_accessibility" msgid="301136219144385106">"Accessibilitat"</string> + <string name="ring_toggle_title" msgid="3281244519428819576">"Trucades"</string> <string name="volume_ringer_status_normal" msgid="4273142424125855384">"Fes sonar"</string> <string name="volume_ringer_status_vibrate" msgid="1825615171021346557">"Vibra"</string> <string name="volume_ringer_status_silent" msgid="6896394161022916369">"Silencia"</string> diff --git a/packages/SystemUI/res/values-cs/strings.xml b/packages/SystemUI/res/values-cs/strings.xml index e0e14a16ce3b..4166697b6c98 100644 --- a/packages/SystemUI/res/values-cs/strings.xml +++ b/packages/SystemUI/res/values-cs/strings.xml @@ -498,6 +498,7 @@ <string name="stream_bluetooth_sco" msgid="2055645746402746292">"Bluetooth"</string> <string name="stream_dtmf" msgid="2447177903892477915">"Tónová volba"</string> <string name="stream_accessibility" msgid="301136219144385106">"Přístupnost"</string> + <string name="ring_toggle_title" msgid="3281244519428819576">"Volání"</string> <string name="volume_ringer_status_normal" msgid="4273142424125855384">"Vyzvánění"</string> <string name="volume_ringer_status_vibrate" msgid="1825615171021346557">"Vibrace"</string> <string name="volume_ringer_status_silent" msgid="6896394161022916369">"Ztlumení"</string> diff --git a/packages/SystemUI/res/values-da/strings.xml b/packages/SystemUI/res/values-da/strings.xml index fb886fc6dd64..52446f80d0a5 100644 --- a/packages/SystemUI/res/values-da/strings.xml +++ b/packages/SystemUI/res/values-da/strings.xml @@ -492,6 +492,7 @@ <string name="stream_bluetooth_sco" msgid="2055645746402746292">"Bluetooth"</string> <string name="stream_dtmf" msgid="2447177903892477915">"Tonesignalfrekvens (DTMF)"</string> <string name="stream_accessibility" msgid="301136219144385106">"Hjælpefunktioner"</string> + <string name="ring_toggle_title" msgid="3281244519428819576">"Opkald"</string> <string name="volume_ringer_status_normal" msgid="4273142424125855384">"Ring"</string> <string name="volume_ringer_status_vibrate" msgid="1825615171021346557">"Vibration"</string> <string name="volume_ringer_status_silent" msgid="6896394161022916369">"Slå lyden fra"</string> diff --git a/packages/SystemUI/res/values-de/strings.xml b/packages/SystemUI/res/values-de/strings.xml index feb7064be29f..83a8e3374225 100644 --- a/packages/SystemUI/res/values-de/strings.xml +++ b/packages/SystemUI/res/values-de/strings.xml @@ -496,6 +496,7 @@ <string name="stream_bluetooth_sco" msgid="2055645746402746292">"Bluetooth"</string> <string name="stream_dtmf" msgid="2447177903892477915">"Doppelton-Mehrfrequenz"</string> <string name="stream_accessibility" msgid="301136219144385106">"Bedienungshilfen"</string> + <string name="ring_toggle_title" msgid="3281244519428819576">"Anrufe"</string> <string name="volume_ringer_status_normal" msgid="4273142424125855384">"Klingeln lassen"</string> <string name="volume_ringer_status_vibrate" msgid="1825615171021346557">"Vibrieren"</string> <string name="volume_ringer_status_silent" msgid="6896394161022916369">"Stummschalten"</string> diff --git a/packages/SystemUI/res/values-el/strings.xml b/packages/SystemUI/res/values-el/strings.xml index 1fe90101e41f..9a618283e914 100644 --- a/packages/SystemUI/res/values-el/strings.xml +++ b/packages/SystemUI/res/values-el/strings.xml @@ -492,6 +492,7 @@ <string name="stream_bluetooth_sco" msgid="2055645746402746292">"Bluetooth"</string> <string name="stream_dtmf" msgid="2447177903892477915">"Πολυσυχνότητα διπλού τόνου"</string> <string name="stream_accessibility" msgid="301136219144385106">"Προσβασιμότητα"</string> + <string name="ring_toggle_title" msgid="3281244519428819576">"Κλήσεις"</string> <string name="volume_ringer_status_normal" msgid="4273142424125855384">"Κουδούνισμα"</string> <string name="volume_ringer_status_vibrate" msgid="1825615171021346557">"Δόνηση"</string> <string name="volume_ringer_status_silent" msgid="6896394161022916369">"Σίγαση"</string> diff --git a/packages/SystemUI/res/values-es-rUS/strings.xml b/packages/SystemUI/res/values-es-rUS/strings.xml index 4af3f5912d36..d808119eb494 100644 --- a/packages/SystemUI/res/values-es-rUS/strings.xml +++ b/packages/SystemUI/res/values-es-rUS/strings.xml @@ -494,6 +494,7 @@ <string name="stream_bluetooth_sco" msgid="2055645746402746292">"Bluetooth"</string> <string name="stream_dtmf" msgid="2447177903892477915">"Multifrecuencia de tono doble"</string> <string name="stream_accessibility" msgid="301136219144385106">"Accesibilidad"</string> + <string name="ring_toggle_title" msgid="3281244519428819576">"Llamadas"</string> <string name="volume_ringer_status_normal" msgid="4273142424125855384">"Hacer sonar"</string> <string name="volume_ringer_status_vibrate" msgid="1825615171021346557">"Vibrar"</string> <string name="volume_ringer_status_silent" msgid="6896394161022916369">"Silenciar"</string> diff --git a/packages/SystemUI/res/values-et/strings.xml b/packages/SystemUI/res/values-et/strings.xml index 27e4920b341e..3a5af25136ac 100644 --- a/packages/SystemUI/res/values-et/strings.xml +++ b/packages/SystemUI/res/values-et/strings.xml @@ -494,6 +494,7 @@ <string name="stream_bluetooth_sco" msgid="2055645746402746292">"Bluetooth"</string> <string name="stream_dtmf" msgid="2447177903892477915">"Kaks mitme tooniga sagedust"</string> <string name="stream_accessibility" msgid="301136219144385106">"Juurdepääsetavus"</string> + <string name="ring_toggle_title" msgid="3281244519428819576">"Kõned"</string> <string name="volume_ringer_status_normal" msgid="4273142424125855384">"Helisemine"</string> <string name="volume_ringer_status_vibrate" msgid="1825615171021346557">"Vibreerimine"</string> <string name="volume_ringer_status_silent" msgid="6896394161022916369">"Vaigistatud"</string> diff --git a/packages/SystemUI/res/values-eu/strings.xml b/packages/SystemUI/res/values-eu/strings.xml index 5d4181e124fe..87348de89560 100644 --- a/packages/SystemUI/res/values-eu/strings.xml +++ b/packages/SystemUI/res/values-eu/strings.xml @@ -494,6 +494,7 @@ <string name="stream_bluetooth_sco" msgid="2055645746402746292">"Bluetooth konexioa"</string> <string name="stream_dtmf" msgid="2447177903892477915">"Tonu anitzeko maiztasun duala"</string> <string name="stream_accessibility" msgid="301136219144385106">"Erabilerraztasuna"</string> + <string name="ring_toggle_title" msgid="3281244519428819576">"Deiak"</string> <string name="volume_ringer_status_normal" msgid="4273142424125855384">"Jo tonua"</string> <string name="volume_ringer_status_vibrate" msgid="1825615171021346557">"Dardara"</string> <string name="volume_ringer_status_silent" msgid="6896394161022916369">"Ez jo tonua"</string> diff --git a/packages/SystemUI/res/values-fa/strings.xml b/packages/SystemUI/res/values-fa/strings.xml index 9360c1dfa614..bb77daa072da 100644 --- a/packages/SystemUI/res/values-fa/strings.xml +++ b/packages/SystemUI/res/values-fa/strings.xml @@ -492,6 +492,7 @@ <string name="stream_bluetooth_sco" msgid="2055645746402746292">"بلوتوث"</string> <string name="stream_dtmf" msgid="2447177903892477915">"فرکانس دوتایی چند نوایی"</string> <string name="stream_accessibility" msgid="301136219144385106">"دسترسپذیری"</string> + <string name="ring_toggle_title" msgid="3281244519428819576">"تماسها"</string> <string name="volume_ringer_status_normal" msgid="4273142424125855384">"زنگ زدن"</string> <string name="volume_ringer_status_vibrate" msgid="1825615171021346557">"لرزش"</string> <string name="volume_ringer_status_silent" msgid="6896394161022916369">"بیصدا"</string> diff --git a/packages/SystemUI/res/values-fi/strings.xml b/packages/SystemUI/res/values-fi/strings.xml index 864f8d0bb2f0..3bebe4b538df 100644 --- a/packages/SystemUI/res/values-fi/strings.xml +++ b/packages/SystemUI/res/values-fi/strings.xml @@ -492,6 +492,7 @@ <string name="stream_bluetooth_sco" msgid="2055645746402746292">"Bluetooth"</string> <string name="stream_dtmf" msgid="2447177903892477915">"Äänitaajuusvalinta"</string> <string name="stream_accessibility" msgid="301136219144385106">"Esteettömyys"</string> + <string name="ring_toggle_title" msgid="3281244519428819576">"Puhelut"</string> <string name="volume_ringer_status_normal" msgid="4273142424125855384">"Soittoääni"</string> <string name="volume_ringer_status_vibrate" msgid="1825615171021346557">"Värinä"</string> <string name="volume_ringer_status_silent" msgid="6896394161022916369">"Äänetön"</string> diff --git a/packages/SystemUI/res/values-fr-rCA/strings.xml b/packages/SystemUI/res/values-fr-rCA/strings.xml index 5c4394b86726..a2dde8584838 100644 --- a/packages/SystemUI/res/values-fr-rCA/strings.xml +++ b/packages/SystemUI/res/values-fr-rCA/strings.xml @@ -494,6 +494,7 @@ <string name="stream_bluetooth_sco" msgid="2055645746402746292">"Bluetooth"</string> <string name="stream_dtmf" msgid="2447177903892477915">"Fréquence double multi ton"</string> <string name="stream_accessibility" msgid="301136219144385106">"Accessibilité"</string> + <string name="ring_toggle_title" msgid="3281244519428819576">"Appels"</string> <string name="volume_ringer_status_normal" msgid="4273142424125855384">"Sonnerie"</string> <string name="volume_ringer_status_vibrate" msgid="1825615171021346557">"Vibration"</string> <string name="volume_ringer_status_silent" msgid="6896394161022916369">"Sonnerie désactivée"</string> diff --git a/packages/SystemUI/res/values-gl/strings.xml b/packages/SystemUI/res/values-gl/strings.xml index a3d580be0ad6..9fbcdeceb000 100644 --- a/packages/SystemUI/res/values-gl/strings.xml +++ b/packages/SystemUI/res/values-gl/strings.xml @@ -280,7 +280,7 @@ <string name="quick_settings_rotation_locked_label" msgid="6359205706154282377">"Rotación bloqueada"</string> <string name="quick_settings_rotation_locked_portrait_label" msgid="5102691921442135053">"Vertical"</string> <string name="quick_settings_rotation_locked_landscape_label" msgid="8553157770061178719">"Horizontal"</string> - <string name="quick_settings_ime_label" msgid="7073463064369468429">"Método de entrada"</string> + <string name="quick_settings_ime_label" msgid="7073463064369468429">"Método de introdución de texto"</string> <string name="quick_settings_location_label" msgid="5011327048748762257">"Localización"</string> <string name="quick_settings_location_off_label" msgid="7464544086507331459">"Localización desactivada"</string> <string name="quick_settings_media_device_label" msgid="1302906836372603762">"Dispositivo multimedia"</string> @@ -494,6 +494,7 @@ <string name="stream_bluetooth_sco" msgid="2055645746402746292">"Bluetooth"</string> <string name="stream_dtmf" msgid="2447177903892477915">"Multifrecuencia de dobre ton"</string> <string name="stream_accessibility" msgid="301136219144385106">"Accesibilidade"</string> + <string name="ring_toggle_title" msgid="3281244519428819576">"Chamadas"</string> <string name="volume_ringer_status_normal" msgid="4273142424125855384">"Facer soar"</string> <string name="volume_ringer_status_vibrate" msgid="1825615171021346557">"Vibrar"</string> <string name="volume_ringer_status_silent" msgid="6896394161022916369">"Silenciar"</string> diff --git a/packages/SystemUI/res/values-gu/strings.xml b/packages/SystemUI/res/values-gu/strings.xml index 83f789693518..418f31e1152c 100644 --- a/packages/SystemUI/res/values-gu/strings.xml +++ b/packages/SystemUI/res/values-gu/strings.xml @@ -492,12 +492,11 @@ <string name="stream_bluetooth_sco" msgid="2055645746402746292">"બ્લૂટૂથ"</string> <string name="stream_dtmf" msgid="2447177903892477915">"દ્વિ બહુ ટોન આવર્તન"</string> <string name="stream_accessibility" msgid="301136219144385106">"ઍક્સેસિબિલિટી"</string> - <!-- no translation found for volume_ringer_status_normal (4273142424125855384) --> - <skip /> - <!-- no translation found for volume_ringer_status_vibrate (1825615171021346557) --> - <skip /> - <!-- no translation found for volume_ringer_status_silent (6896394161022916369) --> + <!-- no translation found for ring_toggle_title (3281244519428819576) --> <skip /> + <string name="volume_ringer_status_normal" msgid="4273142424125855384">"રિંગ કરો"</string> + <string name="volume_ringer_status_vibrate" msgid="1825615171021346557">"વાઇબ્રેટ"</string> + <string name="volume_ringer_status_silent" msgid="6896394161022916369">"મ્યૂટ કરો"</string> <string name="volume_stream_content_description_unmute" msgid="4436631538779230857">"%1$s. અનમ્યૂટ કરવા માટે ટૅપ કરો."</string> <string name="volume_stream_content_description_vibrate" msgid="1187944970457807498">"%1$s. વાઇબ્રેટ પર સેટ કરવા માટે ટૅપ કરો. ઍક્સેસિબિલિટી સેવાઓ મ્યૂટ કરવામાં આવી શકે છે."</string> <string name="volume_stream_content_description_mute" msgid="3625049841390467354">"%1$s. મ્યૂટ કરવા માટે ટૅપ કરો. ઍક્સેસિબિલિટી સેવાઓ મ્યૂટ કરવામાં આવી શકે છે."</string> diff --git a/packages/SystemUI/res/values-hi/strings.xml b/packages/SystemUI/res/values-hi/strings.xml index e7fc696dc89e..1acd6e7c46df 100644 --- a/packages/SystemUI/res/values-hi/strings.xml +++ b/packages/SystemUI/res/values-hi/strings.xml @@ -492,6 +492,8 @@ <string name="stream_bluetooth_sco" msgid="2055645746402746292">"ब्लूटूथ"</string> <string name="stream_dtmf" msgid="2447177903892477915">"दोहरी बहु टोन आवृत्ति"</string> <string name="stream_accessibility" msgid="301136219144385106">"सुलभता"</string> + <!-- no translation found for ring_toggle_title (3281244519428819576) --> + <skip /> <string name="volume_ringer_status_normal" msgid="4273142424125855384">"आवाज़ चालू है"</string> <string name="volume_ringer_status_vibrate" msgid="1825615171021346557">"कंपन (वाइब्रेशन)"</string> <string name="volume_ringer_status_silent" msgid="6896394161022916369">"आवाज़ बंद है"</string> diff --git a/packages/SystemUI/res/values-hr/strings.xml b/packages/SystemUI/res/values-hr/strings.xml index 88f50973cf97..7ba9111398ba 100644 --- a/packages/SystemUI/res/values-hr/strings.xml +++ b/packages/SystemUI/res/values-hr/strings.xml @@ -494,6 +494,7 @@ <string name="stream_bluetooth_sco" msgid="2055645746402746292">"Bluetooth"</string> <string name="stream_dtmf" msgid="2447177903892477915">"DTMF"</string> <string name="stream_accessibility" msgid="301136219144385106">"Pristupačnost"</string> + <string name="ring_toggle_title" msgid="3281244519428819576">"Pozivi"</string> <string name="volume_ringer_status_normal" msgid="4273142424125855384">"Zvonjenje"</string> <string name="volume_ringer_status_vibrate" msgid="1825615171021346557">"Vibriranje"</string> <string name="volume_ringer_status_silent" msgid="6896394161022916369">"Zvuk je isključen"</string> diff --git a/packages/SystemUI/res/values-hu/strings.xml b/packages/SystemUI/res/values-hu/strings.xml index e81df8f54286..8aa2c71cb03d 100644 --- a/packages/SystemUI/res/values-hu/strings.xml +++ b/packages/SystemUI/res/values-hu/strings.xml @@ -492,6 +492,7 @@ <string name="stream_bluetooth_sco" msgid="2055645746402746292">"Bluetooth"</string> <string name="stream_dtmf" msgid="2447177903892477915">"Kéthangú többfrekvenciás jelzésátvitel (DTMF)"</string> <string name="stream_accessibility" msgid="301136219144385106">"Kisegítő lehetőségek"</string> + <string name="ring_toggle_title" msgid="3281244519428819576">"Hívások"</string> <string name="volume_ringer_status_normal" msgid="4273142424125855384">"Csörgés"</string> <string name="volume_ringer_status_vibrate" msgid="1825615171021346557">"Rezgés"</string> <string name="volume_ringer_status_silent" msgid="6896394161022916369">"Néma"</string> diff --git a/packages/SystemUI/res/values-hy/strings.xml b/packages/SystemUI/res/values-hy/strings.xml index 2994b9b05e7b..2841074cf0a2 100644 --- a/packages/SystemUI/res/values-hy/strings.xml +++ b/packages/SystemUI/res/values-hy/strings.xml @@ -492,6 +492,7 @@ <string name="stream_bluetooth_sco" msgid="2055645746402746292">"Bluetooth"</string> <string name="stream_dtmf" msgid="2447177903892477915">"Կրկնակի բազմերանգ հաճախականություն"</string> <string name="stream_accessibility" msgid="301136219144385106">"Մատչելիություն"</string> + <string name="ring_toggle_title" msgid="3281244519428819576">"Զանգեր"</string> <string name="volume_ringer_status_normal" msgid="4273142424125855384">"Սովորական"</string> <string name="volume_ringer_status_vibrate" msgid="1825615171021346557">"Թրթռազանգ"</string> <string name="volume_ringer_status_silent" msgid="6896394161022916369">"Անձայն"</string> diff --git a/packages/SystemUI/res/values-is/strings.xml b/packages/SystemUI/res/values-is/strings.xml index 1efc249e6337..66530bdbdc31 100644 --- a/packages/SystemUI/res/values-is/strings.xml +++ b/packages/SystemUI/res/values-is/strings.xml @@ -492,6 +492,7 @@ <string name="stream_bluetooth_sco" msgid="2055645746402746292">"Bluetooth"</string> <string name="stream_dtmf" msgid="2447177903892477915">"Tvítóna fjöltíðni"</string> <string name="stream_accessibility" msgid="301136219144385106">"Aðgengi"</string> + <string name="ring_toggle_title" msgid="3281244519428819576">"Símtöl"</string> <string name="volume_ringer_status_normal" msgid="4273142424125855384">"Hringing"</string> <string name="volume_ringer_status_vibrate" msgid="1825615171021346557">"Titringur"</string> <string name="volume_ringer_status_silent" msgid="6896394161022916369">"Hljóð af"</string> diff --git a/packages/SystemUI/res/values-iw/strings.xml b/packages/SystemUI/res/values-iw/strings.xml index 29271165a427..cbbffa93bda7 100644 --- a/packages/SystemUI/res/values-iw/strings.xml +++ b/packages/SystemUI/res/values-iw/strings.xml @@ -496,6 +496,7 @@ <string name="stream_bluetooth_sco" msgid="2055645746402746292">"Bluetooth"</string> <string name="stream_dtmf" msgid="2447177903892477915">"טון זוגי מרובה תדרים (DTMF)"</string> <string name="stream_accessibility" msgid="301136219144385106">"נגישות"</string> + <string name="ring_toggle_title" msgid="3281244519428819576">"שיחות"</string> <string name="volume_ringer_status_normal" msgid="4273142424125855384">"צלצול"</string> <string name="volume_ringer_status_vibrate" msgid="1825615171021346557">"רטט"</string> <string name="volume_ringer_status_silent" msgid="6896394161022916369">"השתקה"</string> diff --git a/packages/SystemUI/res/values-ja/strings.xml b/packages/SystemUI/res/values-ja/strings.xml index 38622c95e33b..f840225c5a27 100644 --- a/packages/SystemUI/res/values-ja/strings.xml +++ b/packages/SystemUI/res/values-ja/strings.xml @@ -494,6 +494,7 @@ <string name="stream_bluetooth_sco" msgid="2055645746402746292">"Bluetooth"</string> <string name="stream_dtmf" msgid="2447177903892477915">"デュアルトーン マルチ周波数"</string> <string name="stream_accessibility" msgid="301136219144385106">"ユーザー補助機能"</string> + <string name="ring_toggle_title" msgid="3281244519428819576">"通話"</string> <string name="volume_ringer_status_normal" msgid="4273142424125855384">"着信音"</string> <string name="volume_ringer_status_vibrate" msgid="1825615171021346557">"バイブレーション"</string> <string name="volume_ringer_status_silent" msgid="6896394161022916369">"ミュート"</string> diff --git a/packages/SystemUI/res/values-kk/strings.xml b/packages/SystemUI/res/values-kk/strings.xml index 3f36c72feef4..a9e6ffe094d4 100644 --- a/packages/SystemUI/res/values-kk/strings.xml +++ b/packages/SystemUI/res/values-kk/strings.xml @@ -492,6 +492,7 @@ <string name="stream_bluetooth_sco" msgid="2055645746402746292">"Bluetooth"</string> <string name="stream_dtmf" msgid="2447177903892477915">"Қос үнді көп жиілік"</string> <string name="stream_accessibility" msgid="301136219144385106">"Арнайы мүмкіндіктер"</string> + <string name="ring_toggle_title" msgid="3281244519428819576">"Қоңыраулар"</string> <string name="volume_ringer_status_normal" msgid="4273142424125855384">"Шылдырлау"</string> <string name="volume_ringer_status_vibrate" msgid="1825615171021346557">"Діріл"</string> <string name="volume_ringer_status_silent" msgid="6896394161022916369">"Дыбысын өшіру"</string> diff --git a/packages/SystemUI/res/values-km/strings.xml b/packages/SystemUI/res/values-km/strings.xml index 1d5d7d4ce9af..5819f2a9ddfc 100644 --- a/packages/SystemUI/res/values-km/strings.xml +++ b/packages/SystemUI/res/values-km/strings.xml @@ -492,9 +492,10 @@ <string name="stream_bluetooth_sco" msgid="2055645746402746292">"ប៊្លូធូស"</string> <string name="stream_dtmf" msgid="2447177903892477915">"ហ្វ្រេកង់ពហុសំឡេងទ្វេ"</string> <string name="stream_accessibility" msgid="301136219144385106">"ភាពងាយស្រួល"</string> + <string name="ring_toggle_title" msgid="3281244519428819576">"ហៅទូរសព្ទ"</string> <string name="volume_ringer_status_normal" msgid="4273142424125855384">"រោទ៍"</string> <string name="volume_ringer_status_vibrate" msgid="1825615171021346557">"ញ័រ"</string> - <string name="volume_ringer_status_silent" msgid="6896394161022916369">"បិទ"</string> + <string name="volume_ringer_status_silent" msgid="6896394161022916369">"បិទសំឡេង"</string> <string name="volume_stream_content_description_unmute" msgid="4436631538779230857">"%1$s។ ប៉ះដើម្បីបើកសំឡេង។"</string> <string name="volume_stream_content_description_vibrate" msgid="1187944970457807498">"%1$s។ ប៉ះដើម្បីកំណត់ឲ្យញ័រ។ សេវាកម្មលទ្ធភាពប្រើប្រាស់អាចនឹងត្រូវបានបិទសំឡេង។"</string> <string name="volume_stream_content_description_mute" msgid="3625049841390467354">"%1$s។ ប៉ះដើម្បីបិទសំឡេង។ សេវាកម្មលទ្ធភាពប្រើប្រាស់អាចនឹងត្រូវបានបិទសំឡេង។"</string> diff --git a/packages/SystemUI/res/values-kn/strings.xml b/packages/SystemUI/res/values-kn/strings.xml index b2e9d8f1730d..5e7e1e23f73f 100644 --- a/packages/SystemUI/res/values-kn/strings.xml +++ b/packages/SystemUI/res/values-kn/strings.xml @@ -492,12 +492,11 @@ <string name="stream_bluetooth_sco" msgid="2055645746402746292">"ಬ್ಲೂಟೂತ್"</string> <string name="stream_dtmf" msgid="2447177903892477915">"ಡ್ಯುಯಲ್ ಬಹು ಟೋನ್ ಆವರ್ತನೆ"</string> <string name="stream_accessibility" msgid="301136219144385106">"ಪ್ರವೇಶಿಸುವಿಕೆ"</string> - <!-- no translation found for volume_ringer_status_normal (4273142424125855384) --> - <skip /> - <!-- no translation found for volume_ringer_status_vibrate (1825615171021346557) --> - <skip /> - <!-- no translation found for volume_ringer_status_silent (6896394161022916369) --> + <!-- no translation found for ring_toggle_title (3281244519428819576) --> <skip /> + <string name="volume_ringer_status_normal" msgid="4273142424125855384">"ರಿಂಗ್"</string> + <string name="volume_ringer_status_vibrate" msgid="1825615171021346557">"ವೈಬ್ರೇಟ್"</string> + <string name="volume_ringer_status_silent" msgid="6896394161022916369">"ಮ್ಯೂಟ್"</string> <string name="volume_stream_content_description_unmute" msgid="4436631538779230857">"%1$s. ಅನ್ಮ್ಯೂಟ್ ಮಾಡುವುದಕ್ಕಾಗಿ ಟ್ಯಾಪ್ ಮಾಡಿ."</string> <string name="volume_stream_content_description_vibrate" msgid="1187944970457807498">"%1$s. ಕಂಪನಕ್ಕೆ ಹೊಂದಿಸಲು ಟ್ಯಾಪ್ ಮಾಡಿ. ಪ್ರವೇಶಿಸುವಿಕೆ ಸೇವೆಗಳನ್ನು ಮ್ಯೂಟ್ ಮಾಡಬಹುದು."</string> <string name="volume_stream_content_description_mute" msgid="3625049841390467354">"%1$s. ಮ್ಯೂಟ್ ಮಾಡಲು ಟ್ಯಾಪ್ ಮಾಡಿ. ಪ್ರವೇಶಿಸುವಿಕೆ ಸೇವೆಗಳನ್ನು ಮ್ಯೂಟ್ ಮಾಡಬಹುದು."</string> diff --git a/packages/SystemUI/res/values-ko/strings.xml b/packages/SystemUI/res/values-ko/strings.xml index 83018be6883d..d86c32f6a8da 100644 --- a/packages/SystemUI/res/values-ko/strings.xml +++ b/packages/SystemUI/res/values-ko/strings.xml @@ -494,6 +494,7 @@ <string name="stream_bluetooth_sco" msgid="2055645746402746292">"블루투스"</string> <string name="stream_dtmf" msgid="2447177903892477915">"듀얼 멀티 톤 주파수"</string> <string name="stream_accessibility" msgid="301136219144385106">"접근성"</string> + <string name="ring_toggle_title" msgid="3281244519428819576">"통화"</string> <string name="volume_ringer_status_normal" msgid="4273142424125855384">"벨소리"</string> <string name="volume_ringer_status_vibrate" msgid="1825615171021346557">"진동"</string> <string name="volume_ringer_status_silent" msgid="6896394161022916369">"음소거"</string> diff --git a/packages/SystemUI/res/values-ky/strings.xml b/packages/SystemUI/res/values-ky/strings.xml index 4aaae3c6e311..e10e2b0f08f0 100644 --- a/packages/SystemUI/res/values-ky/strings.xml +++ b/packages/SystemUI/res/values-ky/strings.xml @@ -492,6 +492,7 @@ <string name="stream_bluetooth_sco" msgid="2055645746402746292">"Bluetooth"</string> <string name="stream_dtmf" msgid="2447177903892477915">"Көп тондуу жыштык"</string> <string name="stream_accessibility" msgid="301136219144385106">"Атайын мүмкүнчүлүктөр"</string> + <string name="ring_toggle_title" msgid="3281244519428819576">"Чалуулар"</string> <string name="volume_ringer_status_normal" msgid="4273142424125855384">"Шыңгыратуу"</string> <string name="volume_ringer_status_vibrate" msgid="1825615171021346557">"Дирилдөө"</string> <string name="volume_ringer_status_silent" msgid="6896394161022916369">"Үнсүз"</string> diff --git a/packages/SystemUI/res/values-lo/strings.xml b/packages/SystemUI/res/values-lo/strings.xml index bc1e5b90957f..2587a209bb79 100644 --- a/packages/SystemUI/res/values-lo/strings.xml +++ b/packages/SystemUI/res/values-lo/strings.xml @@ -492,6 +492,7 @@ <string name="stream_bluetooth_sco" msgid="2055645746402746292">"ບຣູທູດ"</string> <string name="stream_dtmf" msgid="2447177903892477915">"Dual multi tone frequency"</string> <string name="stream_accessibility" msgid="301136219144385106">"ການຊ່ວຍເຂົ້າເຖິງ"</string> + <string name="ring_toggle_title" msgid="3281244519428819576">"ການໂທ"</string> <string name="volume_ringer_status_normal" msgid="4273142424125855384">"ເຕືອນດ້ວຍສຽງ"</string> <string name="volume_ringer_status_vibrate" msgid="1825615171021346557">"ສັ່ນເຕືອນ"</string> <string name="volume_ringer_status_silent" msgid="6896394161022916369">"ປິດ"</string> diff --git a/packages/SystemUI/res/values-lt/strings.xml b/packages/SystemUI/res/values-lt/strings.xml index 53ddcb3ecc30..1c97452af21c 100644 --- a/packages/SystemUI/res/values-lt/strings.xml +++ b/packages/SystemUI/res/values-lt/strings.xml @@ -496,6 +496,7 @@ <string name="stream_bluetooth_sco" msgid="2055645746402746292">"Bluetooth"</string> <string name="stream_dtmf" msgid="2447177903892477915">"Dvigubas kelių tonų dažnis"</string> <string name="stream_accessibility" msgid="301136219144385106">"Pritaikymas neįgaliesiems"</string> + <string name="ring_toggle_title" msgid="3281244519428819576">"Skambučiai"</string> <string name="volume_ringer_status_normal" msgid="4273142424125855384">"Skambinti"</string> <string name="volume_ringer_status_vibrate" msgid="1825615171021346557">"Vibruoti"</string> <string name="volume_ringer_status_silent" msgid="6896394161022916369">"Nutildyti"</string> diff --git a/packages/SystemUI/res/values-lv/strings.xml b/packages/SystemUI/res/values-lv/strings.xml index b1c3db5b7841..c14eb3f6a722 100644 --- a/packages/SystemUI/res/values-lv/strings.xml +++ b/packages/SystemUI/res/values-lv/strings.xml @@ -494,6 +494,7 @@ <string name="stream_bluetooth_sco" msgid="2055645746402746292">"Bluetooth"</string> <string name="stream_dtmf" msgid="2447177903892477915">"Divtoņu daudzfrekvenču signalizācija"</string> <string name="stream_accessibility" msgid="301136219144385106">"Pieejamība"</string> + <string name="ring_toggle_title" msgid="3281244519428819576">"Zvani"</string> <string name="volume_ringer_status_normal" msgid="4273142424125855384">"Zvanīt"</string> <string name="volume_ringer_status_vibrate" msgid="1825615171021346557">"Vibrēt"</string> <string name="volume_ringer_status_silent" msgid="6896394161022916369">"Izslēgt skaņu"</string> diff --git a/packages/SystemUI/res/values-mk/strings.xml b/packages/SystemUI/res/values-mk/strings.xml index 3986e168ec59..e30085c7c70b 100644 --- a/packages/SystemUI/res/values-mk/strings.xml +++ b/packages/SystemUI/res/values-mk/strings.xml @@ -492,6 +492,7 @@ <string name="stream_bluetooth_sco" msgid="2055645746402746292">"Bluetooth"</string> <string name="stream_dtmf" msgid="2447177903892477915">"Двојна повеќетонска фреквенција"</string> <string name="stream_accessibility" msgid="301136219144385106">"Пристапност"</string> + <string name="ring_toggle_title" msgid="3281244519428819576">"Повици"</string> <string name="volume_ringer_status_normal" msgid="4273142424125855384">"Ѕвони"</string> <string name="volume_ringer_status_vibrate" msgid="1825615171021346557">"Вибрации"</string> <string name="volume_ringer_status_silent" msgid="6896394161022916369">"Исклучи звук"</string> diff --git a/packages/SystemUI/res/values-ml/strings.xml b/packages/SystemUI/res/values-ml/strings.xml index 3dfde1302b2a..385331eaa958 100644 --- a/packages/SystemUI/res/values-ml/strings.xml +++ b/packages/SystemUI/res/values-ml/strings.xml @@ -492,12 +492,11 @@ <string name="stream_bluetooth_sco" msgid="2055645746402746292">"ബ്ലൂടൂത്ത്"</string> <string name="stream_dtmf" msgid="2447177903892477915">"ഡ്യുവൽ മൾട്ടി റ്റോൺ ഫ്രീക്വൻസി"</string> <string name="stream_accessibility" msgid="301136219144385106">"ഉപയോഗസഹായി"</string> - <!-- no translation found for volume_ringer_status_normal (4273142424125855384) --> - <skip /> - <!-- no translation found for volume_ringer_status_vibrate (1825615171021346557) --> - <skip /> - <!-- no translation found for volume_ringer_status_silent (6896394161022916369) --> + <!-- no translation found for ring_toggle_title (3281244519428819576) --> <skip /> + <string name="volume_ringer_status_normal" msgid="4273142424125855384">"റിംഗ് ചെയ്യുക"</string> + <string name="volume_ringer_status_vibrate" msgid="1825615171021346557">"വൈബ്രേറ്റ് ചെയ്യുക"</string> + <string name="volume_ringer_status_silent" msgid="6896394161022916369">"മ്യൂട്ട് ചെയ്യുക"</string> <string name="volume_stream_content_description_unmute" msgid="4436631538779230857">"%1$s. അൺമ്യൂട്ടുചെയ്യുന്നതിന് ടാപ്പുചെയ്യുക."</string> <string name="volume_stream_content_description_vibrate" msgid="1187944970457807498">"%1$s. വൈബ്രേറ്റിലേക്ക് സജ്ജമാക്കുന്നതിന് ടാപ്പുചെയ്യുക. ഉപയോഗസഹായി സേവനങ്ങൾ മ്യൂട്ടുചെയ്യപ്പെട്ടേക്കാം."</string> <string name="volume_stream_content_description_mute" msgid="3625049841390467354">"%1$s. മ്യൂട്ടുചെയ്യുന്നതിന് ടാപ്പുചെയ്യുക. ഉപയോഗസഹായി സേവനങ്ങൾ മ്യൂട്ടുചെയ്യപ്പെട്ടേക്കാം."</string> diff --git a/packages/SystemUI/res/values-mn/strings.xml b/packages/SystemUI/res/values-mn/strings.xml index 9f09dadc141c..480956c9b99b 100644 --- a/packages/SystemUI/res/values-mn/strings.xml +++ b/packages/SystemUI/res/values-mn/strings.xml @@ -490,6 +490,7 @@ <string name="stream_bluetooth_sco" msgid="2055645746402746292">"Bluetooth"</string> <string name="stream_dtmf" msgid="2447177903892477915">"Олон дууны давтамж"</string> <string name="stream_accessibility" msgid="301136219144385106">"Хүртээмж"</string> + <string name="ring_toggle_title" msgid="3281244519428819576">"Дуудлага"</string> <string name="volume_ringer_status_normal" msgid="4273142424125855384">"Хонх дуугаргах"</string> <string name="volume_ringer_status_vibrate" msgid="1825615171021346557">"Чичиргэх"</string> <string name="volume_ringer_status_silent" msgid="6896394161022916369">"Хаах"</string> diff --git a/packages/SystemUI/res/values-mr/strings.xml b/packages/SystemUI/res/values-mr/strings.xml index 5b0984d68cbf..8a0d6617f535 100644 --- a/packages/SystemUI/res/values-mr/strings.xml +++ b/packages/SystemUI/res/values-mr/strings.xml @@ -492,6 +492,8 @@ <string name="stream_bluetooth_sco" msgid="2055645746402746292">"ब्लूटूथ"</string> <string name="stream_dtmf" msgid="2447177903892477915">"दुहेरी एकाधिक टोन वारंंवारता"</string> <string name="stream_accessibility" msgid="301136219144385106">"प्रवेशयोग्यता"</string> + <!-- no translation found for ring_toggle_title (3281244519428819576) --> + <skip /> <string name="volume_ringer_status_normal" msgid="4273142424125855384">"रिंग करा"</string> <string name="volume_ringer_status_vibrate" msgid="1825615171021346557">"कंपन"</string> <string name="volume_ringer_status_silent" msgid="6896394161022916369">"म्युट करा"</string> diff --git a/packages/SystemUI/res/values-ms/strings.xml b/packages/SystemUI/res/values-ms/strings.xml index 2907761fc749..e074875a632c 100644 --- a/packages/SystemUI/res/values-ms/strings.xml +++ b/packages/SystemUI/res/values-ms/strings.xml @@ -492,6 +492,7 @@ <string name="stream_bluetooth_sco" msgid="2055645746402746292">"Bluetooth"</string> <string name="stream_dtmf" msgid="2447177903892477915">"Multifrekuensi dwinada"</string> <string name="stream_accessibility" msgid="301136219144385106">"Kebolehaksesan"</string> + <string name="ring_toggle_title" msgid="3281244519428819576">"Panggilan"</string> <string name="volume_ringer_status_normal" msgid="4273142424125855384">"Dering"</string> <string name="volume_ringer_status_vibrate" msgid="1825615171021346557">"Getar"</string> <string name="volume_ringer_status_silent" msgid="6896394161022916369">"Redam"</string> diff --git a/packages/SystemUI/res/values-nb/strings.xml b/packages/SystemUI/res/values-nb/strings.xml index 51b3b0d6ddf3..661df03a5db3 100644 --- a/packages/SystemUI/res/values-nb/strings.xml +++ b/packages/SystemUI/res/values-nb/strings.xml @@ -492,6 +492,7 @@ <string name="stream_bluetooth_sco" msgid="2055645746402746292">"Bluetooth"</string> <string name="stream_dtmf" msgid="2447177903892477915">"DTMF (dual-tone multi-frequency)"</string> <string name="stream_accessibility" msgid="301136219144385106">"Tilgjengelighet"</string> + <string name="ring_toggle_title" msgid="3281244519428819576">"Anrop"</string> <string name="volume_ringer_status_normal" msgid="4273142424125855384">"Ring"</string> <string name="volume_ringer_status_vibrate" msgid="1825615171021346557">"Vibrer"</string> <string name="volume_ringer_status_silent" msgid="6896394161022916369">"Ignorer"</string> diff --git a/packages/SystemUI/res/values-ne/strings.xml b/packages/SystemUI/res/values-ne/strings.xml index 0a63ce2177b7..dce9cc45bdbf 100644 --- a/packages/SystemUI/res/values-ne/strings.xml +++ b/packages/SystemUI/res/values-ne/strings.xml @@ -492,6 +492,8 @@ <string name="stream_bluetooth_sco" msgid="2055645746402746292">"ब्लुटुथ"</string> <string name="stream_dtmf" msgid="2447177903892477915">"दोहोरो बहु टोनको फ्रिक्वेन्सी"</string> <string name="stream_accessibility" msgid="301136219144385106">"पहुँच"</string> + <!-- no translation found for ring_toggle_title (3281244519428819576) --> + <skip /> <string name="volume_ringer_status_normal" msgid="4273142424125855384">"घन्टी"</string> <string name="volume_ringer_status_vibrate" msgid="1825615171021346557">"कम्पन"</string> <string name="volume_ringer_status_silent" msgid="6896394161022916369">"म्युट गर्नुहोस्"</string> diff --git a/packages/SystemUI/res/values-pa/strings.xml b/packages/SystemUI/res/values-pa/strings.xml index c6475dcc361b..43503d6324a5 100644 --- a/packages/SystemUI/res/values-pa/strings.xml +++ b/packages/SystemUI/res/values-pa/strings.xml @@ -492,12 +492,11 @@ <string name="stream_bluetooth_sco" msgid="2055645746402746292">"ਬਲੂਟੁੱਥ"</string> <string name="stream_dtmf" msgid="2447177903892477915">"ਦੂਹਰੀ ਮਲਟੀ ਟੋਨ ਆਵਰਤੀ"</string> <string name="stream_accessibility" msgid="301136219144385106">"ਪਹੁੰਚਯੋਗਤਾ"</string> - <!-- no translation found for volume_ringer_status_normal (4273142424125855384) --> - <skip /> - <!-- no translation found for volume_ringer_status_vibrate (1825615171021346557) --> - <skip /> - <!-- no translation found for volume_ringer_status_silent (6896394161022916369) --> + <!-- no translation found for ring_toggle_title (3281244519428819576) --> <skip /> + <string name="volume_ringer_status_normal" msgid="4273142424125855384">"ਘੰਟੀ"</string> + <string name="volume_ringer_status_vibrate" msgid="1825615171021346557">"ਥਰਥਰਾਹਟ"</string> + <string name="volume_ringer_status_silent" msgid="6896394161022916369">"ਮਿਊਟ"</string> <string name="volume_stream_content_description_unmute" msgid="4436631538779230857">"%1$s। ਅਣਮਿਊਟ ਕਰਨ ਲਈ ਟੈਪ ਕਰੋ।"</string> <string name="volume_stream_content_description_vibrate" msgid="1187944970457807498">"%1$s। ਥਰਥਰਾਹਟ ਸੈੱਟ ਕਰਨ ਲਈ ਟੈਪ ਕਰੋ। ਪਹੁੰਚਯੋਗਤਾ ਸੇਵਾਵਾਂ ਮਿਊਟ ਹੋ ਸਕਦੀਆਂ ਹਨ।"</string> <string name="volume_stream_content_description_mute" msgid="3625049841390467354">"%1$s। ਮਿਊਟ ਕਰਨ ਲਈ ਟੈਪ ਕਰੋ। ਪਹੁੰਚਯੋਗਤਾ ਸੇਵਾਵਾਂ ਮਿਊਟ ਹੋ ਸਕਦੀਆਂ ਹਨ।"</string> diff --git a/packages/SystemUI/res/values-pl/strings.xml b/packages/SystemUI/res/values-pl/strings.xml index bc2a9545e526..e567e6ba2a08 100644 --- a/packages/SystemUI/res/values-pl/strings.xml +++ b/packages/SystemUI/res/values-pl/strings.xml @@ -496,6 +496,7 @@ <string name="stream_bluetooth_sco" msgid="2055645746402746292">"Bluetooth"</string> <string name="stream_dtmf" msgid="2447177903892477915">"DTMF"</string> <string name="stream_accessibility" msgid="301136219144385106">"Ułatwienia dostępu"</string> + <string name="ring_toggle_title" msgid="3281244519428819576">"Połączenia"</string> <string name="volume_ringer_status_normal" msgid="4273142424125855384">"Dzwonek"</string> <string name="volume_ringer_status_vibrate" msgid="1825615171021346557">"Wibracje"</string> <string name="volume_ringer_status_silent" msgid="6896394161022916369">"Wyciszenie"</string> diff --git a/packages/SystemUI/res/values-pt-rPT/strings.xml b/packages/SystemUI/res/values-pt-rPT/strings.xml index 7f2c6435612f..681d3e4751ad 100644 --- a/packages/SystemUI/res/values-pt-rPT/strings.xml +++ b/packages/SystemUI/res/values-pt-rPT/strings.xml @@ -492,6 +492,7 @@ <string name="stream_bluetooth_sco" msgid="2055645746402746292">"Bluetooth"</string> <string name="stream_dtmf" msgid="2447177903892477915">"Multifrequência de duas tonalidades"</string> <string name="stream_accessibility" msgid="301136219144385106">"Acessibilidade"</string> + <string name="ring_toggle_title" msgid="3281244519428819576">"Chamadas"</string> <string name="volume_ringer_status_normal" msgid="4273142424125855384">"Toque"</string> <string name="volume_ringer_status_vibrate" msgid="1825615171021346557">"Vibrar"</string> <string name="volume_ringer_status_silent" msgid="6896394161022916369">"Desativar som"</string> diff --git a/packages/SystemUI/res/values-ro/strings.xml b/packages/SystemUI/res/values-ro/strings.xml index 21084556050a..2a0744edd18b 100644 --- a/packages/SystemUI/res/values-ro/strings.xml +++ b/packages/SystemUI/res/values-ro/strings.xml @@ -496,6 +496,7 @@ <string name="stream_bluetooth_sco" msgid="2055645746402746292">"Bluetooth"</string> <string name="stream_dtmf" msgid="2447177903892477915">"Frecvență tonuri multiple duale"</string> <string name="stream_accessibility" msgid="301136219144385106">"Accesibilitate"</string> + <string name="ring_toggle_title" msgid="3281244519428819576">"Apeluri"</string> <string name="volume_ringer_status_normal" msgid="4273142424125855384">"Sonerie"</string> <string name="volume_ringer_status_vibrate" msgid="1825615171021346557">"Vibrații"</string> <string name="volume_ringer_status_silent" msgid="6896394161022916369">"Blocați"</string> diff --git a/packages/SystemUI/res/values-sk/strings.xml b/packages/SystemUI/res/values-sk/strings.xml index 151c2b4e78b7..694695c9f6fe 100644 --- a/packages/SystemUI/res/values-sk/strings.xml +++ b/packages/SystemUI/res/values-sk/strings.xml @@ -498,6 +498,7 @@ <string name="stream_bluetooth_sco" msgid="2055645746402746292">"Bluetooth"</string> <string name="stream_dtmf" msgid="2447177903892477915">"Dvojtónová multifrekvencia"</string> <string name="stream_accessibility" msgid="301136219144385106">"Dostupnosť"</string> + <string name="ring_toggle_title" msgid="3281244519428819576">"Hovory"</string> <string name="volume_ringer_status_normal" msgid="4273142424125855384">"Prezvoniť"</string> <string name="volume_ringer_status_vibrate" msgid="1825615171021346557">"Vibrovať"</string> <string name="volume_ringer_status_silent" msgid="6896394161022916369">"Vypnúť zvuk"</string> diff --git a/packages/SystemUI/res/values-sl/strings.xml b/packages/SystemUI/res/values-sl/strings.xml index ee628e121636..10d0104c232f 100644 --- a/packages/SystemUI/res/values-sl/strings.xml +++ b/packages/SystemUI/res/values-sl/strings.xml @@ -498,6 +498,7 @@ <string name="stream_bluetooth_sco" msgid="2055645746402746292">"Bluetooth"</string> <string name="stream_dtmf" msgid="2447177903892477915">"Dvojna večtonska frekvenca"</string> <string name="stream_accessibility" msgid="301136219144385106">"Funkcije za ljudi s posebnimi potrebami"</string> + <string name="ring_toggle_title" msgid="3281244519428819576">"Klici"</string> <string name="volume_ringer_status_normal" msgid="4273142424125855384">"Zvonjenje"</string> <string name="volume_ringer_status_vibrate" msgid="1825615171021346557">"Vibriranje"</string> <string name="volume_ringer_status_silent" msgid="6896394161022916369">"Utišano"</string> diff --git a/packages/SystemUI/res/values-sq/strings.xml b/packages/SystemUI/res/values-sq/strings.xml index 7641e6c6d29f..665177fba318 100644 --- a/packages/SystemUI/res/values-sq/strings.xml +++ b/packages/SystemUI/res/values-sq/strings.xml @@ -492,6 +492,7 @@ <string name="stream_bluetooth_sco" msgid="2055645746402746292">"Bluetooth"</string> <string name="stream_dtmf" msgid="2447177903892477915">"Frekuenca e dyfishtë me shumë tone"</string> <string name="stream_accessibility" msgid="301136219144385106">"Qasshmëria"</string> + <string name="ring_toggle_title" msgid="3281244519428819576">"Telefonatat"</string> <string name="volume_ringer_status_normal" msgid="4273142424125855384">"Bjeri ziles"</string> <string name="volume_ringer_status_vibrate" msgid="1825615171021346557">"Dridhje"</string> <string name="volume_ringer_status_silent" msgid="6896394161022916369">"Pa zë"</string> diff --git a/packages/SystemUI/res/values-sr/strings.xml b/packages/SystemUI/res/values-sr/strings.xml index b5fab0d76028..cef0f6936ec9 100644 --- a/packages/SystemUI/res/values-sr/strings.xml +++ b/packages/SystemUI/res/values-sr/strings.xml @@ -494,6 +494,7 @@ <string name="stream_bluetooth_sco" msgid="2055645746402746292">"Bluetooth"</string> <string name="stream_dtmf" msgid="2447177903892477915">"Вишеструка фреквенција дуалног тона"</string> <string name="stream_accessibility" msgid="301136219144385106">"Приступачност"</string> + <string name="ring_toggle_title" msgid="3281244519428819576">"Позиви"</string> <string name="volume_ringer_status_normal" msgid="4273142424125855384">"Активирај звоно"</string> <string name="volume_ringer_status_vibrate" msgid="1825615171021346557">"Вибрирај"</string> <string name="volume_ringer_status_silent" msgid="6896394161022916369">"Искључи звук"</string> diff --git a/packages/SystemUI/res/values-sv/strings.xml b/packages/SystemUI/res/values-sv/strings.xml index cc6f542a0b2c..c8f8db40d9e7 100644 --- a/packages/SystemUI/res/values-sv/strings.xml +++ b/packages/SystemUI/res/values-sv/strings.xml @@ -492,6 +492,7 @@ <string name="stream_bluetooth_sco" msgid="2055645746402746292">"Bluetooth"</string> <string name="stream_dtmf" msgid="2447177903892477915">"Tonval"</string> <string name="stream_accessibility" msgid="301136219144385106">"Tillgänglighet"</string> + <string name="ring_toggle_title" msgid="3281244519428819576">"Samtal"</string> <string name="volume_ringer_status_normal" msgid="4273142424125855384">"Ringsignal"</string> <string name="volume_ringer_status_vibrate" msgid="1825615171021346557">"Vibration"</string> <string name="volume_ringer_status_silent" msgid="6896394161022916369">"Dölj"</string> diff --git a/packages/SystemUI/res/values-sw/strings.xml b/packages/SystemUI/res/values-sw/strings.xml index b83f10f24819..8fa370d9ea87 100644 --- a/packages/SystemUI/res/values-sw/strings.xml +++ b/packages/SystemUI/res/values-sw/strings.xml @@ -492,6 +492,7 @@ <string name="stream_bluetooth_sco" msgid="2055645746402746292">"Bluetooth"</string> <string name="stream_dtmf" msgid="2447177903892477915">"Masafa ya ishara ya kampuni ya simu"</string> <string name="stream_accessibility" msgid="301136219144385106">"Zana za walio na matatizo ya kuona au kusikia"</string> + <string name="ring_toggle_title" msgid="3281244519428819576">"Simu"</string> <string name="volume_ringer_status_normal" msgid="4273142424125855384">"Piga"</string> <string name="volume_ringer_status_vibrate" msgid="1825615171021346557">"Tetema"</string> <string name="volume_ringer_status_silent" msgid="6896394161022916369">"Zima sauti"</string> diff --git a/packages/SystemUI/res/values-ta/strings.xml b/packages/SystemUI/res/values-ta/strings.xml index 9d532e27e67a..6568381342c3 100644 --- a/packages/SystemUI/res/values-ta/strings.xml +++ b/packages/SystemUI/res/values-ta/strings.xml @@ -492,12 +492,11 @@ <string name="stream_bluetooth_sco" msgid="2055645746402746292">"புளூடூத்"</string> <string name="stream_dtmf" msgid="2447177903892477915">"டூயல் டோன் மல்டி ஃப்ரீக்வென்சி"</string> <string name="stream_accessibility" msgid="301136219144385106">"அணுகல்தன்மை"</string> - <!-- no translation found for volume_ringer_status_normal (4273142424125855384) --> - <skip /> - <!-- no translation found for volume_ringer_status_vibrate (1825615171021346557) --> - <skip /> - <!-- no translation found for volume_ringer_status_silent (6896394161022916369) --> + <!-- no translation found for ring_toggle_title (3281244519428819576) --> <skip /> + <string name="volume_ringer_status_normal" msgid="4273142424125855384">"ஒலி"</string> + <string name="volume_ringer_status_vibrate" msgid="1825615171021346557">"அதிர்வு"</string> + <string name="volume_ringer_status_silent" msgid="6896394161022916369">"அமைதி"</string> <string name="volume_stream_content_description_unmute" msgid="4436631538779230857">"%1$s. ஒலி இயக்க, தட்டவும்."</string> <string name="volume_stream_content_description_vibrate" msgid="1187944970457807498">"%1$s. அதிர்விற்கு அமைக்க, தட்டவும். அணுகல்தன்மை சேவைகள் ஒலியடக்கப்படக்கூடும்."</string> <string name="volume_stream_content_description_mute" msgid="3625049841390467354">"%1$s. ஒலியடக்க, தட்டவும். அணுகல்தன்மை சேவைகள் ஒலியடக்கப்படக்கூடும்."</string> @@ -762,7 +761,7 @@ <string name="instant_apps" msgid="6647570248119804907">"இன்ஸ்டண்ட் பயன்பாடுகள்"</string> <string name="instant_apps_message" msgid="8116608994995104836">"இன்ஸ்டண்ட் பயன்பாடுகளுக்கு நிறுவல் தேவையில்லை."</string> <string name="app_info" msgid="6856026610594615344">"ஆப்ஸ் தகவல்"</string> - <string name="go_to_web" msgid="2650669128861626071">"உலாவிக்குக்குச் செல்"</string> + <string name="go_to_web" msgid="2650669128861626071">"உலாவிக்குச் செல்"</string> <string name="mobile_data" msgid="7094582042819250762">"மொபைல் டேட்டா"</string> <string name="wifi_is_off" msgid="1838559392210456893">"வைஃபை முடக்கத்தில் உள்ளது"</string> <string name="bt_is_off" msgid="2640685272289706392">"புளூடூத் முடக்கத்தில் உள்ளது"</string> diff --git a/packages/SystemUI/res/values-te/strings.xml b/packages/SystemUI/res/values-te/strings.xml index f1c66f5c942d..fa7cb09b5519 100644 --- a/packages/SystemUI/res/values-te/strings.xml +++ b/packages/SystemUI/res/values-te/strings.xml @@ -492,12 +492,11 @@ <string name="stream_bluetooth_sco" msgid="2055645746402746292">"బ్లూటూత్"</string> <string name="stream_dtmf" msgid="2447177903892477915">"డ్యూయల్ మల్టీ టోన్ ఫ్రీక్వెన్సీ"</string> <string name="stream_accessibility" msgid="301136219144385106">"యాక్సెస్ సామర్థ్యం"</string> - <!-- no translation found for volume_ringer_status_normal (4273142424125855384) --> - <skip /> - <!-- no translation found for volume_ringer_status_vibrate (1825615171021346557) --> - <skip /> - <!-- no translation found for volume_ringer_status_silent (6896394161022916369) --> + <!-- no translation found for ring_toggle_title (3281244519428819576) --> <skip /> + <string name="volume_ringer_status_normal" msgid="4273142424125855384">"రింగ్"</string> + <string name="volume_ringer_status_vibrate" msgid="1825615171021346557">"వైబ్రేట్"</string> + <string name="volume_ringer_status_silent" msgid="6896394161022916369">"మ్యూట్"</string> <string name="volume_stream_content_description_unmute" msgid="4436631538779230857">"%1$s. అన్మ్యూట్ చేయడానికి నొక్కండి."</string> <string name="volume_stream_content_description_vibrate" msgid="1187944970457807498">"%1$s. వైబ్రేషన్కు సెట్ చేయడానికి నొక్కండి. యాక్సెస్ సామర్థ్య సేవలు మ్యూట్ చేయబడవచ్చు."</string> <string name="volume_stream_content_description_mute" msgid="3625049841390467354">"%1$s. మ్యూట్ చేయడానికి నొక్కండి. యాక్సెస్ సామర్థ్య సేవలు మ్యూట్ చేయబడవచ్చు."</string> diff --git a/packages/SystemUI/res/values-th/strings.xml b/packages/SystemUI/res/values-th/strings.xml index ea61018db6c2..d43efdde7a43 100644 --- a/packages/SystemUI/res/values-th/strings.xml +++ b/packages/SystemUI/res/values-th/strings.xml @@ -492,6 +492,7 @@ <string name="stream_bluetooth_sco" msgid="2055645746402746292">"บลูทูธ"</string> <string name="stream_dtmf" msgid="2447177903892477915">"การส่งสัญญาณเสียงแบบ 2 เสียงพร้อมกัน"</string> <string name="stream_accessibility" msgid="301136219144385106">"การเข้าถึง"</string> + <string name="ring_toggle_title" msgid="3281244519428819576">"การโทร"</string> <string name="volume_ringer_status_normal" msgid="4273142424125855384">"ทำให้ส่งเสียง"</string> <string name="volume_ringer_status_vibrate" msgid="1825615171021346557">"สั่น"</string> <string name="volume_ringer_status_silent" msgid="6896394161022916369">"ปิดเสียง"</string> diff --git a/packages/SystemUI/res/values-tl/strings.xml b/packages/SystemUI/res/values-tl/strings.xml index cdf28d6e5348..4aa9ca4bc408 100644 --- a/packages/SystemUI/res/values-tl/strings.xml +++ b/packages/SystemUI/res/values-tl/strings.xml @@ -492,6 +492,7 @@ <string name="stream_bluetooth_sco" msgid="2055645746402746292">"Bluetooth"</string> <string name="stream_dtmf" msgid="2447177903892477915">"Dual multi tone frequency"</string> <string name="stream_accessibility" msgid="301136219144385106">"Pagiging Naa-access"</string> + <string name="ring_toggle_title" msgid="3281244519428819576">"Mga Tawag"</string> <string name="volume_ringer_status_normal" msgid="4273142424125855384">"Ipa-ring"</string> <string name="volume_ringer_status_vibrate" msgid="1825615171021346557">"I-vibrate"</string> <string name="volume_ringer_status_silent" msgid="6896394161022916369">"I-mute"</string> diff --git a/packages/SystemUI/res/values-tr/strings.xml b/packages/SystemUI/res/values-tr/strings.xml index 5dc81a1d7eaf..d028fdd6ac08 100644 --- a/packages/SystemUI/res/values-tr/strings.xml +++ b/packages/SystemUI/res/values-tr/strings.xml @@ -492,6 +492,7 @@ <string name="stream_bluetooth_sco" msgid="2055645746402746292">"Bluetooth"</string> <string name="stream_dtmf" msgid="2447177903892477915">"Çift ton çoklu frekans"</string> <string name="stream_accessibility" msgid="301136219144385106">"Erişilebilirlik"</string> + <string name="ring_toggle_title" msgid="3281244519428819576">"Çağrılar"</string> <string name="volume_ringer_status_normal" msgid="4273142424125855384">"Zili çaldır"</string> <string name="volume_ringer_status_vibrate" msgid="1825615171021346557">"Titreşim"</string> <string name="volume_ringer_status_silent" msgid="6896394161022916369">"Sesi kapat"</string> diff --git a/packages/SystemUI/res/values-ur/strings.xml b/packages/SystemUI/res/values-ur/strings.xml index 64980887907f..73fd588616e9 100644 --- a/packages/SystemUI/res/values-ur/strings.xml +++ b/packages/SystemUI/res/values-ur/strings.xml @@ -492,12 +492,11 @@ <string name="stream_bluetooth_sco" msgid="2055645746402746292">"بلوٹوتھ"</string> <string name="stream_dtmf" msgid="2447177903892477915">"دوہری ملٹی ٹون فریکوئنسی"</string> <string name="stream_accessibility" msgid="301136219144385106">"ایکسیسبیلٹی"</string> - <!-- no translation found for volume_ringer_status_normal (4273142424125855384) --> - <skip /> - <!-- no translation found for volume_ringer_status_vibrate (1825615171021346557) --> - <skip /> - <!-- no translation found for volume_ringer_status_silent (6896394161022916369) --> + <!-- no translation found for ring_toggle_title (3281244519428819576) --> <skip /> + <string name="volume_ringer_status_normal" msgid="4273142424125855384">"رِنگ کریں"</string> + <string name="volume_ringer_status_vibrate" msgid="1825615171021346557">"وائبریٹ"</string> + <string name="volume_ringer_status_silent" msgid="6896394161022916369">"خاموش کریں"</string> <string name="volume_stream_content_description_unmute" msgid="4436631538779230857">"%1$s۔ آواز چالو کرنے کیلئے تھپتھپائیں۔"</string> <string name="volume_stream_content_description_vibrate" msgid="1187944970457807498">"%1$s۔ ارتعاش پر سیٹ کرنے کیلئے تھپتھپائیں۔ ایکسیسبیلٹی سروسز شاید خاموش ہوں۔"</string> <string name="volume_stream_content_description_mute" msgid="3625049841390467354">"%1$s۔ خاموش کرنے کیلئے تھپتھپائیں۔ ایکسیسبیلٹی سروسز شاید خاموش ہوں۔"</string> diff --git a/packages/SystemUI/res/values-uz/strings.xml b/packages/SystemUI/res/values-uz/strings.xml index 273079d7dcc2..55eb4fb51ee1 100644 --- a/packages/SystemUI/res/values-uz/strings.xml +++ b/packages/SystemUI/res/values-uz/strings.xml @@ -494,6 +494,7 @@ <string name="stream_bluetooth_sco" msgid="2055645746402746292">"Bluetooth"</string> <string name="stream_dtmf" msgid="2447177903892477915">"Ikkitali ko‘pchastotali ovoz"</string> <string name="stream_accessibility" msgid="301136219144385106">"Maxsus imkoniyatlar"</string> + <string name="ring_toggle_title" msgid="3281244519428819576">"Chaqiruvlar"</string> <string name="volume_ringer_status_normal" msgid="4273142424125855384">"Jiringlatish"</string> <string name="volume_ringer_status_vibrate" msgid="1825615171021346557">"Tebranish"</string> <string name="volume_ringer_status_silent" msgid="6896394161022916369">"Ovozsiz"</string> diff --git a/packages/SystemUI/res/values-vi/strings.xml b/packages/SystemUI/res/values-vi/strings.xml index df062984e2d3..551decf2dcb6 100644 --- a/packages/SystemUI/res/values-vi/strings.xml +++ b/packages/SystemUI/res/values-vi/strings.xml @@ -492,6 +492,7 @@ <string name="stream_bluetooth_sco" msgid="2055645746402746292">"Bluetooth"</string> <string name="stream_dtmf" msgid="2447177903892477915">"Tần số đa chuông kép"</string> <string name="stream_accessibility" msgid="301136219144385106">"Trợ năng"</string> + <string name="ring_toggle_title" msgid="3281244519428819576">"Cuộc gọi"</string> <string name="volume_ringer_status_normal" msgid="4273142424125855384">"Đổ chuông"</string> <string name="volume_ringer_status_vibrate" msgid="1825615171021346557">"Rung"</string> <string name="volume_ringer_status_silent" msgid="6896394161022916369">"Tắt tiếng"</string> diff --git a/packages/SystemUI/res/values-zh-rCN/strings.xml b/packages/SystemUI/res/values-zh-rCN/strings.xml index f7999a6d9789..2b64d8685292 100644 --- a/packages/SystemUI/res/values-zh-rCN/strings.xml +++ b/packages/SystemUI/res/values-zh-rCN/strings.xml @@ -492,6 +492,8 @@ <string name="stream_bluetooth_sco" msgid="2055645746402746292">"蓝牙"</string> <string name="stream_dtmf" msgid="2447177903892477915">"双音多频"</string> <string name="stream_accessibility" msgid="301136219144385106">"无障碍"</string> + <!-- no translation found for ring_toggle_title (3281244519428819576) --> + <skip /> <string name="volume_ringer_status_normal" msgid="4273142424125855384">"响铃"</string> <string name="volume_ringer_status_vibrate" msgid="1825615171021346557">"振动"</string> <string name="volume_ringer_status_silent" msgid="6896394161022916369">"静音"</string> diff --git a/packages/SystemUI/res/values-zh-rHK/strings.xml b/packages/SystemUI/res/values-zh-rHK/strings.xml index 188ad7cae870..382425d1a083 100644 --- a/packages/SystemUI/res/values-zh-rHK/strings.xml +++ b/packages/SystemUI/res/values-zh-rHK/strings.xml @@ -494,6 +494,7 @@ <string name="stream_bluetooth_sco" msgid="2055645746402746292">"藍牙"</string> <string name="stream_dtmf" msgid="2447177903892477915">"雙音多頻訊號"</string> <string name="stream_accessibility" msgid="301136219144385106">"無障礙功能"</string> + <string name="ring_toggle_title" msgid="3281244519428819576">"通話"</string> <string name="volume_ringer_status_normal" msgid="4273142424125855384">"鈴聲"</string> <string name="volume_ringer_status_vibrate" msgid="1825615171021346557">"震動"</string> <string name="volume_ringer_status_silent" msgid="6896394161022916369">"靜音"</string> diff --git a/packages/SystemUI/res/values-zh-rTW/strings.xml b/packages/SystemUI/res/values-zh-rTW/strings.xml index 75f8015fda5a..ef9ea43065be 100644 --- a/packages/SystemUI/res/values-zh-rTW/strings.xml +++ b/packages/SystemUI/res/values-zh-rTW/strings.xml @@ -492,6 +492,7 @@ <string name="stream_bluetooth_sco" msgid="2055645746402746292">"藍牙"</string> <string name="stream_dtmf" msgid="2447177903892477915">"雙音多頻"</string> <string name="stream_accessibility" msgid="301136219144385106">"協助工具"</string> + <string name="ring_toggle_title" msgid="3281244519428819576">"通話"</string> <string name="volume_ringer_status_normal" msgid="4273142424125855384">"鈴聲"</string> <string name="volume_ringer_status_vibrate" msgid="1825615171021346557">"震動"</string> <string name="volume_ringer_status_silent" msgid="6896394161022916369">"靜音"</string> diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java index d58b69deb2df..2058f15bdcc6 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java @@ -71,6 +71,7 @@ import android.util.Log; import android.util.SparseBooleanArray; import android.util.SparseIntArray; +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.telephony.IccCardConstants; import com.android.internal.telephony.IccCardConstants.State; import com.android.internal.telephony.PhoneConstants; @@ -1111,7 +1112,8 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener { } } - private KeyguardUpdateMonitor(Context context) { + @VisibleForTesting + protected KeyguardUpdateMonitor(Context context) { mContext = context; mSubscriptionManager = SubscriptionManager.from(context); mDeviceProvisioned = isDeviceProvisionedInSettingsDb(); diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java index e80d6d34b1dc..3177c03d1afa 100644 --- a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java +++ b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java @@ -16,6 +16,7 @@ package com.android.systemui; +import android.app.AlarmManager; import android.content.Context; import android.util.ArrayMap; import android.util.Log; @@ -92,10 +93,10 @@ public class SystemUIFactory { public ScrimController createScrimController(LightBarController lightBarController, ScrimView scrimBehind, ScrimView scrimInFront, View headsUpScrim, - LockscreenWallpaper lockscreenWallpaper, Consumer<Boolean> scrimVisibleListener, - DozeParameters dozeParameters) { + LockscreenWallpaper lockscreenWallpaper, Consumer<Integer> scrimVisibleListener, + DozeParameters dozeParameters, AlarmManager alarmManager) { return new ScrimController(lightBarController, scrimBehind, scrimInFront, headsUpScrim, - scrimVisibleListener, dozeParameters); + scrimVisibleListener, dozeParameters, alarmManager); } public NotificationIconAreaController createNotificationIconAreaController(Context context, diff --git a/packages/SystemUI/src/com/android/systemui/doze/AlwaysOnDisplayPolicy.java b/packages/SystemUI/src/com/android/systemui/doze/AlwaysOnDisplayPolicy.java index cc2244a4e934..8515bf2e2968 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/AlwaysOnDisplayPolicy.java +++ b/packages/SystemUI/src/com/android/systemui/doze/AlwaysOnDisplayPolicy.java @@ -40,12 +40,16 @@ public class AlwaysOnDisplayPolicy { private static final long DEFAULT_PROX_SCREEN_OFF_DELAY_MS = 10 * DateUtils.SECOND_IN_MILLIS; private static final long DEFAULT_PROX_COOLDOWN_TRIGGER_MS = 2 * DateUtils.SECOND_IN_MILLIS; private static final long DEFAULT_PROX_COOLDOWN_PERIOD_MS = 5 * DateUtils.SECOND_IN_MILLIS; + private static final long DEFAULT_WALLPAPER_VISIBILITY_MS = 60 * DateUtils.SECOND_IN_MILLIS; + private static final long DEFAULT_WALLPAPER_FADE_OUT_MS = 400; static final String KEY_SCREEN_BRIGHTNESS_ARRAY = "screen_brightness_array"; static final String KEY_DIMMING_SCRIM_ARRAY = "dimming_scrim_array"; static final String KEY_PROX_SCREEN_OFF_DELAY_MS = "prox_screen_off_delay"; static final String KEY_PROX_COOLDOWN_TRIGGER_MS = "prox_cooldown_trigger"; static final String KEY_PROX_COOLDOWN_PERIOD_MS = "prox_cooldown_period"; + static final String KEY_WALLPAPER_VISIBILITY_MS = "wallpaper_visibility_timeout"; + static final String KEY_WALLPAPER_FADE_OUT_MS = "wallpaper_fade_out_duration"; /** * Integer array to map ambient brightness type to real screen brightness. @@ -89,6 +93,24 @@ public class AlwaysOnDisplayPolicy { */ public long proxCooldownPeriodMs; + /** + * For how long(ms) the wallpaper should still be visible + * after entering AoD. + * + * @see Settings.Global#ALWAYS_ON_DISPLAY_CONSTANTS + * @see #KEY_WALLPAPER_VISIBILITY_MS + */ + public long wallpaperVisibilityDuration; + + /** + * Duration(ms) of the fade out animation after + * {@link #KEY_WALLPAPER_VISIBILITY_MS} elapses. + * + * @see Settings.Global#ALWAYS_ON_DISPLAY_CONSTANTS + * @see #KEY_WALLPAPER_FADE_OUT_MS + */ + public long wallpaperFadeOutDuration; + private final KeyValueListParser mParser; private final Context mContext; private SettingsObserver mSettingsObserver; @@ -138,6 +160,10 @@ public class AlwaysOnDisplayPolicy { DEFAULT_PROX_COOLDOWN_TRIGGER_MS); proxCooldownPeriodMs = mParser.getLong(KEY_PROX_COOLDOWN_PERIOD_MS, DEFAULT_PROX_COOLDOWN_PERIOD_MS); + wallpaperFadeOutDuration = mParser.getLong(KEY_WALLPAPER_FADE_OUT_MS, + DEFAULT_WALLPAPER_FADE_OUT_MS); + wallpaperVisibilityDuration = mParser.getLong(KEY_WALLPAPER_VISIBILITY_MS, + DEFAULT_WALLPAPER_VISIBILITY_MS); screenBrightnessArray = mParser.getIntArray(KEY_SCREEN_BRIGHTNESS_ARRAY, resources.getIntArray( R.array.config_doze_brightness_sensor_to_brightness)); diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java b/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java index a409fcb4504a..0f0402d0b3d3 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java @@ -66,6 +66,7 @@ public class DozeFactory { createDozeUi(context, host, wakeLock, machine, handler, alarmManager, params), new DozeScreenState(wrappedService, handler), createDozeScreenBrightness(context, wrappedService, sensorManager, host, handler), + new DozeWallpaperState() }); return machine; diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeService.java b/packages/SystemUI/src/com/android/systemui/doze/DozeService.java index 6650cc634700..34d392861d7c 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeService.java +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeService.java @@ -24,9 +24,10 @@ import android.util.Log; import com.android.systemui.Dependency; import com.android.systemui.plugins.DozeServicePlugin; -import com.android.systemui.plugins.PluginManager; import com.android.systemui.plugins.DozeServicePlugin.RequestDoze; import com.android.systemui.plugins.PluginListener; +import com.android.systemui.plugins.PluginManager; + import java.io.FileDescriptor; import java.io.PrintWriter; diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeWallpaperState.java b/packages/SystemUI/src/com/android/systemui/doze/DozeWallpaperState.java new file mode 100644 index 000000000000..50c1ede80313 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeWallpaperState.java @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.systemui.doze; + +import android.app.IWallpaperManager; +import android.content.Context; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.util.Log; + +import com.android.internal.annotations.VisibleForTesting; + +import java.io.PrintWriter; + +/** + * Propagates doze state to wallpaper engine. + */ +public class DozeWallpaperState implements DozeMachine.Part { + + private static final String TAG = "DozeWallpaperState"; + private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); + + @VisibleForTesting + final IWallpaperManager mWallpaperManagerService; + private boolean mIsAmbientMode; + + public DozeWallpaperState() { + this(IWallpaperManager.Stub.asInterface( + ServiceManager.getService(Context.WALLPAPER_SERVICE))); + } + + @VisibleForTesting + DozeWallpaperState(IWallpaperManager wallpaperManagerService) { + mWallpaperManagerService = wallpaperManagerService; + } + + @Override + public void transitionTo(DozeMachine.State oldState, DozeMachine.State newState) { + final boolean isAmbientMode; + switch (newState) { + case DOZE_AOD: + case DOZE_REQUEST_PULSE: + case DOZE_PULSING: + case DOZE_PULSE_DONE: + isAmbientMode = true; + break; + default: + isAmbientMode = false; + } + + if (isAmbientMode != mIsAmbientMode) { + mIsAmbientMode = isAmbientMode; + try { + Log.i(TAG, "AoD wallpaper state changed to: " + mIsAmbientMode); + mWallpaperManagerService.setInAmbientMode(mIsAmbientMode); + } catch (RemoteException e) { + // Cannot notify wallpaper manager service, but it's fine, let's just skip it. + Log.w(TAG, "Cannot notify state to WallpaperManagerService: " + mIsAmbientMode); + } + } + } + + @Override + public void dump(PrintWriter pw) { + pw.println("DozeWallpaperState:"); + pw.println(" isAmbientMode: " + mIsAmbientMode); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java index 50cbd69e31c8..6d85fb37e374 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java @@ -26,6 +26,7 @@ import android.util.SparseBooleanArray; import com.android.internal.hardware.AmbientDisplayConfiguration; import com.android.systemui.R; +import com.android.systemui.doze.AlwaysOnDisplayPolicy; import java.io.PrintWriter; @@ -37,10 +38,12 @@ public class DozeParameters { private final AmbientDisplayConfiguration mAmbientDisplayConfiguration; private static IntInOutMatcher sPickupSubtypePerformsProxMatcher; + private final AlwaysOnDisplayPolicy mAlwaysOnPolicy; public DozeParameters(Context context) { mContext = context; mAmbientDisplayConfiguration = new AmbientDisplayConfiguration(mContext); + mAlwaysOnPolicy = new AlwaysOnDisplayPolicy(context); } public void dump(PrintWriter pw) { @@ -83,6 +86,11 @@ public class DozeParameters { return getPulseInDuration() + getPulseVisibleDuration() + getPulseOutDuration(); } + public float getScreenBrightnessDoze() { + return mContext.getResources().getInteger( + com.android.internal.R.integer.config_screenBrightnessDoze) / 255f; + } + public int getPulseInDuration() { return getInt("doze.pulse.duration.in", R.integer.doze_pulse_duration_in); } @@ -115,6 +123,26 @@ public class DozeParameters { return getInt("doze.pickup.vibration.threshold", R.integer.doze_pickup_vibration_threshold); } + /** + * For how long a wallpaper can be visible in AoD before it fades aways. + * @return duration in millis. + */ + public long getWallpaperAodDuration() { + return mAlwaysOnPolicy.wallpaperVisibilityDuration; + } + + /** + * How long it takes for the wallpaper fade away (Animation duration.) + * @return duration in millis. + */ + public long getWallpaperFadeOutDuration() { + return mAlwaysOnPolicy.wallpaperFadeOutDuration; + } + + /** + * Checks if always on is available and enabled for the current user. + * @return {@code true} if enabled and available. + */ public boolean getAlwaysOn() { return mAmbientDisplayConfiguration.alwaysOnEnabled(UserHandle.USER_CURRENT); } @@ -123,7 +151,7 @@ public class DozeParameters { * Some screens need to be completely black before changing the display power mode, * unexpected behavior might happen if this parameter isn't respected. * - * @return true if screen needs to be completely black before a power transition. + * @return {@code true} if screen needs to be completely black before a power transition. */ public boolean getDisplayNeedsBlanking() { return mContext.getResources().getBoolean( @@ -134,7 +162,7 @@ public class DozeParameters { * Whether we can implement our own screen off animation or if we need * to rely on DisplayPowerManager to dim the display. * - * @return true if SystemUI can control the screen off animation. + * @return {@code true} if SystemUI can control the screen off animation. */ public boolean getCanControlScreenOffAnimation() { return !mContext.getResources().getBoolean( diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java index 168758f64b60..8abc3f4789e8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java @@ -20,6 +20,7 @@ import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.PropertyValuesHolder; import android.animation.ValueAnimator; +import android.app.AlarmManager; import android.app.WallpaperManager; import android.content.Context; import android.graphics.Color; @@ -51,6 +52,7 @@ import com.android.systemui.statusbar.NotificationData; import com.android.systemui.statusbar.ScrimView; import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener; import com.android.systemui.statusbar.stack.ViewState; +import com.android.systemui.util.AlarmTimeout; import com.android.systemui.util.wakelock.DelayedWakeLock; import com.android.systemui.util.wakelock.WakeLock; @@ -73,6 +75,19 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, = new PathInterpolator(0f, 0, 0.7f, 1f); public static final Interpolator KEYGUARD_FADE_OUT_INTERPOLATOR_LOCKED = new PathInterpolator(0.3f, 0f, 0.8f, 1f); + + /** + * When both scrims have 0 alpha. + */ + public static final int VISIBILITY_FULLY_TRANSPARENT = 0; + /** + * When scrims aren't transparent (alpha 0) but also not opaque (alpha 1.) + */ + public static final int VISIBILITY_SEMI_TRANSPARENT = 1; + /** + * When at least 1 scrim is fully opaque (alpha set to 1.) + */ + public static final int VISIBILITY_FULLY_OPAQUE = 2; /** * Default alpha value for most scrims. */ @@ -111,6 +126,7 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, private final UnlockMethodCache mUnlockMethodCache; private final KeyguardUpdateMonitor mKeyguardUpdateMonitor; private final DozeParameters mDozeParameters; + private final AlarmTimeout mTimeTicker; private final SysuiColorExtractor mColorExtractor; private GradientColors mLockColors; @@ -138,23 +154,25 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, private float mCurrentBehindAlpha = NOT_INITIALIZED; private int mCurrentInFrontTint; private int mCurrentBehindTint; + private boolean mWallpaperVisibilityTimedOut; private int mPinnedHeadsUpCount; private float mTopHeadsUpDragAmount; private View mDraggedHeadsUpView; private boolean mKeyguardFadingOutInProgress; private ValueAnimator mKeyguardFadeoutAnimation; - private boolean mScrimsVisible; - private final Consumer<Boolean> mScrimVisibleListener; + private int mScrimsVisibility; + private final Consumer<Integer> mScrimVisibleListener; private boolean mBlankScreen; private boolean mScreenBlankingCallbackCalled; private Callback mCallback; + private boolean mWallpaperSupportsAmbientMode; private final WakeLock mWakeLock; private boolean mWakeLockHeld; public ScrimController(LightBarController lightBarController, ScrimView scrimBehind, - ScrimView scrimInFront, View headsUpScrim, Consumer<Boolean> scrimVisibleListener, - DozeParameters dozeParameters) { + ScrimView scrimInFront, View headsUpScrim, Consumer<Integer> scrimVisibleListener, + DozeParameters dozeParameters, AlarmManager alarmManager) { mScrimBehind = scrimBehind; mScrimInFront = scrimInFront; mHeadsUpScrim = headsUpScrim; @@ -164,6 +182,8 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, mKeyguardUpdateMonitor = KeyguardUpdateMonitor.getInstance(mContext); mLightBarController = lightBarController; mScrimBehindAlphaResValue = mContext.getResources().getFloat(R.dimen.scrim_behind_alpha); + mTimeTicker = new AlarmTimeout(alarmManager, this::onHideWallpaperTimeout, + "hide_aod_wallpaper", new Handler()); mWakeLock = createWakeLock(); // Scrim alpha is initially set to the value on the resource but might be changed // to make sure that text on top of it is legible. @@ -236,13 +256,18 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, } // Do not let the device sleep until we're done with all animations - if (!mWakeLockHeld) { - if (mWakeLock != null) { - mWakeLockHeld = true; - mWakeLock.acquire(); - } else { - Log.w(TAG, "Cannot hold wake lock, it has not been set yet"); + holdWakeLock(); + + // AOD wallpapers should fade away after a while + if (mWallpaperSupportsAmbientMode && mDozeParameters.getAlwaysOn() + && (mState == ScrimState.AOD || mState == ScrimState.PULSING)) { + if (!mWallpaperVisibilityTimedOut) { + mTimeTicker.schedule(mDozeParameters.getWallpaperAodDuration(), + AlarmTimeout.MODE_IGNORE_IF_SCHEDULED); } + } else { + mTimeTicker.cancel(); + mWallpaperVisibilityTimedOut = false; } if (!mKeyguardUpdateMonitor.needsSlowUnlockTransition()) { @@ -279,6 +304,30 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, mTracking = false; } + @VisibleForTesting + protected void onHideWallpaperTimeout() { + if (mState != ScrimState.AOD && mState != ScrimState.PULSING) { + return; + } + + holdWakeLock(); + mWallpaperVisibilityTimedOut = true; + mAnimateChange = true; + mAnimationDuration = mDozeParameters.getWallpaperFadeOutDuration(); + scheduleUpdate(); + } + + private void holdWakeLock() { + if (!mWakeLockHeld) { + if (mWakeLock != null) { + mWakeLockHeld = true; + mWakeLock.acquire(); + } else { + Log.w(TAG, "Cannot hold wake lock, it has not been set yet"); + } + } + } + /** * Current state of the shade expansion when pulling it from the top. * This value is 1 when on top of the keyguard and goes to 0 as the user drags up. @@ -391,6 +440,14 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, mLightBarController.setScrimColor(mScrimInFront.getColors()); } + // We want to override the back scrim opacity for AOD and PULSING + // when it's time to fade the wallpaper away. + boolean overrideBackScrimAlpha = (mState == ScrimState.PULSING || mState == ScrimState.AOD) + && mWallpaperVisibilityTimedOut; + if (overrideBackScrimAlpha) { + mCurrentBehindAlpha = 1; + } + setScrimInFrontAlpha(mCurrentInFrontAlpha); setScrimBehindAlpha(mCurrentBehindAlpha); @@ -398,12 +455,18 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, } private void dispatchScrimsVisible() { - boolean scrimsVisible = mScrimBehind.getViewAlpha() > 0 || mScrimInFront.getViewAlpha() > 0; - - if (mScrimsVisible != scrimsVisible) { - mScrimsVisible = scrimsVisible; + final int currentScrimVisibility; + if (mScrimInFront.getViewAlpha() == 1 || mScrimBehind.getViewAlpha() == 1) { + currentScrimVisibility = VISIBILITY_FULLY_OPAQUE; + } else if (mScrimInFront.getViewAlpha() == 0 && mScrimBehind.getViewAlpha() == 0) { + currentScrimVisibility = VISIBILITY_FULLY_TRANSPARENT; + } else { + currentScrimVisibility = VISIBILITY_SEMI_TRANSPARENT; + } - mScrimVisibleListener.accept(scrimsVisible); + if (mScrimsVisibility != currentScrimVisibility) { + mScrimsVisibility = currentScrimVisibility; + mScrimVisibleListener.accept(currentScrimVisibility); } } @@ -811,6 +874,14 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, pw.print(" mTracking="); pw.println(mTracking); } + public void setWallpaperSupportsAmbientMode(boolean wallpaperSupportsAmbientMode) { + mWallpaperSupportsAmbientMode = wallpaperSupportsAmbientMode; + ScrimState[] states = ScrimState.values(); + for (int i = 0; i < states.length; i++) { + states[i].setWallpaperSupportsAmbientMode(wallpaperSupportsAmbientMode); + } + } + public interface Callback { default void onStart() { } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java index c33cc509e816..fa2c1b3fa01c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java @@ -19,6 +19,7 @@ package com.android.systemui.statusbar.phone; import android.graphics.Color; import android.os.Trace; +import com.android.keyguard.KeyguardUpdateMonitor; import com.android.systemui.statusbar.ScrimView; import com.android.systemui.statusbar.stack.StackStateAnimator; @@ -89,8 +90,10 @@ public enum ScrimState { updateScrimColor(mScrimInFront, 1, Color.BLACK); } final boolean alwaysOnEnabled = mDozeParameters.getAlwaysOn(); - mBlankScreen = previousState == ScrimState.PULSING; - mCurrentBehindAlpha = 1; + final boolean wasPulsing = previousState == ScrimState.PULSING; + mBlankScreen = wasPulsing && !mCanControlScreenOff; + mCurrentBehindAlpha = mWallpaperSupportsAmbientMode + && !mKeyguardUpdateMonitor.hasLockscreenWallpaper() ? 0f : 1f; mCurrentInFrontAlpha = alwaysOnEnabled ? mAodFrontScrimAlpha : 1f; mCurrentInFrontTint = Color.BLACK; mCurrentBehindTint = Color.BLACK; @@ -106,9 +109,10 @@ public enum ScrimState { PULSING { @Override public void prepare(ScrimState previousState) { - mCurrentBehindAlpha = 1; mCurrentInFrontAlpha = 0; mCurrentInFrontTint = Color.BLACK; + mCurrentBehindAlpha = mWallpaperSupportsAmbientMode + && !mKeyguardUpdateMonitor.hasLockscreenWallpaper() ? 0f : 1f; mCurrentBehindTint = Color.BLACK; mBlankScreen = mDisplayRequiresBlanking; if (mDisplayRequiresBlanking) { @@ -158,6 +162,8 @@ public enum ScrimState { DozeParameters mDozeParameters; boolean mDisplayRequiresBlanking; boolean mCanControlScreenOff; + boolean mWallpaperSupportsAmbientMode; + KeyguardUpdateMonitor mKeyguardUpdateMonitor; public void init(ScrimView scrimInFront, ScrimView scrimBehind, DozeParameters dozeParameters) { mScrimInFront = scrimInFront; @@ -165,6 +171,7 @@ public enum ScrimState { mDozeParameters = dozeParameters; mDisplayRequiresBlanking = dozeParameters.getDisplayNeedsBlanking(); mCanControlScreenOff = dozeParameters.getCanControlScreenOffAnimation(); + mKeyguardUpdateMonitor = KeyguardUpdateMonitor.getInstance(scrimInFront.getContext()); } public void prepare(ScrimState previousState) { @@ -218,4 +225,8 @@ public enum ScrimState { public void setScrimBehindAlphaKeyguard(float scrimBehindAlphaKeyguard) { mScrimBehindAlphaKeyguard = scrimBehindAlphaKeyguard; } + + public void setWallpaperSupportsAmbientMode(boolean wallpaperSupportsAmbientMode) { + mWallpaperSupportsAmbientMode = wallpaperSupportsAmbientMode; + } }
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java index fecd6bd4b9ec..c5349d186b86 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java @@ -46,6 +46,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.app.ActivityManager; import android.app.ActivityOptions; +import android.app.AlarmManager; import android.app.KeyguardManager; import android.app.Notification; import android.app.NotificationManager; @@ -53,6 +54,7 @@ import android.app.PendingIntent; import android.app.StatusBarManager; import android.app.TaskStackBuilder; import android.app.WallpaperColors; +import android.app.WallpaperInfo; import android.app.WallpaperManager; import android.app.admin.DevicePolicyManager; import android.content.BroadcastReceiver; @@ -249,7 +251,6 @@ import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Stack; -import java.util.function.Function; public class StatusBar extends SystemUI implements DemoMode, DragDownHelper.DragDownCallback, ActivityStarter, OnUnlockMethodChangedListener, @@ -526,7 +527,22 @@ public class StatusBar extends SystemUI implements DemoMode, protected NotificationLockscreenUserManager mLockscreenUserManager; protected NotificationRemoteInputManager mRemoteInputManager; + private BroadcastReceiver mWallpaperChangedReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + WallpaperManager wallpaperManager = context.getSystemService(WallpaperManager.class); + if (wallpaperManager == null) { + Log.w(TAG, "WallpaperManager not available"); + return; + } + WallpaperInfo info = wallpaperManager.getWallpaperInfo(); + final boolean supportsAmbientMode = info != null && + info.getSupportsAmbientMode(); + mStatusBarWindowManager.setWallpaperSupportsAmbientMode(supportsAmbientMode); + mScrimController.setWallpaperSupportsAmbientMode(supportsAmbientMode); + } + }; private Runnable mLaunchTransitionEndRunnable; protected boolean mLaunchTransitionFadingAway; @@ -704,6 +720,11 @@ public class StatusBar extends SystemUI implements DemoMode, createAndAddWindows(); + // Make sure we always have the most current wallpaper info. + IntentFilter wallpaperChangedFilter = new IntentFilter(Intent.ACTION_WALLPAPER_CHANGED); + mContext.registerReceiver(mWallpaperChangedReceiver, wallpaperChangedFilter); + mWallpaperChangedReceiver.onReceive(mContext, null); + mCommandQueue.disable(switches[0], switches[6], false /* animate */); setSystemUiVisibility(switches[1], switches[7], switches[8], 0xffffffff, fullscreenStackBounds, dockedStackBounds); @@ -922,9 +943,9 @@ public class StatusBar extends SystemUI implements DemoMode, scrimBehind, scrimInFront, headsUpScrim, mLockscreenWallpaper, scrimsVisible -> { if (mStatusBarWindowManager != null) { - mStatusBarWindowManager.setScrimsVisible(scrimsVisible); + mStatusBarWindowManager.setScrimsVisibility(scrimsVisible); } - }, new DozeParameters(mContext)); + }, new DozeParameters(mContext), mContext.getSystemService(AlarmManager.class)); if (mScrimSrcModeEnabled) { Runnable runnable = () -> { boolean asSrc = mBackdrop.getVisibility() != View.VISIBLE; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowManager.java index b0b5b8ec1e09..c30f6339f8da 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowManager.java @@ -54,6 +54,7 @@ public class StatusBarWindowManager implements RemoteInputController.Callback, D private final Context mContext; private final WindowManager mWindowManager; private final IActivityManager mActivityManager; + private final DozeParameters mDozeParameters; private View mStatusBarView; private WindowManager.LayoutParams mLp; private WindowManager.LayoutParams mLpChanged; @@ -70,8 +71,8 @@ public class StatusBarWindowManager implements RemoteInputController.Callback, D mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); mActivityManager = ActivityManager.getService(); mKeyguardScreenRotation = shouldEnableKeyguardScreenRotation(); - mScreenBrightnessDoze = mContext.getResources().getInteger( - com.android.internal.R.integer.config_screenBrightnessDoze) / 255f; + mDozeParameters = new DozeParameters(mContext); + mScreenBrightnessDoze = mDozeParameters.getScreenBrightnessDoze(); } private boolean shouldEnableKeyguardScreenRotation() { @@ -136,7 +137,11 @@ public class StatusBarWindowManager implements RemoteInputController.Callback, D mLpChanged.privateFlags &= ~WindowManager.LayoutParams.PRIVATE_FLAG_KEYGUARD; } - if (state.keyguardShowing && !state.backdropShowing && !state.dozing) { + final boolean showWallpaperOnAod = mDozeParameters.getAlwaysOn() && + state.wallpaperSupportsAmbientMode && + state.scrimsVisibility != ScrimController.VISIBILITY_FULLY_OPAQUE; + if (state.keyguardShowing && !state.backdropShowing && + (!state.dozing || showWallpaperOnAod)) { mLpChanged.flags |= WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER; } else { mLpChanged.flags &= ~WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER; @@ -188,7 +193,8 @@ public class StatusBarWindowManager implements RemoteInputController.Callback, D private boolean isExpanded(State state) { return !state.forceCollapsed && (state.isKeyguardShowingAndNotOccluded() || state.panelVisible || state.keyguardFadingAway || state.bouncerShowing - || state.headsUpShowing || state.scrimsVisible); + || state.headsUpShowing + || state.scrimsVisibility != ScrimController.VISIBILITY_FULLY_TRANSPARENT); } private void applyFitsSystemWindows(State state) { @@ -336,8 +342,8 @@ public class StatusBarWindowManager implements RemoteInputController.Callback, D apply(mCurrentState); } - public void setScrimsVisible(boolean scrimsVisible) { - mCurrentState.scrimsVisible = scrimsVisible; + public void setScrimsVisibility(int scrimsVisibility) { + mCurrentState.scrimsVisibility = scrimsVisibility; apply(mCurrentState); } @@ -346,6 +352,11 @@ public class StatusBarWindowManager implements RemoteInputController.Callback, D apply(mCurrentState); } + public void setWallpaperSupportsAmbientMode(boolean supportsAmbientMode) { + mCurrentState.wallpaperSupportsAmbientMode = supportsAmbientMode; + apply(mCurrentState); + } + /** * @param state The {@link StatusBarState} of the status bar. */ @@ -433,6 +444,7 @@ public class StatusBarWindowManager implements RemoteInputController.Callback, D boolean forceDozeBrightness; boolean forceUserActivity; boolean backdropShowing; + boolean wallpaperSupportsAmbientMode; /** * The {@link StatusBar} state from the status bar. @@ -442,7 +454,7 @@ public class StatusBarWindowManager implements RemoteInputController.Callback, D boolean remoteInputActive; boolean forcePluginOpen; boolean dozing; - boolean scrimsVisible; + int scrimsVisibility; private boolean isKeyguardShowingAndNotOccluded() { return keyguardShowing && !keyguardOccluded; diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeWallpaperStateTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeWallpaperStateTest.java new file mode 100644 index 000000000000..8e7f83da6203 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeWallpaperStateTest.java @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.systemui.doze; + +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +import android.app.IWallpaperManager; +import android.os.Handler; +import android.os.RemoteException; +import android.support.test.filters.SmallTest; + +import com.android.systemui.SysuiTestCase; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +@SmallTest +public class DozeWallpaperStateTest extends SysuiTestCase { + + @Test + public void testDreamNotification() throws RemoteException { + IWallpaperManager wallpaperManagerService = mock(IWallpaperManager.class); + DozeWallpaperState dozeWallpaperState = new DozeWallpaperState(wallpaperManagerService); + dozeWallpaperState.transitionTo(DozeMachine.State.UNINITIALIZED, + DozeMachine.State.DOZE_AOD); + verify(wallpaperManagerService).setInAmbientMode(eq(true)); + dozeWallpaperState.transitionTo(DozeMachine.State.DOZE_AOD, DozeMachine.State.FINISH); + verify(wallpaperManagerService).setInAmbientMode(eq(false)); + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java index a40ef64b4a82..9b7efe9949ad 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java @@ -16,13 +16,21 @@ package com.android.systemui.statusbar.phone; +import static com.android.systemui.statusbar.phone.ScrimController.VISIBILITY_FULLY_OPAQUE; +import static com.android.systemui.statusbar.phone.ScrimController.VISIBILITY_FULLY_TRANSPARENT; +import static com.android.systemui.statusbar.phone.ScrimController.VISIBILITY_SEMI_TRANSPARENT; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; -import static org.mockito.Mockito.times; +import static org.mockito.Mockito.reset; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.animation.Animator; +import android.app.AlarmManager; import android.graphics.Color; import android.os.Handler; import android.os.Looper; @@ -31,6 +39,7 @@ import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.view.View; +import com.android.keyguard.KeyguardUpdateMonitor; import com.android.systemui.SysuiTestCase; import com.android.systemui.statusbar.ScrimView; import com.android.systemui.util.wakelock.WakeLock; @@ -52,12 +61,13 @@ public class ScrimControllerTest extends SysuiTestCase { private ScrimView mScrimBehind; private ScrimView mScrimInFront; private View mHeadsUpScrim; - private Consumer<Boolean> mScrimVisibilityCallback; - private Boolean mScrimVisibile; + private Consumer<Integer> mScrimVisibilityCallback; + private int mScrimVisibility; private LightBarController mLightBarController; private DozeParameters mDozeParamenters; private WakeLock mWakeLock; private boolean mAlwaysOnEnabled; + private AlarmManager mAlarmManager; @Before public void setup() { @@ -66,13 +76,15 @@ public class ScrimControllerTest extends SysuiTestCase { mScrimInFront = new ScrimView(getContext()); mHeadsUpScrim = mock(View.class); mWakeLock = mock(WakeLock.class); + mAlarmManager = mock(AlarmManager.class); mAlwaysOnEnabled = true; - mScrimVisibilityCallback = (Boolean visible) -> mScrimVisibile = visible; + mScrimVisibilityCallback = (Integer visible) -> mScrimVisibility = visible; mDozeParamenters = mock(DozeParameters.class); when(mDozeParamenters.getAlwaysOn()).thenAnswer(invocation -> mAlwaysOnEnabled); when(mDozeParamenters.getDisplayNeedsBlanking()).thenReturn(true); mScrimController = new SynchronousScrimController(mLightBarController, mScrimBehind, - mScrimInFront, mHeadsUpScrim, mScrimVisibilityCallback, mDozeParamenters); + mScrimInFront, mHeadsUpScrim, mScrimVisibilityCallback, mDozeParamenters, + mAlarmManager); } @Test @@ -87,29 +99,70 @@ public class ScrimControllerTest extends SysuiTestCase { mScrimController.finishAnimationsImmediately(); // Front scrim should be transparent // Back scrim should be visible without tint - assertScrimVisibility(false /* front */, true /* behind */); + assertScrimVisibility(VISIBILITY_FULLY_TRANSPARENT, VISIBILITY_SEMI_TRANSPARENT); assertScrimTint(mScrimBehind, false /* tinted */); } @Test - public void transitionToAod() { + public void transitionToAod_withRegularWallpaper() { mScrimController.transitionTo(ScrimState.AOD); mScrimController.finishAnimationsImmediately(); // Front scrim should be transparent // Back scrim should be visible with tint - assertScrimVisibility(false /* front */, true /* behind */); + assertScrimVisibility(VISIBILITY_FULLY_TRANSPARENT, VISIBILITY_FULLY_OPAQUE); + assertScrimTint(mScrimBehind, true /* tinted */); + assertScrimTint(mScrimInFront, true /* tinted */); + } + + @Test + public void transitionToAod_withAodWallpaper() { + mScrimController.setWallpaperSupportsAmbientMode(true); + mScrimController.transitionTo(ScrimState.AOD); + mScrimController.finishAnimationsImmediately(); + // Front scrim should be transparent + // Back scrim should be transparent + assertScrimVisibility(VISIBILITY_FULLY_TRANSPARENT, VISIBILITY_FULLY_TRANSPARENT); + + // Move on to PULSING and check if the back scrim is still transparent + mScrimController.transitionTo(ScrimState.PULSING); + mScrimController.finishAnimationsImmediately(); + assertScrimVisibility(VISIBILITY_FULLY_TRANSPARENT, VISIBILITY_FULLY_TRANSPARENT); + } + + @Test + public void transitionToAod_withAodWallpaperAndLockScreenWallpaper() { + ScrimState.AOD.mKeyguardUpdateMonitor = new KeyguardUpdateMonitor(getContext()) { + @Override + public boolean hasLockscreenWallpaper() { + return true; + } + }; + mScrimController.setWallpaperSupportsAmbientMode(true); + mScrimController.transitionTo(ScrimState.AOD); + mScrimController.finishAnimationsImmediately(); + // Front scrim should be transparent + // Back scrim should be visible with tint + assertScrimVisibility(VISIBILITY_FULLY_TRANSPARENT, VISIBILITY_FULLY_OPAQUE); assertScrimTint(mScrimBehind, true /* tinted */); assertScrimTint(mScrimInFront, true /* tinted */); } @Test public void transitionToPulsing() { + // Pre-condition + // Need to go to AoD first because PULSING doesn't change + // the back scrim opacity - otherwise it would hide AoD wallpapers. + mScrimController.setWallpaperSupportsAmbientMode(false); + mScrimController.transitionTo(ScrimState.AOD); + mScrimController.finishAnimationsImmediately(); + assertScrimVisibility(VISIBILITY_FULLY_TRANSPARENT, VISIBILITY_FULLY_OPAQUE); + mScrimController.transitionTo(ScrimState.PULSING); mScrimController.finishAnimationsImmediately(); // Front scrim should be transparent // Back scrim should be visible with tint // Pulse callback should have been invoked - assertScrimVisibility(false /* front */, true /* behind */); + assertScrimVisibility(VISIBILITY_FULLY_TRANSPARENT, VISIBILITY_FULLY_OPAQUE); assertScrimTint(mScrimBehind, true /* tinted */); } @@ -119,7 +172,7 @@ public class ScrimControllerTest extends SysuiTestCase { mScrimController.finishAnimationsImmediately(); // Front scrim should be transparent // Back scrim should be visible without tint - assertScrimVisibility(true /* front */, true /* behind */); + assertScrimVisibility(VISIBILITY_SEMI_TRANSPARENT, VISIBILITY_SEMI_TRANSPARENT); assertScrimTint(mScrimBehind, false /* tinted */); } @@ -129,13 +182,13 @@ public class ScrimControllerTest extends SysuiTestCase { mScrimController.finishAnimationsImmediately(); // Front scrim should be transparent // Back scrim should be transparent - assertScrimVisibility(false /* front */, false /* behind */); + assertScrimVisibility(VISIBILITY_FULLY_TRANSPARENT, VISIBILITY_FULLY_TRANSPARENT); assertScrimTint(mScrimBehind, false /* tinted */); assertScrimTint(mScrimInFront, false /* tinted */); // Back scrim should be visible after start dragging mScrimController.setPanelExpansion(0.5f); - assertScrimVisibility(false /* front */, true /* behind */); + assertScrimVisibility(VISIBILITY_FULLY_TRANSPARENT, VISIBILITY_SEMI_TRANSPARENT); } @Test @@ -151,7 +204,7 @@ public class ScrimControllerTest extends SysuiTestCase { // Front scrim should be transparent // Back scrim should be transparent // Neither scrims should be tinted anymore after the animation. - assertScrimVisibility(false /* front */, false /* behind */); + assertScrimVisibility(VISIBILITY_FULLY_TRANSPARENT, VISIBILITY_FULLY_TRANSPARENT); assertScrimTint(mScrimInFront, false /* tinted */); assertScrimTint(mScrimBehind, false /* tinted */); } @@ -169,8 +222,8 @@ public class ScrimControllerTest extends SysuiTestCase { Assert.assertTrue("Scrim should be visible during transition. Alpha: " + mScrimInFront.getViewAlpha(), mScrimInFront.getViewAlpha() > 0); assertScrimTint(mScrimInFront, true /* tinted */); - Assert.assertTrue("Scrim should be visible during transition.", - mScrimVisibile); + Assert.assertSame("Scrim should be visible during transition.", + mScrimVisibility, VISIBILITY_FULLY_OPAQUE); } }); mScrimController.finishAnimationsImmediately(); @@ -224,10 +277,10 @@ public class ScrimControllerTest extends SysuiTestCase { @Test public void testHoldsWakeLock() { mScrimController.transitionTo(ScrimState.AOD); - verify(mWakeLock, times(1)).acquire(); + verify(mWakeLock).acquire(); verify(mWakeLock, never()).release(); mScrimController.finishAnimationsImmediately(); - verify(mWakeLock, times(1)).release(); + verify(mWakeLock).release(); } @Test @@ -236,7 +289,30 @@ public class ScrimControllerTest extends SysuiTestCase { mScrimController.finishAnimationsImmediately(); ScrimController.Callback callback = mock(ScrimController.Callback.class); mScrimController.transitionTo(ScrimState.UNLOCKED, callback); - verify(callback, times(1)).onFinished(); + verify(callback).onFinished(); + } + + @Test + public void testHoldsAodWallpaperAnimationLock() { + // Pre-conditions + mScrimController.transitionTo(ScrimState.AOD); + mScrimController.finishAnimationsImmediately(); + reset(mWakeLock); + + mScrimController.onHideWallpaperTimeout(); + verify(mWakeLock).acquire(); + verify(mWakeLock, never()).release(); + mScrimController.finishAnimationsImmediately(); + verify(mWakeLock).release(); + } + + @Test + public void testWillHideAoDWallpaper() { + mScrimController.setWallpaperSupportsAmbientMode(true); + mScrimController.transitionTo(ScrimState.AOD); + verify(mAlarmManager).setExact(anyInt(), anyLong(), any(), any(), any()); + mScrimController.transitionTo(ScrimState.KEYGUARD); + verify(mAlarmManager).cancel(any(AlarmManager.OnAlarmListener.class)); } private void assertScrimTint(ScrimView scrimView, boolean tinted) { @@ -247,12 +323,23 @@ public class ScrimControllerTest extends SysuiTestCase { tinted, viewIsTinted); } - private void assertScrimVisibility(boolean inFront, boolean behind) { + private void assertScrimVisibility(int inFront, int behind) { + boolean inFrontVisible = inFront != ScrimController.VISIBILITY_FULLY_TRANSPARENT; + boolean behindVisible = behind != ScrimController.VISIBILITY_FULLY_TRANSPARENT; Assert.assertEquals("Unexpected front scrim visibility. Alpha is " - + mScrimInFront.getViewAlpha(), inFront, mScrimInFront.getViewAlpha() > 0); + + mScrimInFront.getViewAlpha(), inFrontVisible, mScrimInFront.getViewAlpha() > 0); Assert.assertEquals("Unexpected back scrim visibility. Alpha is " - + mScrimBehind.getViewAlpha(), behind, mScrimBehind.getViewAlpha() > 0); - Assert.assertEquals("Invalid visibility.", inFront || behind, mScrimVisibile); + + mScrimBehind.getViewAlpha(), behindVisible, mScrimBehind.getViewAlpha() > 0); + + final int visibility; + if (inFront == VISIBILITY_FULLY_OPAQUE || behind == VISIBILITY_FULLY_OPAQUE) { + visibility = VISIBILITY_FULLY_OPAQUE; + } else if (inFront > VISIBILITY_FULLY_TRANSPARENT || behind > VISIBILITY_FULLY_TRANSPARENT) { + visibility = VISIBILITY_SEMI_TRANSPARENT; + } else { + visibility = VISIBILITY_FULLY_TRANSPARENT; + } + Assert.assertEquals("Invalid visibility.", visibility, mScrimVisibility); } /** @@ -264,9 +351,10 @@ public class ScrimControllerTest extends SysuiTestCase { public SynchronousScrimController(LightBarController lightBarController, ScrimView scrimBehind, ScrimView scrimInFront, View headsUpScrim, - Consumer<Boolean> scrimVisibleListener, DozeParameters dozeParameters) { + Consumer<Integer> scrimVisibleListener, DozeParameters dozeParameters, + AlarmManager alarmManager) { super(lightBarController, scrimBehind, scrimInFront, headsUpScrim, - scrimVisibleListener, dozeParameters); + scrimVisibleListener, dozeParameters, alarmManager); mHandler = new FakeHandler(Looper.myLooper()); } diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java index 4bf3c5ab9268..33618249f3c1 100644 --- a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java +++ b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java @@ -52,6 +52,7 @@ import android.os.UserManager; import android.provider.Settings; import android.service.autofill.AutofillService; import android.service.autofill.AutofillServiceInfo; +import android.service.autofill.FieldClassification; import android.service.autofill.FieldClassification.Match; import android.service.autofill.FillEventHistory; import android.service.autofill.FillEventHistory.Event; @@ -81,6 +82,7 @@ import com.android.server.autofill.ui.AutoFillUI; import java.io.PrintWriter; import java.util.ArrayList; +import java.util.List; import java.util.Random; /** @@ -720,37 +722,45 @@ final class AutofillManagerServiceImpl { @Nullable ArrayList<AutofillId> manuallyFilledFieldIds, @Nullable ArrayList<ArrayList<String>> manuallyFilledDatasetIds, @Nullable ArrayList<AutofillId> detectedFieldIdsList, - @Nullable ArrayList<Match> detectedMatchesList, + @Nullable ArrayList<FieldClassification> detectedFieldClassificationsList, @NonNull String appPackageName) { - synchronized (mLock) { if (isValidEventLocked("logDatasetNotSelected()", sessionId)) { AutofillId[] detectedFieldsIds = null; - Match[] detectedMatches = null; + FieldClassification[] detectedFieldClassifications = null; if (detectedFieldIdsList != null) { detectedFieldsIds = new AutofillId[detectedFieldIdsList.size()]; detectedFieldIdsList.toArray(detectedFieldsIds); - detectedMatches = new Match[detectedMatchesList.size()]; - detectedMatchesList.toArray(detectedMatches); + detectedFieldClassifications = + new FieldClassification[detectedFieldClassificationsList.size()]; + detectedFieldClassificationsList.toArray(detectedFieldClassifications); - final int size = detectedMatchesList.size(); + final int numberFields = detectedFieldsIds.length; + int totalSize = 0; float totalScore = 0; - for (int i = 0; i < size; i++) { - totalScore += detectedMatches[i].getScore(); + for (int i = 0; i < numberFields; i++) { + final FieldClassification fc = detectedFieldClassifications[i]; + final List<Match> matches = fc.getMatches(); + final int size = matches.size(); + totalSize += size; + for (int j = 0; j < size; j++) { + totalScore += matches.get(j).getScore(); + } } - final int averageScore = (int) ((totalScore * 100) / size); - mMetricsLogger.write( - Helper.newLogMaker(MetricsEvent.AUTOFILL_FIELD_CLASSIFICATION_MATCHES, - appPackageName, getServicePackageName()) - .setCounterValue(size) - .addTaggedData(MetricsEvent.FIELD_AUTOFILL_MATCH_SCORE, averageScore)); + final int averageScore = (int) ((totalScore * 100) / totalSize); + mMetricsLogger.write(Helper + .newLogMaker(MetricsEvent.AUTOFILL_FIELD_CLASSIFICATION_MATCHES, + appPackageName, getServicePackageName()) + .setCounterValue(numberFields) + .addTaggedData(MetricsEvent.FIELD_AUTOFILL_MATCH_SCORE, + averageScore)); } mEventHistory.addEvent(new Event(Event.TYPE_CONTEXT_COMMITTED, null, clientState, selectedDatasets, ignoredDatasets, changedFieldIds, changedDatasetIds, manuallyFilledFieldIds, manuallyFilledDatasetIds, - detectedFieldsIds, detectedMatches)); + detectedFieldsIds, detectedFieldClassifications)); } } } diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java index ceae93cefe95..7b85a6c935da 100644 --- a/services/autofill/java/com/android/server/autofill/Session.java +++ b/services/autofill/java/com/android/server/autofill/Session.java @@ -66,6 +66,7 @@ import android.service.autofill.SaveRequest; import android.service.autofill.UserData; import android.service.autofill.ValueFinder; import android.service.autofill.EditDistanceScorer; +import android.service.autofill.FieldClassification; import android.util.ArrayMap; import android.util.ArraySet; import android.util.LocalLog; @@ -961,15 +962,15 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState final UserData userData = mService.getUserData(); final ArrayList<AutofillId> detectedFieldIds; - final ArrayList<Match> detectedMatches; + final ArrayList<FieldClassification> detectedFieldClassifications; if (userData != null) { final int maxFieldsSize = UserData.getMaxFieldClassificationIdsSize(); detectedFieldIds = new ArrayList<>(maxFieldsSize); - detectedMatches = new ArrayList<>(maxFieldsSize); + detectedFieldClassifications = new ArrayList<>(maxFieldsSize); } else { detectedFieldIds = null; - detectedMatches = null; + detectedFieldClassifications = null; } for (int i = 0; i < mViewStates.size(); i++) { @@ -1078,8 +1079,8 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState // Sets field classification score for field if (userData!= null) { - setScore(detectedFieldIds, detectedMatches, userData, viewState.id, - currentValue); + setScore(detectedFieldIds, detectedFieldClassifications, userData, + viewState.id, currentValue); } } // else } // else @@ -1093,7 +1094,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState + ", changedDatasetIds=" + changedDatasetIds + ", manuallyFilledIds=" + manuallyFilledIds + ", detectedFieldIds=" + detectedFieldIds - + ", detectedMatches=" + detectedMatches + + ", detectedFieldClassifications=" + detectedFieldClassifications ); } @@ -1116,16 +1117,17 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState mService.logContextCommitted(id, mClientState, mSelectedDatasetIds, ignoredDatasets, changedFieldIds, changedDatasetIds, manuallyFilledFieldIds, manuallyFilledDatasetIds, - detectedFieldIds, detectedMatches, mComponentName.getPackageName()); + detectedFieldIds, detectedFieldClassifications, mComponentName.getPackageName()); } /** - * Adds the top score match to {@code detectedFieldsIds} and {@code detectedMatches} for + * Adds the matches to {@code detectedFieldsIds} and {@code detectedFieldClassifications} for * {@code fieldId} based on its {@code currentValue} and {@code userData}. */ private static void setScore(@NonNull ArrayList<AutofillId> detectedFieldIds, - @NonNull ArrayList<Match> detectedMatches, @NonNull UserData userData, - @NonNull AutofillId fieldId, @NonNull AutofillValue currentValue) { + @NonNull ArrayList<FieldClassification> detectedFieldClassifications, + @NonNull UserData userData, @NonNull AutofillId fieldId, + @NonNull AutofillValue currentValue) { final String[] userValues = userData.getValues(); final String[] remoteIds = userData.getRemoteIds(); @@ -1138,23 +1140,26 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState + valuesLength + ", ids.length = " + idsLength); return; } - String remoteId = null; - float topScore = 0; + + ArrayList<Match> matches = null; for (int i = 0; i < userValues.length; i++) { + String remoteId = remoteIds[i]; final String value = userValues[i]; final float score = userData.getScorer().getScore(currentValue, value); - if (score > topScore) { - topScore = score; - remoteId = remoteIds[i]; + if (score > 0) { + if (sVerbose) { + Slog.v(TAG, "adding score " + score + " at index " + i + " and id " + fieldId); + } + if (matches == null) { + matches = new ArrayList<>(userValues.length); + } + matches.add(new Match(remoteId, score)); } + else if (sVerbose) Slog.v(TAG, "skipping score 0 at index " + i + " and id " + fieldId); } - - if (remoteId != null && topScore > 0) { - if (sVerbose) Slog.v(TAG, "setScores(): top score for #" + fieldId + " is " + topScore); + if (matches != null) { detectedFieldIds.add(fieldId); - detectedMatches.add(new Match(remoteId, topScore)); - } else if (sVerbose) { - Slog.v(TAG, "setScores(): no top score for #" + fieldId + ": " + topScore); + detectedFieldClassifications.add(new FieldClassification(matches)); } } diff --git a/services/core/java/com/android/server/IpSecService.java b/services/core/java/com/android/server/IpSecService.java index e9eb3b3c469d..a76480836850 100644 --- a/services/core/java/com/android/server/IpSecService.java +++ b/services/core/java/com/android/server/IpSecService.java @@ -34,6 +34,7 @@ import android.net.IpSecTransform; import android.net.IpSecTransformResponse; import android.net.IpSecUdpEncapResponse; import android.net.NetworkUtils; +import android.net.TrafficStats; import android.net.util.NetdService; import android.os.Binder; import android.os.IBinder; @@ -120,6 +121,7 @@ public class IpSecService extends IIpSecService.Stub { } private final IpSecServiceConfiguration mSrvConfig; + final UidFdTagger mUidFdTagger; /** * Interface for user-reference and kernel-resource cleanup. @@ -762,8 +764,23 @@ public class IpSecService extends IIpSecService.Stub { /** @hide */ @VisibleForTesting public IpSecService(Context context, IpSecServiceConfiguration config) { + this(context, config, (fd, uid) -> { + try{ + TrafficStats.setThreadStatsUid(uid); + TrafficStats.tagFileDescriptor(fd); + } finally { + TrafficStats.clearThreadStatsUid(); + } + }); + } + + /** @hide */ + @VisibleForTesting + public IpSecService( + Context context, IpSecServiceConfiguration config, UidFdTagger uidFdTagger) { mContext = context; mSrvConfig = config; + mUidFdTagger = uidFdTagger; } public void systemReady() { @@ -925,6 +942,26 @@ public class IpSecService extends IIpSecService.Stub { } /** + * Functional interface to do traffic tagging of given sockets to UIDs. + * + * <p>Specifically used by openUdpEncapsulationSocket to ensure data usage on the UDP encap + * sockets are billed to the UID that the UDP encap socket was created on behalf of. + * + * <p>Separate class so that the socket tagging logic can be mocked; TrafficStats uses static + * methods that cannot be easily mocked/tested. + */ + @VisibleForTesting + public interface UidFdTagger { + /** + * Sets socket tag to assign all traffic to the provided UID. + * + * <p>Since the socket is created on behalf of an unprivileged application, all traffic + * should be accounted to the UID of the unprivileged application. + */ + public void tag(FileDescriptor fd, int uid) throws IOException; + } + + /** * Open a socket via the system server and bind it to the specified port (random if port=0). * This will return a PFD to the user that represent a bound UDP socket. The system server will * cache the socket and a record of its owner so that it can and must be freed when no longer @@ -939,7 +976,8 @@ public class IpSecService extends IIpSecService.Stub { } checkNotNull(binder, "Null Binder passed to openUdpEncapsulationSocket"); - UserRecord userRecord = mUserResourceTracker.getUserRecord(Binder.getCallingUid()); + int callingUid = Binder.getCallingUid(); + UserRecord userRecord = mUserResourceTracker.getUserRecord(callingUid); int resourceId = mNextResourceId.getAndIncrement(); FileDescriptor sockFd = null; try { @@ -948,6 +986,7 @@ public class IpSecService extends IIpSecService.Stub { } sockFd = Os.socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); + mUidFdTagger.tag(sockFd, callingUid); if (port != 0) { Log.v(TAG, "Binding to port " + port); diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java index 7ecb9ce7033e..6a0d3ff92db8 100644 --- a/services/core/java/com/android/server/StorageManagerService.java +++ b/services/core/java/com/android/server/StorageManagerService.java @@ -2718,15 +2718,14 @@ class StorageManagerService extends IStorageManager.Stub final boolean primary = true; final boolean removable = primaryPhysical; final boolean emulated = !primaryPhysical; - final long mtpReserveSize = 0L; final boolean allowMassStorage = false; final long maxFileSize = 0L; final UserHandle owner = new UserHandle(userId); final String uuid = null; final String state = Environment.MEDIA_REMOVED; - res.add(0, new StorageVolume(id, StorageVolume.STORAGE_ID_INVALID, path, - description, primary, removable, emulated, mtpReserveSize, + res.add(0, new StorageVolume(id, path, + description, primary, removable, emulated, allowMassStorage, maxFileSize, owner, uuid, state)); } diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java index 3cd2f6ae3b31..088ddeaab820 100644 --- a/services/core/java/com/android/server/am/ActiveServices.java +++ b/services/core/java/com/android/server/am/ActiveServices.java @@ -2640,7 +2640,7 @@ public final class ActiveServices { try { bumpServiceExecutingLocked(s, false, "unbind"); if (b.client != s.app && (c.flags&Context.BIND_WAIVE_PRIORITY) == 0 - && s.app.setProcState <= ActivityManager.PROCESS_STATE_RECEIVER) { + && s.app.setProcState <= ActivityManager.PROCESS_STATE_HEAVY_WEIGHT) { // If this service's process is not already in the cached list, // then update it in the LRU list here because this may be causing // it to go down there and we want it to start out near the top. diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index eaa7108b9e9d..2ca4fdb92b73 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -391,6 +391,7 @@ import com.android.internal.notification.SystemNotificationChannels; import com.android.internal.os.BackgroundThread; import com.android.internal.os.BatteryStatsImpl; import com.android.internal.os.BinderInternal; +import com.android.internal.os.ByteTransferPipe; import com.android.internal.os.IResultReceiver; import com.android.internal.os.ProcessCpuTracker; import com.android.internal.os.TransferPipe; @@ -17241,20 +17242,24 @@ public class ActivityManagerService extends IActivityManager.Stub } } + private static void sortMemItems(List<MemItem> items) { + Collections.sort(items, new Comparator<MemItem>() { + @Override + public int compare(MemItem lhs, MemItem rhs) { + if (lhs.pss < rhs.pss) { + return 1; + } else if (lhs.pss > rhs.pss) { + return -1; + } + return 0; + } + }); + } + static final void dumpMemItems(PrintWriter pw, String prefix, String tag, ArrayList<MemItem> items, boolean sort, boolean isCompact, boolean dumpSwapPss) { if (sort && !isCompact) { - Collections.sort(items, new Comparator<MemItem>() { - @Override - public int compare(MemItem lhs, MemItem rhs) { - if (lhs.pss < rhs.pss) { - return 1; - } else if (lhs.pss > rhs.pss) { - return -1; - } - return 0; - } - }); + sortMemItems(items); } for (int i=0; i<items.size(); i++) { @@ -17282,6 +17287,33 @@ public class ActivityManagerService extends IActivityManager.Stub } } + static final void dumpMemItems(ProtoOutputStream proto, long fieldId, String tag, + ArrayList<MemItem> items, boolean sort, boolean dumpSwapPss) { + if (sort) { + sortMemItems(items); + } + + for (int i=0; i<items.size(); i++) { + MemItem mi = items.get(i); + final long token = proto.start(fieldId); + + proto.write(MemInfoProto.MemItem.TAG, tag); + proto.write(MemInfoProto.MemItem.LABEL, mi.shortLabel); + proto.write(MemInfoProto.MemItem.IS_PROC, mi.isProc); + proto.write(MemInfoProto.MemItem.ID, mi.id); + proto.write(MemInfoProto.MemItem.HAS_ACTIVITIES, mi.hasActivities); + proto.write(MemInfoProto.MemItem.PSS_KB, mi.pss); + if (dumpSwapPss) { + proto.write(MemInfoProto.MemItem.SWAP_PSS_KB, mi.swapPss); + } + if (mi.subitems != null) { + dumpMemItems(proto, MemInfoProto.MemItem.SUB_ITEMS, mi.shortLabel, mi.subitems, + true, dumpSwapPss); + } + proto.end(token); + } + } + // These are in KB. static final long[] DUMP_MEM_BUCKETS = new long[] { 5*1024, 7*1024, 10*1024, 15*1024, 20*1024, 30*1024, 40*1024, 80*1024, @@ -17493,7 +17525,7 @@ public class ActivityManagerService extends IActivityManager.Stub ArrayList<ProcessRecord> procs = collectProcesses(pw, opti, opts.packages, args); if (opts.dumpProto) { - dumpApplicationMemoryUsage(fd, pw, opts, innerArgs, brief, procs); + dumpApplicationMemoryUsage(fd, opts, innerArgs, brief, procs); } else { dumpApplicationMemoryUsage(fd, pw, prefix, opts, innerArgs, brief, procs, categoryPw); } @@ -17983,7 +18015,7 @@ public class ActivityManagerService extends IActivityManager.Stub } } - private final void dumpApplicationMemoryUsage(FileDescriptor fd, PrintWriter pw, + private final void dumpApplicationMemoryUsage(FileDescriptor fd, MemoryUsageDumpOptions opts, String[] innerArgs, boolean brief, ArrayList<ProcessRecord> procs) { final long uptimeMs = SystemClock.uptimeMillis(); @@ -18025,8 +18057,8 @@ public class ActivityManagerService extends IActivityManager.Stub final int pid = r.pid; final long nToken = proto.start(MemInfoProto.NATIVE_PROCESSES); - proto.write(MemInfoProto.NativeProcess.PID, pid); - proto.write(MemInfoProto.NativeProcess.PROCESS_NAME, r.baseName); + proto.write(MemInfoProto.ProcessMemory.PID, pid); + proto.write(MemInfoProto.ProcessMemory.PROCESS_NAME, r.baseName); if (mi == null) { mi = new Debug.MemoryInfo(); @@ -18052,8 +18084,332 @@ public class ActivityManagerService extends IActivityManager.Stub return; } - // TODO: finish - pw.println("Java processes aren't implemented yet. Have a coffee instead! :]"); + if (!brief && !opts.oomOnly && (procs.size() == 1 || opts.isCheckinRequest || opts.packages)) { + opts.dumpDetails = true; + } + + ProtoOutputStream proto = new ProtoOutputStream(fd); + + proto.write(MemInfoProto.UPTIME_DURATION_MS, uptimeMs); + proto.write(MemInfoProto.ELAPSED_REALTIME_MS, realtimeMs); + + ArrayList<MemItem> procMems = new ArrayList<MemItem>(); + final SparseArray<MemItem> procMemsMap = new SparseArray<MemItem>(); + long nativePss = 0; + long nativeSwapPss = 0; + long dalvikPss = 0; + long dalvikSwapPss = 0; + long[] dalvikSubitemPss = opts.dumpDalvik ? new long[Debug.MemoryInfo.NUM_DVK_STATS] : + EmptyArray.LONG; + long[] dalvikSubitemSwapPss = opts.dumpDalvik ? new long[Debug.MemoryInfo.NUM_DVK_STATS] : + EmptyArray.LONG; + long otherPss = 0; + long otherSwapPss = 0; + long[] miscPss = new long[Debug.MemoryInfo.NUM_OTHER_STATS]; + long[] miscSwapPss = new long[Debug.MemoryInfo.NUM_OTHER_STATS]; + + long oomPss[] = new long[DUMP_MEM_OOM_LABEL.length]; + long oomSwapPss[] = new long[DUMP_MEM_OOM_LABEL.length]; + ArrayList<MemItem>[] oomProcs = (ArrayList<MemItem>[]) + new ArrayList[DUMP_MEM_OOM_LABEL.length]; + + long totalPss = 0; + long totalSwapPss = 0; + long cachedPss = 0; + long cachedSwapPss = 0; + boolean hasSwapPss = false; + + Debug.MemoryInfo mi = null; + for (int i = procs.size() - 1 ; i >= 0 ; i--) { + final ProcessRecord r = procs.get(i); + final IApplicationThread thread; + final int pid; + final int oomAdj; + final boolean hasActivities; + synchronized (this) { + thread = r.thread; + pid = r.pid; + oomAdj = r.getSetAdjWithServices(); + hasActivities = r.activities.size() > 0; + } + if (thread == null) { + continue; + } + if (mi == null) { + mi = new Debug.MemoryInfo(); + } + if (opts.dumpDetails || (!brief && !opts.oomOnly)) { + Debug.getMemoryInfo(pid, mi); + hasSwapPss = mi.hasSwappedOutPss; + } else { + mi.dalvikPss = (int) Debug.getPss(pid, tmpLong, null); + mi.dalvikPrivateDirty = (int) tmpLong[0]; + } + if (opts.dumpDetails) { + if (opts.localOnly) { + final long aToken = proto.start(MemInfoProto.APP_PROCESSES); + final long mToken = proto.start(MemInfoProto.AppData.PROCESS_MEMORY); + proto.write(MemInfoProto.ProcessMemory.PID, pid); + proto.write(MemInfoProto.ProcessMemory.PROCESS_NAME, r.processName); + ActivityThread.dumpMemInfoTable(proto, mi, opts.dumpDalvik, + opts.dumpSummaryOnly, 0, 0, 0, 0, 0, 0); + proto.end(mToken); + proto.end(aToken); + } else { + try { + ByteTransferPipe tp = new ByteTransferPipe(); + try { + thread.dumpMemInfoProto(tp.getWriteFd(), + mi, opts.dumpFullDetails, opts.dumpDalvik, opts.dumpSummaryOnly, + opts.dumpUnreachable, innerArgs); + proto.write(MemInfoProto.APP_PROCESSES, tp.get()); + } finally { + tp.kill(); + } + } catch (IOException e) { + Log.e(TAG, "Got IOException!", e); + } catch (RemoteException e) { + Log.e(TAG, "Got RemoteException!", e); + } + } + } + + final long myTotalPss = mi.getTotalPss(); + final long myTotalUss = mi.getTotalUss(); + final long myTotalSwapPss = mi.getTotalSwappedOutPss(); + + synchronized (this) { + if (r.thread != null && oomAdj == r.getSetAdjWithServices()) { + // Record this for posterity if the process has been stable. + r.baseProcessTracker.addPss(myTotalPss, myTotalUss, true, r.pkgList); + } + } + + if (!opts.isCheckinRequest && mi != null) { + totalPss += myTotalPss; + totalSwapPss += myTotalSwapPss; + MemItem pssItem = new MemItem(r.processName + " (pid " + pid + + (hasActivities ? " / activities)" : ")"), r.processName, myTotalPss, + myTotalSwapPss, pid, hasActivities); + procMems.add(pssItem); + procMemsMap.put(pid, pssItem); + + nativePss += mi.nativePss; + nativeSwapPss += mi.nativeSwappedOutPss; + dalvikPss += mi.dalvikPss; + dalvikSwapPss += mi.dalvikSwappedOutPss; + for (int j=0; j<dalvikSubitemPss.length; j++) { + dalvikSubitemPss[j] += mi.getOtherPss(Debug.MemoryInfo.NUM_OTHER_STATS + j); + dalvikSubitemSwapPss[j] += + mi.getOtherSwappedOutPss(Debug.MemoryInfo.NUM_OTHER_STATS + j); + } + otherPss += mi.otherPss; + otherSwapPss += mi.otherSwappedOutPss; + for (int j=0; j<Debug.MemoryInfo.NUM_OTHER_STATS; j++) { + long mem = mi.getOtherPss(j); + miscPss[j] += mem; + otherPss -= mem; + mem = mi.getOtherSwappedOutPss(j); + miscSwapPss[j] += mem; + otherSwapPss -= mem; + } + + if (oomAdj >= ProcessList.CACHED_APP_MIN_ADJ) { + cachedPss += myTotalPss; + cachedSwapPss += myTotalSwapPss; + } + + for (int oomIndex=0; oomIndex<oomPss.length; oomIndex++) { + if (oomIndex == (oomPss.length - 1) + || (oomAdj >= DUMP_MEM_OOM_ADJ[oomIndex] + && oomAdj < DUMP_MEM_OOM_ADJ[oomIndex + 1])) { + oomPss[oomIndex] += myTotalPss; + oomSwapPss[oomIndex] += myTotalSwapPss; + if (oomProcs[oomIndex] == null) { + oomProcs[oomIndex] = new ArrayList<MemItem>(); + } + oomProcs[oomIndex].add(pssItem); + break; + } + } + } + } + + long nativeProcTotalPss = 0; + + if (procs.size() > 1 && !opts.packages) { + // If we are showing aggregations, also look for native processes to + // include so that our aggregations are more accurate. + updateCpuStatsNow(); + mi = null; + synchronized (mProcessCpuTracker) { + final int N = mProcessCpuTracker.countStats(); + for (int i=0; i<N; i++) { + ProcessCpuTracker.Stats st = mProcessCpuTracker.getStats(i); + if (st.vsize > 0 && procMemsMap.indexOfKey(st.pid) < 0) { + if (mi == null) { + mi = new Debug.MemoryInfo(); + } + if (!brief && !opts.oomOnly) { + Debug.getMemoryInfo(st.pid, mi); + } else { + mi.nativePss = (int)Debug.getPss(st.pid, tmpLong, null); + mi.nativePrivateDirty = (int)tmpLong[0]; + } + + final long myTotalPss = mi.getTotalPss(); + final long myTotalSwapPss = mi.getTotalSwappedOutPss(); + totalPss += myTotalPss; + nativeProcTotalPss += myTotalPss; + + MemItem pssItem = new MemItem(st.name + " (pid " + st.pid + ")", + st.name, myTotalPss, mi.getSummaryTotalSwapPss(), st.pid, false); + procMems.add(pssItem); + + nativePss += mi.nativePss; + nativeSwapPss += mi.nativeSwappedOutPss; + dalvikPss += mi.dalvikPss; + dalvikSwapPss += mi.dalvikSwappedOutPss; + for (int j=0; j<dalvikSubitemPss.length; j++) { + dalvikSubitemPss[j] += mi.getOtherPss(Debug.MemoryInfo.NUM_OTHER_STATS + j); + dalvikSubitemSwapPss[j] += + mi.getOtherSwappedOutPss(Debug.MemoryInfo.NUM_OTHER_STATS + j); + } + otherPss += mi.otherPss; + otherSwapPss += mi.otherSwappedOutPss; + for (int j=0; j<Debug.MemoryInfo.NUM_OTHER_STATS; j++) { + long mem = mi.getOtherPss(j); + miscPss[j] += mem; + otherPss -= mem; + mem = mi.getOtherSwappedOutPss(j); + miscSwapPss[j] += mem; + otherSwapPss -= mem; + } + oomPss[0] += myTotalPss; + oomSwapPss[0] += myTotalSwapPss; + if (oomProcs[0] == null) { + oomProcs[0] = new ArrayList<MemItem>(); + } + oomProcs[0].add(pssItem); + } + } + } + + ArrayList<MemItem> catMems = new ArrayList<MemItem>(); + + catMems.add(new MemItem("Native", "Native", nativePss, nativeSwapPss, -1)); + final int dalvikId = -2; + catMems.add(new MemItem("Dalvik", "Dalvik", dalvikPss, dalvikSwapPss, dalvikId)); + catMems.add(new MemItem("Unknown", "Unknown", otherPss, otherSwapPss, -3)); + for (int j=0; j<Debug.MemoryInfo.NUM_OTHER_STATS; j++) { + String label = Debug.MemoryInfo.getOtherLabel(j); + catMems.add(new MemItem(label, label, miscPss[j], miscSwapPss[j], j)); + } + if (dalvikSubitemPss.length > 0) { + // Add dalvik subitems. + for (MemItem memItem : catMems) { + int memItemStart = 0, memItemEnd = 0; + if (memItem.id == dalvikId) { + memItemStart = Debug.MemoryInfo.OTHER_DVK_STAT_DALVIK_START; + memItemEnd = Debug.MemoryInfo.OTHER_DVK_STAT_DALVIK_END; + } else if (memItem.id == Debug.MemoryInfo.OTHER_DALVIK_OTHER) { + memItemStart = Debug.MemoryInfo.OTHER_DVK_STAT_DALVIK_OTHER_START; + memItemEnd = Debug.MemoryInfo.OTHER_DVK_STAT_DALVIK_OTHER_END; + } else if (memItem.id == Debug.MemoryInfo.OTHER_DEX) { + memItemStart = Debug.MemoryInfo.OTHER_DVK_STAT_DEX_START; + memItemEnd = Debug.MemoryInfo.OTHER_DVK_STAT_DEX_END; + } else if (memItem.id == Debug.MemoryInfo.OTHER_ART) { + memItemStart = Debug.MemoryInfo.OTHER_DVK_STAT_ART_START; + memItemEnd = Debug.MemoryInfo.OTHER_DVK_STAT_ART_END; + } else { + continue; // No subitems, continue. + } + memItem.subitems = new ArrayList<MemItem>(); + for (int j=memItemStart; j<=memItemEnd; j++) { + final String name = Debug.MemoryInfo.getOtherLabel( + Debug.MemoryInfo.NUM_OTHER_STATS + j); + memItem.subitems.add(new MemItem(name, name, dalvikSubitemPss[j], + dalvikSubitemSwapPss[j], j)); + } + } + } + + ArrayList<MemItem> oomMems = new ArrayList<MemItem>(); + for (int j=0; j<oomPss.length; j++) { + if (oomPss[j] != 0) { + String label = opts.isCompact ? DUMP_MEM_OOM_COMPACT_LABEL[j] + : DUMP_MEM_OOM_LABEL[j]; + MemItem item = new MemItem(label, label, oomPss[j], oomSwapPss[j], + DUMP_MEM_OOM_ADJ[j]); + item.subitems = oomProcs[j]; + oomMems.add(item); + } + } + + opts.dumpSwapPss = opts.dumpSwapPss && hasSwapPss && totalSwapPss != 0; + if (!opts.oomOnly) { + dumpMemItems(proto, MemInfoProto.TOTAL_PSS_BY_PROCESS, "proc", + procMems, true, opts.dumpSwapPss); + } + dumpMemItems(proto, MemInfoProto.TOTAL_PSS_BY_OOM_ADJUSTMENT, "oom", + oomMems, false, opts.dumpSwapPss); + if (!brief && !opts.oomOnly) { + dumpMemItems(proto, MemInfoProto.TOTAL_PSS_BY_CATEGORY, "cat", + catMems, true, opts.dumpSwapPss); + } + MemInfoReader memInfo = new MemInfoReader(); + memInfo.readMemInfo(); + if (nativeProcTotalPss > 0) { + synchronized (this) { + final long cachedKb = memInfo.getCachedSizeKb(); + final long freeKb = memInfo.getFreeSizeKb(); + final long zramKb = memInfo.getZramTotalSizeKb(); + final long kernelKb = memInfo.getKernelUsedSizeKb(); + EventLogTags.writeAmMeminfo(cachedKb*1024, freeKb*1024, zramKb*1024, + kernelKb*1024, nativeProcTotalPss*1024); + mProcessStats.addSysMemUsageLocked(cachedKb, freeKb, zramKb, kernelKb, + nativeProcTotalPss); + } + } + if (!brief) { + proto.write(MemInfoProto.TOTAL_RAM_KB, memInfo.getTotalSizeKb()); + proto.write(MemInfoProto.STATUS, mLastMemoryLevel); + proto.write(MemInfoProto.CACHED_PSS_KB, cachedPss); + proto.write(MemInfoProto.CACHED_KERNEL_KB, memInfo.getCachedSizeKb()); + proto.write(MemInfoProto.FREE_KB, memInfo.getFreeSizeKb()); + } + long lostRAM = memInfo.getTotalSizeKb() - (totalPss - totalSwapPss) + - memInfo.getFreeSizeKb() - memInfo.getCachedSizeKb() + - memInfo.getKernelUsedSizeKb() - memInfo.getZramTotalSizeKb(); + proto.write(MemInfoProto.USED_PSS_KB, totalPss - cachedPss); + proto.write(MemInfoProto.USED_KERNEL_KB, memInfo.getKernelUsedSizeKb()); + proto.write(MemInfoProto.LOST_RAM_KB, lostRAM); + if (!brief) { + if (memInfo.getZramTotalSizeKb() != 0) { + proto.write(MemInfoProto.TOTAL_ZRAM_KB, memInfo.getZramTotalSizeKb()); + proto.write(MemInfoProto.ZRAM_PHYSICAL_USED_IN_SWAP_KB, + memInfo.getSwapTotalSizeKb() - memInfo.getSwapFreeSizeKb()); + proto.write(MemInfoProto.TOTAL_ZRAM_SWAP_KB, memInfo.getSwapTotalSizeKb()); + } + final long[] ksm = getKsmInfo(); + proto.write(MemInfoProto.KSM_SHARING_KB, ksm[KSM_SHARING]); + proto.write(MemInfoProto.KSM_SHARED_KB, ksm[KSM_SHARED]); + proto.write(MemInfoProto.KSM_UNSHARED_KB, ksm[KSM_UNSHARED]); + proto.write(MemInfoProto.KSM_VOLATILE_KB, ksm[KSM_VOLATILE]); + + proto.write(MemInfoProto.TUNING_MB, ActivityManager.staticGetMemoryClass()); + proto.write(MemInfoProto.TUNING_LARGE_MB, ActivityManager.staticGetLargeMemoryClass()); + proto.write(MemInfoProto.OOM_KB, + mProcessList.getMemLevel(ProcessList.CACHED_APP_MAX_ADJ) / 1024); + proto.write(MemInfoProto.RESTORE_LIMIT_KB, + mProcessList.getCachedRestoreThresholdKb()); + + proto.write(MemInfoProto.IS_LOW_RAM_DEVICE, ActivityManager.isLowRamDeviceStatic()); + proto.write(MemInfoProto.IS_HIGH_END_GFX, ActivityManager.isHighEndGfx()); + } + } + + proto.flush(); } private void appendBasicMemEntry(StringBuilder sb, int oomAdj, int procState, long pss, @@ -23254,7 +23610,8 @@ public class ActivityManagerService extends IActivityManager.Stub break; } } - } else if (app.curProcState == ActivityManager.PROCESS_STATE_HEAVY_WEIGHT) { + } else if (app.curProcState == ActivityManager.PROCESS_STATE_HEAVY_WEIGHT + && !app.killedByAm) { if (app.trimMemoryLevel < ComponentCallbacks2.TRIM_MEMORY_BACKGROUND && app.thread != null) { try { diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java index eb1636d732ee..c9c26ef18c39 100644 --- a/services/core/java/com/android/server/am/ProcessList.java +++ b/services/core/java/com/android/server/am/ProcessList.java @@ -379,15 +379,15 @@ public final class ProcessList { case ActivityManager.PROCESS_STATE_BACKUP: procState = "BKUP"; break; - case ActivityManager.PROCESS_STATE_HEAVY_WEIGHT: - procState = "HVY "; - break; case ActivityManager.PROCESS_STATE_SERVICE: procState = "SVC "; break; case ActivityManager.PROCESS_STATE_RECEIVER: procState = "RCVR"; break; + case ActivityManager.PROCESS_STATE_HEAVY_WEIGHT: + procState = "HVY "; + break; case ActivityManager.PROCESS_STATE_HOME: procState = "HOME"; break; @@ -490,9 +490,9 @@ public final class ProcessList { PROC_MEM_IMPORTANT, // ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND PROC_MEM_IMPORTANT, // ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND PROC_MEM_IMPORTANT, // ActivityManager.PROCESS_STATE_BACKUP - PROC_MEM_IMPORTANT, // ActivityManager.PROCESS_STATE_HEAVY_WEIGHT PROC_MEM_SERVICE, // ActivityManager.PROCESS_STATE_SERVICE PROC_MEM_CACHED, // ActivityManager.PROCESS_STATE_RECEIVER + PROC_MEM_IMPORTANT, // ActivityManager.PROCESS_STATE_HEAVY_WEIGHT PROC_MEM_CACHED, // ActivityManager.PROCESS_STATE_HOME PROC_MEM_CACHED, // ActivityManager.PROCESS_STATE_LAST_ACTIVITY PROC_MEM_CACHED, // ActivityManager.PROCESS_STATE_CACHED_ACTIVITY @@ -512,9 +512,9 @@ public final class ProcessList { PSS_FIRST_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND PSS_FIRST_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND PSS_FIRST_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_BACKUP - PSS_FIRST_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_HEAVY_WEIGHT PSS_FIRST_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_SERVICE PSS_FIRST_CACHED_INTERVAL, // ActivityManager.PROCESS_STATE_RECEIVER + PSS_FIRST_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_HEAVY_WEIGHT PSS_FIRST_CACHED_INTERVAL, // ActivityManager.PROCESS_STATE_HOME PSS_FIRST_CACHED_INTERVAL, // ActivityManager.PROCESS_STATE_LAST_ACTIVITY PSS_FIRST_CACHED_INTERVAL, // ActivityManager.PROCESS_STATE_CACHED_ACTIVITY @@ -534,9 +534,9 @@ public final class ProcessList { PSS_SAME_IMPORTANT_INTERVAL, // ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND PSS_SAME_IMPORTANT_INTERVAL, // ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND PSS_SAME_IMPORTANT_INTERVAL, // ActivityManager.PROCESS_STATE_BACKUP - PSS_SAME_IMPORTANT_INTERVAL, // ActivityManager.PROCESS_STATE_HEAVY_WEIGHT PSS_SAME_SERVICE_INTERVAL, // ActivityManager.PROCESS_STATE_SERVICE PSS_SAME_SERVICE_INTERVAL, // ActivityManager.PROCESS_STATE_RECEIVER + PSS_SAME_IMPORTANT_INTERVAL, // ActivityManager.PROCESS_STATE_HEAVY_WEIGHT PSS_SAME_CACHED_INTERVAL, // ActivityManager.PROCESS_STATE_HOME PSS_SAME_CACHED_INTERVAL, // ActivityManager.PROCESS_STATE_LAST_ACTIVITY PSS_SAME_CACHED_INTERVAL, // ActivityManager.PROCESS_STATE_CACHED_ACTIVITY @@ -545,7 +545,7 @@ public final class ProcessList { PSS_SAME_CACHED_INTERVAL, // ActivityManager.PROCESS_STATE_CACHED_EMPTY }; - private static final long[] sTestFirstAwakePssTimes = new long[] { + private static final long[] sTestFirstPssTimes = new long[] { PSS_TEST_FIRST_TOP_INTERVAL, // ActivityManager.PROCESS_STATE_PERSISTENT PSS_TEST_FIRST_TOP_INTERVAL, // ActivityManager.PROCESS_STATE_PERSISTENT_UI PSS_TEST_FIRST_TOP_INTERVAL, // ActivityManager.PROCESS_STATE_TOP @@ -556,9 +556,9 @@ public final class ProcessList { PSS_TEST_FIRST_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND PSS_TEST_FIRST_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND PSS_TEST_FIRST_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_BACKUP - PSS_TEST_FIRST_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_HEAVY_WEIGHT PSS_TEST_FIRST_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_SERVICE PSS_TEST_FIRST_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_RECEIVER + PSS_TEST_FIRST_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_HEAVY_WEIGHT PSS_TEST_FIRST_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_HOME PSS_TEST_FIRST_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_LAST_ACTIVITY PSS_TEST_FIRST_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_CACHED_ACTIVITY @@ -567,7 +567,7 @@ public final class ProcessList { PSS_TEST_FIRST_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_CACHED_EMPTY }; - private static final long[] sTestSameAwakePssTimes = new long[] { + private static final long[] sTestSamePssTimes = new long[] { PSS_TEST_SAME_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_PERSISTENT PSS_TEST_SAME_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_PERSISTENT_UI PSS_TEST_SAME_IMPORTANT_INTERVAL, // ActivityManager.PROCESS_STATE_TOP @@ -578,9 +578,9 @@ public final class ProcessList { PSS_TEST_SAME_IMPORTANT_INTERVAL, // ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND PSS_TEST_SAME_IMPORTANT_INTERVAL, // ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND PSS_TEST_SAME_IMPORTANT_INTERVAL, // ActivityManager.PROCESS_STATE_BACKUP - PSS_TEST_SAME_IMPORTANT_INTERVAL, // ActivityManager.PROCESS_STATE_HEAVY_WEIGHT PSS_TEST_SAME_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_SERVICE PSS_TEST_SAME_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_RECEIVER + PSS_TEST_SAME_IMPORTANT_INTERVAL, // ActivityManager.PROCESS_STATE_HEAVY_WEIGHT PSS_TEST_SAME_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_HOME PSS_TEST_SAME_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_LAST_ACTIVITY PSS_TEST_SAME_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_CACHED_ACTIVITY @@ -601,8 +601,8 @@ public final class ProcessList { boolean sleeping, long now) { final long[] table = test ? (first - ? sTestFirstAwakePssTimes - : sTestSameAwakePssTimes) + ? sTestFirstPssTimes + : sTestSamePssTimes) : (first ? sFirstAwakePssTimes : sSameAwakePssTimes); diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index efa0bf82f209..2d7a6adeba3f 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -3884,7 +3884,8 @@ public class AudioService extends IAudioService.Stub IsInCall = telecomManager.isInCall(); Binder.restoreCallingIdentity(ident); - return (IsInCall || getMode() == AudioManager.MODE_IN_COMMUNICATION); + return (IsInCall || getMode() == AudioManager.MODE_IN_COMMUNICATION || + getMode() == AudioManager.MODE_IN_CALL); } /** diff --git a/services/core/java/com/android/server/location/ContextHubClientBroker.java b/services/core/java/com/android/server/location/ContextHubClientBroker.java index 41d9feb97c4e..9640e042fe6a 100644 --- a/services/core/java/com/android/server/location/ContextHubClientBroker.java +++ b/services/core/java/com/android/server/location/ContextHubClientBroker.java @@ -179,7 +179,7 @@ public class ContextHubClientBroker extends IContextHubClient.Stub } /** - * Handles a nanoapp load event. + * Notifies the client of a nanoapp load event if the connection is open. * * @param nanoAppId the ID of the nanoapp that was loaded. */ @@ -195,7 +195,7 @@ public class ContextHubClientBroker extends IContextHubClient.Stub } /** - * Handles a nanoapp unload event. + * Notifies the client of a nanoapp unload event if the connection is open. * * @param nanoAppId the ID of the nanoapp that was unloaded. */ @@ -211,7 +211,7 @@ public class ContextHubClientBroker extends IContextHubClient.Stub } /** - * Handles a hub reset for this client. + * Notifies the client of a hub reset event if the connection is open. */ /* package */ void onHubReset() { if (mConnectionOpen.get()) { @@ -223,4 +223,21 @@ public class ContextHubClientBroker extends IContextHubClient.Stub } } } + + /** + * Notifies the client of a nanoapp abort event if the connection is open. + * + * @param nanoAppId the ID of the nanoapp that aborted + * @param abortCode the nanoapp specific abort code + */ + /* package */ void onNanoAppAborted(long nanoAppId, int abortCode) { + if (mConnectionOpen.get()) { + try { + mCallbackInterface.onNanoAppAborted(nanoAppId, abortCode); + } catch (RemoteException e) { + Log.e(TAG, "RemoteException while calling onNanoAppAborted on client" + + " (host endpoint ID = " + mHostEndPointId + ")", e); + } + } + } } diff --git a/services/core/java/com/android/server/location/ContextHubClientManager.java b/services/core/java/com/android/server/location/ContextHubClientManager.java index d58a7460b23f..60b5b1f06c42 100644 --- a/services/core/java/com/android/server/location/ContextHubClientManager.java +++ b/services/core/java/com/android/server/location/ContextHubClientManager.java @@ -149,35 +149,38 @@ import java.util.function.Consumer; } /** - * Handles a nanoapp load event. - * - * @param contextHubId the ID of the hub where the nanoapp was loaded. - * @param nanoAppId the ID of the nanoapp that was loaded. + * @param contextHubId the ID of the hub where the nanoapp was loaded + * @param nanoAppId the ID of the nanoapp that was loaded */ /* package */ void onNanoAppLoaded(int contextHubId, long nanoAppId) { forEachClientOfHub(contextHubId, client -> client.onNanoAppLoaded(nanoAppId)); } /** - * Handles a nanoapp unload event. - * - * @param contextHubId the ID of the hub where the nanoapp was unloaded. - * @param nanoAppId the ID of the nanoapp that was unloaded. + * @param contextHubId the ID of the hub where the nanoapp was unloaded + * @param nanoAppId the ID of the nanoapp that was unloaded */ /* package */ void onNanoAppUnloaded(int contextHubId, long nanoAppId) { forEachClientOfHub(contextHubId, client -> client.onNanoAppUnloaded(nanoAppId)); } /** - * Handles a hub reset. - * - * @param contextHubId the ID of the hub that has reset. + * @param contextHubId the ID of the hub that has reset */ /* package */ void onHubReset(int contextHubId) { forEachClientOfHub(contextHubId, client -> client.onHubReset()); } /** + * @param contextHubId the ID of the hub that contained the nanoapp that aborted + * @param nanoAppId the ID of the nanoapp that aborted + * @param abortCode the nanoapp specific abort code + */ + /* package */ void onNanoAppAborted(int contextHubId, long nanoAppId, int abortCode) { + forEachClientOfHub(contextHubId, client -> client.onNanoAppAborted(nanoAppId, abortCode)); + } + + /** * Creates a new ContextHubClientBroker object for a client and registers it with the client * manager. * diff --git a/services/core/java/com/android/server/location/ContextHubService.java b/services/core/java/com/android/server/location/ContextHubService.java index 1ad0cf931165..7f88663d4e3b 100644 --- a/services/core/java/com/android/server/location/ContextHubService.java +++ b/services/core/java/com/android/server/location/ContextHubService.java @@ -269,13 +269,9 @@ public class ContextHubService extends IContextHubService.Stub { checkPermissions(); int[] returnArray = new int[mContextHubInfo.length]; - Log.d(TAG, "System supports " + returnArray.length + " hubs"); for (int i = 0; i < returnArray.length; ++i) { returnArray[i] = i; - Log.d(TAG, String.format("Hub %s is mapped to %d", - mContextHubInfo[i].getName(), returnArray[i])); } - return returnArray; } @@ -425,12 +421,6 @@ public class ContextHubService extends IContextHubService.Stub { for (int i = 0; i < foundInstances.size(); i++) { retArray[i] = foundInstances.get(i).intValue(); } - - if (retArray.length == 0) { - Log.d(TAG, "No nanoapps found on hub ID " + hubHandle + " using NanoAppFilter: " - + filter); - } - return retArray; } @@ -583,7 +573,7 @@ public class ContextHubService extends IContextHubService.Stub { * @param abortCode the nanoapp-specific abort code */ private void handleAppAbortCallback(int contextHubId, long nanoAppId, int abortCode) { - // TODO(b/31049861): Implement this + mClientManager.onNanoAppAborted(contextHubId, nanoAppId, abortCode); } /** diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java index 60f451ab437e..aa55930697ba 100644 --- a/services/core/java/com/android/server/locksettings/LockSettingsService.java +++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java @@ -28,6 +28,8 @@ import static com.android.internal.widget.LockPatternUtils.USER_FRP; import static com.android.internal.widget.LockPatternUtils.frpCredentialEnabled; import static com.android.internal.widget.LockPatternUtils.userOwnsFrpCredential; +import android.annotation.NonNull; +import android.annotation.Nullable; import android.annotation.UserIdInt; import android.app.ActivityManager; import android.app.IActivityManager; @@ -60,6 +62,7 @@ import android.os.Process; import android.os.RemoteException; import android.os.ResultReceiver; import android.os.ServiceManager; +import android.os.ServiceSpecificException; import android.os.ShellCallback; import android.os.StrictMode; import android.os.SystemProperties; @@ -75,6 +78,10 @@ import android.security.keystore.AndroidKeyStoreProvider; import android.security.keystore.KeyProperties; import android.security.keystore.KeyProtection; import android.security.keystore.UserNotAuthenticatedException; +import android.security.recoverablekeystore.KeyEntryRecoveryData; +import android.security.recoverablekeystore.KeyStoreRecoveryData; +import android.security.recoverablekeystore.KeyStoreRecoveryMetadata; +import android.security.recoverablekeystore.RecoverableKeyStoreLoader.RecoverableKeyStoreLoaderException; import android.service.gatekeeper.GateKeeperResponse; import android.service.gatekeeper.IGateKeeperService; import android.text.TextUtils; @@ -95,9 +102,10 @@ import com.android.internal.widget.LockPatternUtils; import com.android.internal.widget.VerifyCredentialResponse; import com.android.server.SystemService; import com.android.server.locksettings.LockSettingsStorage.CredentialHash; +import com.android.server.locksettings.LockSettingsStorage.PersistentData; +import com.android.server.locksettings.recoverablekeystore.RecoverableKeyStoreManager; import com.android.server.locksettings.SyntheticPasswordManager.AuthenticationResult; import com.android.server.locksettings.SyntheticPasswordManager.AuthenticationToken; -import com.android.server.locksettings.LockSettingsStorage.PersistentData; import libcore.util.HexEncoding; @@ -169,6 +177,8 @@ public class LockSettingsService extends ILockSettings.Stub { private final KeyStore mKeyStore; + private final RecoverableKeyStoreManager mRecoverableKeyStoreManager; + private boolean mFirstCallToVold; protected IGateKeeperService mGateKeeperService; @@ -367,6 +377,10 @@ public class LockSettingsService extends ILockSettings.Stub { return KeyStore.getInstance(); } + public RecoverableKeyStoreManager getRecoverableKeyStoreManager() { + return RecoverableKeyStoreManager.getInstance(mContext); + } + public IStorageManager getStorageManager() { final IBinder service = ServiceManager.getService("mount"); if (service != null) { @@ -393,6 +407,7 @@ public class LockSettingsService extends ILockSettings.Stub { mInjector = injector; mContext = injector.getContext(); mKeyStore = injector.getKeyStore(); + mRecoverableKeyStoreManager = injector.getRecoverableKeyStoreManager(); mHandler = injector.getHandler(); mStrongAuth = injector.getStrongAuth(); mActivityManager = injector.getActivityManager(); @@ -1914,6 +1929,72 @@ public class LockSettingsService extends ILockSettings.Stub { } } + @Override + public void initRecoveryService(@NonNull String rootCertificateAlias, + @NonNull byte[] signedPublicKeyList, int userId) + throws RemoteException { + mRecoverableKeyStoreManager.initRecoveryService(rootCertificateAlias, + signedPublicKeyList, userId); + } + + @Override + public KeyStoreRecoveryData getRecoveryData(@NonNull byte[] account, int userId) + throws RemoteException { + return mRecoverableKeyStoreManager.getRecoveryData(account, userId); + } + + @Override + public void setServerParameters(long serverParameters, int userId) throws RemoteException { + mRecoverableKeyStoreManager.setServerParameters(serverParameters, userId); + } + + @Override + public void setRecoveryStatus(@NonNull String packageName, @Nullable String[] aliases, + int status, int userId) throws RemoteException { + mRecoverableKeyStoreManager.setRecoveryStatus(packageName, aliases, status, userId); + } + + @Override + public void setRecoverySecretTypes(@NonNull @KeyStoreRecoveryMetadata.UserSecretType + int[] secretTypes, int userId) throws RemoteException { + mRecoverableKeyStoreManager.setRecoverySecretTypes(secretTypes, userId); + } + + @Override + public int[] getRecoverySecretTypes(int userId) throws RemoteException { + return mRecoverableKeyStoreManager.getRecoverySecretTypes(userId); + + } + + @Override + public int[] getPendingRecoverySecretTypes(int userId) throws RemoteException { + throw new SecurityException("Not implemented"); + } + + @Override + public void recoverySecretAvailable(@NonNull KeyStoreRecoveryMetadata recoverySecret, + int userId) + throws RemoteException { + mRecoverableKeyStoreManager.recoverySecretAvailable(recoverySecret, userId); + } + + @Override + public byte[] startRecoverySession(@NonNull String sessionId, + @NonNull byte[] verifierPublicKey, @NonNull byte[] vaultParams, + @NonNull byte[] vaultChallenge, @NonNull List<KeyStoreRecoveryMetadata> secrets, + int userId) throws RemoteException { + return mRecoverableKeyStoreManager.startRecoverySession(sessionId, verifierPublicKey, + vaultParams, vaultChallenge, secrets, userId); + } + + @Override + public void recoverKeys(@NonNull String sessionId, @NonNull byte[] recoveryKeyBlob, + @NonNull List<KeyEntryRecoveryData> applicationKeys, int userId) + throws RemoteException { + mRecoverableKeyStoreManager.recoverKeys(sessionId, recoveryKeyBlob, applicationKeys, + userId); + } + private static final String[] VALID_SETTINGS = new String[] { LockPatternUtils.LOCKOUT_PERMANENT_KEY, LockPatternUtils.LOCKOUT_ATTEMPT_DEADLINE, diff --git a/core/java/android/os/IIncidentReportCompletedListener.aidl b/services/core/java/com/android/server/locksettings/recoverablekeystore/InsecureUserException.java index 2d66bf635a2a..5155a99ee04f 100644 --- a/core/java/android/os/IIncidentReportCompletedListener.aidl +++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/InsecureUserException.java @@ -1,11 +1,11 @@ -/** - * Copyright (c) 2016, The Android Open Source Project +/* + * Copyright (C) 2017 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -14,19 +14,18 @@ * limitations under the License. */ -package android.os; +package com.android.server.locksettings.recoverablekeystore; /** - * Listener for incident report status - * - * {@hide} - */ -oneway interface IIncidentReportCompletedListener { + * Error thrown initializing {@link PlatformKeyManager} if the user is not secure (i.e., has no + * lock screen set). + */ +public class InsecureUserException extends Exception { + /** - * Called when there has been an incident report. - * - * The system service implementing this method should delete or move the file - * after it is finished with it. + * A new instance with {@code message} error message. */ - void onIncidentReport(String filename); + public InsecureUserException(String message) { + super(message); + } } diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/KeyStoreProxy.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/KeyStoreProxy.java new file mode 100644 index 000000000000..7c9b39597ab2 --- /dev/null +++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/KeyStoreProxy.java @@ -0,0 +1,43 @@ +/* + * 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.locksettings.recoverablekeystore; + +import java.security.Key; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.UnrecoverableKeyException; + +/** + * Proxies {@link java.security.KeyStore}. As all of its methods are final, it cannot otherwise be + * mocked for tests. + * + * @hide + */ +public interface KeyStoreProxy { + + /** @see KeyStore#containsAlias(String) */ + boolean containsAlias(String alias) throws KeyStoreException; + + /** @see KeyStore#getKey(String, char[]) */ + Key getKey(String alias, char[] password) + throws KeyStoreException, NoSuchAlgorithmException, UnrecoverableKeyException; + + /** @see KeyStore#setEntry(String, KeyStore.Entry, KeyStore.ProtectionParameter) */ + void setEntry(String alias, KeyStore.Entry entry, KeyStore.ProtectionParameter protParam) + throws KeyStoreException; +} diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/KeyStoreProxyImpl.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/KeyStoreProxyImpl.java new file mode 100644 index 000000000000..ceee3815845e --- /dev/null +++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/KeyStoreProxyImpl.java @@ -0,0 +1,55 @@ +/* + * 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.locksettings.recoverablekeystore; + +import java.security.Key; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.UnrecoverableKeyException; + +/** + * Implementation of {@link KeyStoreProxy} that delegates all method calls to the {@link KeyStore}. + */ +public class KeyStoreProxyImpl implements KeyStoreProxy { + + private final KeyStore mKeyStore; + + /** + * A new instance, delegating to {@code keyStore}. + */ + public KeyStoreProxyImpl(KeyStore keyStore) { + mKeyStore = keyStore; + } + + @Override + public boolean containsAlias(String alias) throws KeyStoreException { + return mKeyStore.containsAlias(alias); + } + + @Override + public Key getKey(String alias, char[] password) + throws KeyStoreException, NoSuchAlgorithmException, UnrecoverableKeyException { + return mKeyStore.getKey(alias, password); + } + + @Override + public void setEntry(String alias, KeyStore.Entry entry, KeyStore.ProtectionParameter protParam) + throws KeyStoreException { + mKeyStore.setEntry(alias, entry, protParam); + } +} diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncUtils.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncUtils.java index e4d2b953b61a..37aeb3af051e 100644 --- a/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncUtils.java +++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncUtils.java @@ -16,6 +16,8 @@ package com.android.server.locksettings.recoverablekeystore; +import com.android.internal.annotations.VisibleForTesting; + import java.nio.charset.StandardCharsets; import java.security.InvalidKeyException; import java.security.MessageDigest; @@ -25,6 +27,7 @@ import java.security.SecureRandom; import java.util.HashMap; import java.util.Map; +import javax.crypto.AEADBadTagException; import javax.crypto.KeyGenerator; import javax.crypto.SecretKey; @@ -45,9 +48,13 @@ public class KeySyncUtils { "V1 locally_encrypted_recovery_key".getBytes(StandardCharsets.UTF_8); private static final byte[] ENCRYPTED_APPLICATION_KEY_HEADER = "V1 encrypted_application_key".getBytes(StandardCharsets.UTF_8); + private static final byte[] RECOVERY_CLAIM_HEADER = + "V1 KF_claim".getBytes(StandardCharsets.UTF_8); private static final byte[] THM_KF_HASH_PREFIX = "THM_KF_hash".getBytes(StandardCharsets.UTF_8); + private static final int KEY_CLAIMANT_LENGTH_BYTES = 16; + /** * Encrypts the recovery key using both the lock screen hash and the remote storage's public * key. @@ -121,7 +128,7 @@ public class KeySyncUtils { */ public static SecretKey generateRecoveryKey() throws NoSuchAlgorithmException { KeyGenerator keyGenerator = KeyGenerator.getInstance(RECOVERY_KEY_ALGORITHM); - keyGenerator.init(RECOVERY_KEY_SIZE_BITS, SecureRandom.getInstanceStrong()); + keyGenerator.init(RECOVERY_KEY_SIZE_BITS, new SecureRandom()); return keyGenerator.generateKey(); } @@ -153,13 +160,100 @@ public class KeySyncUtils { } /** - * Returns a new array, the contents of which are the concatenation of {@code a} and {@code b}. + * Returns a random 16-byte key claimant. + * + * @hide + */ + public static byte[] generateKeyClaimant() { + SecureRandom secureRandom = new SecureRandom(); + byte[] key = new byte[KEY_CLAIMANT_LENGTH_BYTES]; + secureRandom.nextBytes(key); + return key; + } + + /** + * Encrypts a claim to recover a remote recovery key. + * + * @param publicKey The public key of the remote server. + * @param vaultParams Associated vault parameters. + * @param challenge The challenge issued by the server. + * @param thmKfHash The THM hash of the lock screen. + * @param keyClaimant The random key claimant. + * @return The encrypted recovery claim, to be sent to the remote server. + * @throws NoSuchAlgorithmException if any SecureBox algorithm is not present. + * @throws InvalidKeyException if the {@code publicKey} could not be used to encrypt. + * + * @hide */ - private static byte[] concat(byte[] a, byte[] b) { - byte[] result = new byte[a.length + b.length]; - System.arraycopy(a, 0, result, 0, a.length); - System.arraycopy(b, 0, result, a.length, b.length); - return result; + public static byte[] encryptRecoveryClaim( + PublicKey publicKey, + byte[] vaultParams, + byte[] challenge, + byte[] thmKfHash, + byte[] keyClaimant) throws NoSuchAlgorithmException, InvalidKeyException { + return SecureBox.encrypt( + publicKey, + /*sharedSecret=*/ null, + /*header=*/ concat(RECOVERY_CLAIM_HEADER, vaultParams, challenge), + /*payload=*/ concat(thmKfHash, keyClaimant)); + } + + /** + * Decrypts a recovery key, after having retrieved it from a remote server. + * + * @param lskfHash The lock screen hash associated with the key. + * @param encryptedRecoveryKey The encrypted key. + * @return The raw key material. + * @throws NoSuchAlgorithmException if any SecureBox algorithm is unavailable. + * @throws AEADBadTagException if the message has been tampered with or was encrypted with a + * different key. + */ + public static byte[] decryptRecoveryKey(byte[] lskfHash, byte[] encryptedRecoveryKey) + throws NoSuchAlgorithmException, InvalidKeyException, AEADBadTagException { + return SecureBox.decrypt( + /*ourPrivateKey=*/ null, + /*sharedSecret=*/ lskfHash, + /*header=*/ LOCALLY_ENCRYPTED_RECOVERY_KEY_HEADER, + /*encryptedPayload=*/ encryptedRecoveryKey); + } + + /** + * Decrypts an application key, using the recovery key. + * + * @param recoveryKey The recovery key - used to wrap all application keys. + * @param encryptedApplicationKey The application key to unwrap. + * @return The raw key material of the application key. + * @throws NoSuchAlgorithmException if any SecureBox algorithm is unavailable. + * @throws AEADBadTagException if the message has been tampered with or was encrypted with a + * different key. + */ + public static byte[] decryptApplicationKey(byte[] recoveryKey, byte[] encryptedApplicationKey) + throws NoSuchAlgorithmException, InvalidKeyException, AEADBadTagException { + return SecureBox.decrypt( + /*ourPrivateKey=*/ null, + /*sharedSecret=*/ recoveryKey, + /*header=*/ ENCRYPTED_APPLICATION_KEY_HEADER, + /*encryptedPayload=*/ encryptedApplicationKey); + } + + /** + * Returns the concatenation of all the given {@code arrays}. + */ + @VisibleForTesting + static byte[] concat(byte[]... arrays) { + int length = 0; + for (byte[] array : arrays) { + length += array.length; + } + + byte[] concatenated = new byte[length]; + int pos = 0; + for (byte[] array : arrays) { + System.arraycopy(array, /*srcPos=*/ 0, concatenated, pos, array.length); + pos += array.length; + } + + return concatenated; } // Statics only diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/PlatformEncryptionKey.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/PlatformEncryptionKey.java new file mode 100644 index 000000000000..38f5b45ea190 --- /dev/null +++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/PlatformEncryptionKey.java @@ -0,0 +1,62 @@ +/* + * 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.locksettings.recoverablekeystore; + +import android.security.keystore.AndroidKeyStoreSecretKey; + +/** + * Private key stored in AndroidKeyStore. Used to wrap recoverable keys before writing them to disk. + * + * <p>Identified by a generation ID, which increments whenever a new platform key is generated. A + * new key must be generated whenever the user disables their lock screen, as the decryption key is + * tied to lock-screen authentication. + * + * <p>One current platform key exists per profile on the device. (As each must be tied to a + * different user's lock screen.) + * + * @hide + */ +public class PlatformEncryptionKey { + + private final int mGenerationId; + private final AndroidKeyStoreSecretKey mKey; + + /** + * A new instance. + * + * @param generationId The generation ID of the key. + * @param key The secret key handle. Can be used to encrypt WITHOUT requiring screen unlock. + */ + public PlatformEncryptionKey(int generationId, AndroidKeyStoreSecretKey key) { + mGenerationId = generationId; + mKey = key; + } + + /** + * Returns the generation ID of the key. + */ + public int getGenerationId() { + return mGenerationId; + } + + /** + * Returns the actual key, which can only be used to encrypt. + */ + public AndroidKeyStoreSecretKey getKey() { + return mKey; + } +} diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/PlatformKeyManager.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/PlatformKeyManager.java new file mode 100644 index 000000000000..074c596ec727 --- /dev/null +++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/PlatformKeyManager.java @@ -0,0 +1,345 @@ +/* + * 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.locksettings.recoverablekeystore; + +import android.app.KeyguardManager; +import android.content.Context; +import android.content.SharedPreferences; +import android.os.Environment; +import android.security.keystore.AndroidKeyStoreSecretKey; +import android.security.keystore.KeyProperties; +import android.security.keystore.KeyProtection; +import android.util.Log; + +import com.android.internal.annotations.VisibleForTesting; + +import java.io.File; +import java.io.IOException; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.UnrecoverableKeyException; +import java.security.cert.CertificateException; +import java.util.Locale; + +import javax.crypto.KeyGenerator; +import javax.crypto.SecretKey; +import javax.security.auth.DestroyFailedException; + +/** + * Manages creating and checking the validity of the platform key. + * + * <p>The platform key is used to wrap the material of recoverable keys before persisting them to + * disk. It is also used to decrypt the same keys on a screen unlock, before re-wrapping them with + * a recovery key and syncing them with remote storage. + * + * <p>Each platform key has two entries in AndroidKeyStore: + * + * <ul> + * <li>Encrypt entry - this entry enables the root user to at any time encrypt. + * <li>Decrypt entry - this entry enables the root user to decrypt only after recent user + * authentication, i.e., within 15 seconds after a screen unlock. + * </ul> + * + * <p>Both entries are enabled only for AES/GCM/NoPadding Cipher algorithm. + * + * @hide + */ +public class PlatformKeyManager { + private static final String TAG = "PlatformKeyManager"; + + private static final String KEY_ALGORITHM = "AES"; + private static final int KEY_SIZE_BITS = 256; + private static final String SHARED_PREFS_KEY_GENERATION_ID = "generationId"; + private static final String SHARED_PREFS_PATH = "/system/recoverablekeystore/platform_keys.xml"; + private static final String KEY_ALIAS_PREFIX = + "com.android.server.locksettings.recoverablekeystore/platform/"; + private static final String ENCRYPT_KEY_ALIAS_SUFFIX = "encrypt"; + private static final String DECRYPT_KEY_ALIAS_SUFFIX = "decrypt"; + private static final int USER_AUTHENTICATION_VALIDITY_DURATION_SECONDS = 15; + + private final Context mContext; + private final KeyStoreProxy mKeyStore; + private final SharedPreferences mSharedPreferences; + private final int mUserId; + + private static final String ANDROID_KEY_STORE_PROVIDER = "AndroidKeyStore"; + + /** + * A new instance operating on behalf of {@code userId}, storing its prefs in the location + * defined by {@code context}. + * + * @param context This should be the context of the RecoverableKeyStoreLoader service. + * @param userId The ID of the user to whose lock screen the platform key must be bound. + * @throws KeyStoreException if failed to initialize AndroidKeyStore. + * @throws NoSuchAlgorithmException if AES is unavailable - should never happen. + * @throws InsecureUserException if the user does not have a lock screen set. + * @throws SecurityException if the caller does not have permission to write to /data/system. + * + * @hide + */ + public static PlatformKeyManager getInstance(Context context, int userId) + throws KeyStoreException, NoSuchAlgorithmException, InsecureUserException { + context = context.getApplicationContext(); + File sharedPreferencesFile = new File( + Environment.getDataDirectory().getAbsoluteFile(), SHARED_PREFS_PATH); + sharedPreferencesFile.mkdirs(); + PlatformKeyManager keyManager = new PlatformKeyManager( + userId, + context, + new KeyStoreProxyImpl(getAndLoadAndroidKeyStore()), + context.getSharedPreferences(sharedPreferencesFile, Context.MODE_PRIVATE)); + keyManager.init(); + return keyManager; + } + + @VisibleForTesting + PlatformKeyManager( + int userId, + Context context, + KeyStoreProxy keyStore, + SharedPreferences sharedPreferences) { + mUserId = userId; + mKeyStore = keyStore; + mContext = context; + mSharedPreferences = sharedPreferences; + } + + /** + * Returns the current generation ID of the platform key. This increments whenever a platform + * key has to be replaced. (e.g., because the user has removed and then re-added their lock + * screen). + * + * @hide + */ + public int getGenerationId() { + return mSharedPreferences.getInt(getGenerationIdKey(), 1); + } + + /** + * Returns {@code true} if the platform key is available. A platform key won't be available if + * the user has not set up a lock screen. + * + * @hide + */ + public boolean isAvailable() { + return mContext.getSystemService(KeyguardManager.class).isDeviceSecure(mUserId); + } + + /** + * Generates a new key and increments the generation ID. Should be invoked if the platform key + * is corrupted and needs to be rotated. + * + * @throws NoSuchAlgorithmException if AES is unavailable - should never happen. + * @throws KeyStoreException if there is an error in AndroidKeyStore. + * + * @hide + */ + public void regenerate() throws NoSuchAlgorithmException, KeyStoreException { + int generationId = getGenerationId(); + generateAndLoadKey(generationId + 1); + setGenerationId(generationId + 1); + } + + /** + * Returns the platform key used for encryption. + * + * @throws KeyStoreException if there was an AndroidKeyStore error. + * @throws UnrecoverableKeyException if the key could not be recovered. + * @throws NoSuchAlgorithmException if AES is unavailable - should never occur. + * + * @hide + */ + public PlatformEncryptionKey getEncryptKey() + throws KeyStoreException, UnrecoverableKeyException, NoSuchAlgorithmException { + int generationId = getGenerationId(); + AndroidKeyStoreSecretKey key = (AndroidKeyStoreSecretKey) mKeyStore.getKey( + getEncryptAlias(generationId), /*password=*/ null); + return new PlatformEncryptionKey(generationId, key); + } + + /** + * Returns the platform key used for decryption. Only works after a recent screen unlock. + * + * @throws KeyStoreException if there was an AndroidKeyStore error. + * @throws UnrecoverableKeyException if the key could not be recovered. + * @throws NoSuchAlgorithmException if AES is unavailable - should never occur. + * + * @hide + */ + public PlatformDecryptionKey getDecryptKey() + throws KeyStoreException, UnrecoverableKeyException, NoSuchAlgorithmException { + int generationId = getGenerationId(); + AndroidKeyStoreSecretKey key = (AndroidKeyStoreSecretKey) mKeyStore.getKey( + getDecryptAlias(generationId), /*password=*/ null); + return new PlatformDecryptionKey(generationId, key); + } + + /** + * Initializes the class. If there is no current platform key, and the user has a lock screen + * set, will create the platform key and set the generation ID. + * + * @throws KeyStoreException if there was an error in AndroidKeyStore. + * @throws NoSuchAlgorithmException if AES is unavailable - should never happen. + * + * @hide + */ + public void init() throws KeyStoreException, NoSuchAlgorithmException, InsecureUserException { + if (!isAvailable()) { + throw new InsecureUserException(String.format( + Locale.US, "%d does not have a lock screen set.", mUserId)); + } + + int generationId = getGenerationId(); + if (isKeyLoaded(generationId)) { + Log.i(TAG, String.format( + Locale.US, "Platform key generation %d exists already.", generationId)); + return; + } + if (generationId == 1) { + Log.i(TAG, "Generating initial platform ID."); + } else { + Log.w(TAG, String.format(Locale.US, "Platform generation ID was %d but no " + + "entry was present in AndroidKeyStore. Generating fresh key.", generationId)); + } + + generateAndLoadKey(generationId); + } + + /** + * Returns the alias of the encryption key with the specific {@code generationId} in the + * AndroidKeyStore. + * + * <p>These IDs look as follows: + * {@code com.security.recoverablekeystore/platform/<user id>/<generation id>/encrypt} + * + * @param generationId The generation ID. + * @return The alias. + */ + private String getEncryptAlias(int generationId) { + return KEY_ALIAS_PREFIX + mUserId + "/" + generationId + "/" + ENCRYPT_KEY_ALIAS_SUFFIX; + } + + /** + * Returns the alias of the decryption key with the specific {@code generationId} in the + * AndroidKeyStore. + * + * <p>These IDs look as follows: + * {@code com.security.recoverablekeystore/platform/<user id>/<generation id>/decrypt} + * + * @param generationId The generation ID. + * @return The alias. + */ + private String getDecryptAlias(int generationId) { + return KEY_ALIAS_PREFIX + mUserId + "/" + generationId + "/" + DECRYPT_KEY_ALIAS_SUFFIX; + } + + /** + * Sets the current generation ID to {@code generationId}. + */ + private void setGenerationId(int generationId) { + mSharedPreferences.edit().putInt(getGenerationIdKey(), generationId).commit(); + } + + /** + * Returns the current user's generation ID key in the shared preferences. + */ + private String getGenerationIdKey() { + return SHARED_PREFS_KEY_GENERATION_ID + "/" + mUserId; + } + + /** + * Returns {@code true} if a key has been loaded with the given {@code generationId} into + * AndroidKeyStore. + * + * @throws KeyStoreException if there was an error checking AndroidKeyStore. + */ + private boolean isKeyLoaded(int generationId) throws KeyStoreException { + return mKeyStore.containsAlias(getEncryptAlias(generationId)) + && mKeyStore.containsAlias(getDecryptAlias(generationId)); + } + + /** + * Generates a new 256-bit AES key, and loads it into AndroidKeyStore with the given + * {@code generationId} determining its aliases. + * + * @throws NoSuchAlgorithmException if AES is unavailable. This should never happen, as it is + * available since API version 1. + * @throws KeyStoreException if there was an issue loading the keys into AndroidKeyStore. + */ + private void generateAndLoadKey(int generationId) + throws NoSuchAlgorithmException, KeyStoreException { + String encryptAlias = getEncryptAlias(generationId); + String decryptAlias = getDecryptAlias(generationId); + SecretKey secretKey = generateAesKey(); + + mKeyStore.setEntry( + encryptAlias, + new KeyStore.SecretKeyEntry(secretKey), + new KeyProtection.Builder(KeyProperties.PURPOSE_ENCRYPT) + .setBlockModes(KeyProperties.BLOCK_MODE_GCM) + .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE) + .build()); + mKeyStore.setEntry( + decryptAlias, + new KeyStore.SecretKeyEntry(secretKey), + new KeyProtection.Builder(KeyProperties.PURPOSE_DECRYPT) + .setUserAuthenticationRequired(true) + .setUserAuthenticationValidityDurationSeconds( + USER_AUTHENTICATION_VALIDITY_DURATION_SECONDS) + .setBlockModes(KeyProperties.BLOCK_MODE_GCM) + .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE) + .setBoundToSpecificSecureUserId(mUserId) + .build()); + + try { + secretKey.destroy(); + } catch (DestroyFailedException e) { + Log.w(TAG, "Failed to destroy in-memory platform key.", e); + } + } + + /** + * Generates a new 256-bit AES key, in software. + * + * @return The software-generated AES key. + * @throws NoSuchAlgorithmException if AES key generation is not available. This should never + * happen, as AES has been supported since API level 1. + */ + private static SecretKey generateAesKey() throws NoSuchAlgorithmException { + KeyGenerator keyGenerator = KeyGenerator.getInstance(KEY_ALGORITHM); + keyGenerator.init(KEY_SIZE_BITS); + return keyGenerator.generateKey(); + } + + /** + * Returns AndroidKeyStore-provided {@link KeyStore}, having already invoked + * {@link KeyStore#load(KeyStore.LoadStoreParameter)}. + * + * @throws KeyStoreException if there was a problem getting or initializing the key store. + */ + private static KeyStore getAndLoadAndroidKeyStore() throws KeyStoreException { + KeyStore keyStore = KeyStore.getInstance(ANDROID_KEY_STORE_PROVIDER); + try { + keyStore.load(/*param=*/ null); + } catch (CertificateException | IOException | NoSuchAlgorithmException e) { + // Should never happen. + throw new KeyStoreException("Unable to load keystore.", e); + } + return keyStore; + } +} diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyGenerator.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyGenerator.java index 40c788997ba5..54deec27f423 100644 --- a/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyGenerator.java +++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyGenerator.java @@ -56,7 +56,7 @@ public class RecoverableKeyGenerator { * @hide */ public static RecoverableKeyGenerator newInstance( - AndroidKeyStoreSecretKey platformKey, RecoverableKeyStorage recoverableKeyStorage) + PlatformEncryptionKey platformKey, RecoverableKeyStorage recoverableKeyStorage) throws NoSuchAlgorithmException { // NB: This cannot use AndroidKeyStore as the provider, as we need access to the raw key // material, so that it can be synced to disk in encrypted form. @@ -66,11 +66,11 @@ public class RecoverableKeyGenerator { private final KeyGenerator mKeyGenerator; private final RecoverableKeyStorage mRecoverableKeyStorage; - private final AndroidKeyStoreSecretKey mPlatformKey; + private final PlatformEncryptionKey mPlatformKey; private RecoverableKeyGenerator( KeyGenerator keyGenerator, - AndroidKeyStoreSecretKey platformKey, + PlatformEncryptionKey platformKey, RecoverableKeyStorage recoverableKeyStorage) { mKeyGenerator = keyGenerator; mRecoverableKeyStorage = recoverableKeyStorage; diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java new file mode 100644 index 000000000000..e459f288b4c0 --- /dev/null +++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java @@ -0,0 +1,211 @@ +/* + * 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.locksettings.recoverablekeystore; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.Context; +import android.os.Binder; +import android.os.RemoteException; +import android.os.ServiceSpecificException; +import android.os.UserHandle; + +import android.security.recoverablekeystore.KeyEntryRecoveryData; +import android.security.recoverablekeystore.KeyStoreRecoveryData; +import android.security.recoverablekeystore.KeyStoreRecoveryMetadata; +import android.security.recoverablekeystore.RecoverableKeyStoreLoader; + +import com.android.internal.annotations.VisibleForTesting; + +import java.util.ArrayList; +import java.util.List; + +/** + * Class with {@link RecoverableKeyStoreLoader} API implementation and internal methods to interact + * with {@code LockSettingsService}. + * + * @hide + */ +public class RecoverableKeyStoreManager { + private static final String TAG = "RecoverableKeyStoreManager"; + + private static RecoverableKeyStoreManager mInstance; + private Context mContext; + + /** + * Returns a new or existing instance. + * + * @hide + */ + public static synchronized RecoverableKeyStoreManager getInstance(Context mContext) { + if (mInstance == null) { + mInstance = new RecoverableKeyStoreManager(mContext); + } + return mInstance; + } + + @VisibleForTesting + RecoverableKeyStoreManager(Context context) { + mContext = context; + } + + public int initRecoveryService( + @NonNull String rootCertificateAlias, @NonNull byte[] signedPublicKeyList, int userId) + throws RemoteException { + checkRecoverKeyStorePermission(); + // TODO open /system/etc/security/... cert file + throw new UnsupportedOperationException(); + } + + /** + * Gets all data necessary to recover application keys on new device. + * + * @return recovery data + * @hide + */ + public KeyStoreRecoveryData getRecoveryData(@NonNull byte[] account, int userId) + throws RemoteException { + checkRecoverKeyStorePermission(); + final int callingUid = Binder.getCallingUid(); // Recovery agent uid. + final int callingUserId = UserHandle.getCallingUserId(); + final long callingIdentiy = Binder.clearCallingIdentity(); + try { + // TODO: Return the latest snapshot for the calling recovery agent. + } finally { + Binder.restoreCallingIdentity(callingIdentiy); + } + + // KeyStoreRecoveryData without application keys and empty recovery blob. + KeyStoreRecoveryData recoveryData = + new KeyStoreRecoveryData( + /*snapshotVersion=*/ 1, + new ArrayList<KeyStoreRecoveryMetadata>(), + new ArrayList<KeyEntryRecoveryData>(), + /*encryptedRecoveryKeyBlob=*/ new byte[] {}); + throw new ServiceSpecificException( + RecoverableKeyStoreLoader.UNINITIALIZED_RECOVERY_PUBLIC_KEY); + } + + public void setServerParameters(long serverParameters, int userId) throws RemoteException { + checkRecoverKeyStorePermission(); + throw new UnsupportedOperationException(); + } + + public void setRecoveryStatus( + @NonNull String packageName, @Nullable String[] aliases, int status, int userId) + throws RemoteException { + checkRecoverKeyStorePermission(); + throw new UnsupportedOperationException(); + } + + /** + * Sets recovery secrets list used by all recovery agents for given {@code userId} + * + * @hide + */ + public void setRecoverySecretTypes( + @NonNull @KeyStoreRecoveryMetadata.UserSecretType int[] secretTypes, int userId) + throws RemoteException { + checkRecoverKeyStorePermission(); + throw new UnsupportedOperationException(); + } + + /** + * Gets secret types necessary to create Recovery Data. + * + * @return secret types + * @hide + */ + public int[] getRecoverySecretTypes(int userId) throws RemoteException { + checkRecoverKeyStorePermission(); + throw new UnsupportedOperationException(); + } + + /** + * Gets secret types RecoverableKeyStoreLoaders is waiting for to create new Recovery Data. + * + * @return secret types + * @hide + */ + public int[] getPendingRecoverySecretTypes(int userId) throws RemoteException { + checkRecoverKeyStorePermission(); + throw new UnsupportedOperationException(); + } + + public void recoverySecretAvailable( + @NonNull KeyStoreRecoveryMetadata recoverySecret, int userId) throws RemoteException { + final int callingUid = Binder.getCallingUid(); // Recovery agent uid. + if (recoverySecret.getLockScreenUiFormat() == KeyStoreRecoveryMetadata.TYPE_LOCKSCREEN) { + throw new SecurityException( + "Caller " + callingUid + "is not allowed to set lock screen secret"); + } + checkRecoverKeyStorePermission(); + // TODO: add hook from LockSettingsService to set lock screen secret. + throw new UnsupportedOperationException(); + } + + /** + * Initializes recovery session. + * + * @return recovery claim + * @hide + */ + public byte[] startRecoverySession( + @NonNull String sessionId, + @NonNull byte[] verifierPublicKey, + @NonNull byte[] vaultParams, + @NonNull byte[] vaultChallenge, + @NonNull List<KeyStoreRecoveryMetadata> secrets, + int userId) + throws RemoteException { + checkRecoverKeyStorePermission(); + throw new UnsupportedOperationException(); + } + + public void recoverKeys( + @NonNull String sessionId, + @NonNull byte[] recoveryKeyBlob, + @NonNull List<KeyEntryRecoveryData> applicationKeys, + int userId) + throws RemoteException { + checkRecoverKeyStorePermission(); + throw new UnsupportedOperationException(); + } + + /** This function can only be used inside LockSettingsService. */ + public void lockScreenSecretAvailable( + @KeyStoreRecoveryMetadata.LockScreenUiFormat int type, + String unencryptedPassword, + int userId) { + // TODO: compute SHA256 or Argon2id depending on secret type. + throw new UnsupportedOperationException(); + } + + /** This function can only be used inside LockSettingsService. */ + public void lockScreenSecretChanged( + @KeyStoreRecoveryMetadata.LockScreenUiFormat int type, + @Nullable String unencryptedPassword, + int userId) { + throw new UnsupportedOperationException(); + } + + private void checkRecoverKeyStorePermission() { + mContext.enforceCallingOrSelfPermission( + RecoverableKeyStoreLoader.PERMISSION_RECOVER_KEYSTORE, + "Caller " + Binder.getCallingUid() + " doesn't have RecoverKeyStore permission."); + } +} diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/SecureBox.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/SecureBox.java index 457fdc14c7f6..742cb4591864 100644 --- a/services/core/java/com/android/server/locksettings/recoverablekeystore/SecureBox.java +++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/SecureBox.java @@ -16,10 +16,15 @@ package com.android.server.locksettings.recoverablekeystore; +import android.annotation.Nullable; + import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; import java.security.PublicKey; +import javax.crypto.AEADBadTagException; + /** * TODO(b/69056040) Add implementation of SecureBox. This is a placeholder so KeySyncUtils compiles. * @@ -32,8 +37,25 @@ public class SecureBox { * @hide */ public static byte[] encrypt( - PublicKey theirPublicKey, byte[] sharedSecret, byte[] header, byte[] payload) + @Nullable PublicKey theirPublicKey, + @Nullable byte[] sharedSecret, + @Nullable byte[] header, + @Nullable byte[] payload) throws NoSuchAlgorithmException, InvalidKeyException { throw new UnsupportedOperationException("Needs to be implemented."); } + + /** + * TODO(b/69056040) Add implementation of decrypt. + * + * @hide + */ + public static byte[] decrypt( + @Nullable PrivateKey ourPrivateKey, + @Nullable byte[] sharedSecret, + @Nullable byte[] header, + byte[] encryptedPayload) + throws NoSuchAlgorithmException, InvalidKeyException, AEADBadTagException { + throw new UnsupportedOperationException("Needs to be implemented."); + } } diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/WrappedKey.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/WrappedKey.java index f18e7961de5f..dfa173c8d463 100644 --- a/services/core/java/com/android/server/locksettings/recoverablekeystore/WrappedKey.java +++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/WrappedKey.java @@ -44,6 +44,7 @@ public class WrappedKey { private static final String APPLICATION_KEY_ALGORITHM = "AES"; private static final int GCM_TAG_LENGTH_BITS = 128; + private final int mPlatformKeyGenerationId; private final byte[] mNonce; private final byte[] mKeyMaterial; @@ -55,8 +56,8 @@ public class WrappedKey { * {@link android.security.keystore.AndroidKeyStoreKey} for an example of a key that does * not expose its key material. */ - public static WrappedKey fromSecretKey( - SecretKey wrappingKey, SecretKey key) throws InvalidKeyException, KeyStoreException { + public static WrappedKey fromSecretKey(PlatformEncryptionKey wrappingKey, SecretKey key) + throws InvalidKeyException, KeyStoreException { if (key.getEncoded() == null) { throw new InvalidKeyException( "key does not expose encoded material. It cannot be wrapped."); @@ -70,7 +71,7 @@ public class WrappedKey { "Android does not support AES/GCM/NoPadding. This should never happen."); } - cipher.init(Cipher.WRAP_MODE, wrappingKey); + cipher.init(Cipher.WRAP_MODE, wrappingKey.getKey()); byte[] encryptedKeyMaterial; try { encryptedKeyMaterial = cipher.wrap(key); @@ -90,7 +91,10 @@ public class WrappedKey { } } - return new WrappedKey(/*mNonce=*/ cipher.getIV(), /*mKeyMaterial=*/ encryptedKeyMaterial); + return new WrappedKey( + /*nonce=*/ cipher.getIV(), + /*keyMaterial=*/ encryptedKeyMaterial, + /*platformKeyGenerationId=*/ wrappingKey.getGenerationId()); } /** @@ -98,12 +102,14 @@ public class WrappedKey { * * @param nonce The nonce with which the key material was encrypted. * @param keyMaterial The encrypted bytes of the key material. + * @param platformKeyGenerationId The generation ID of the key used to wrap this key. * * @hide */ - public WrappedKey(byte[] nonce, byte[] keyMaterial) { + public WrappedKey(byte[] nonce, byte[] keyMaterial, int platformKeyGenerationId) { mNonce = nonce; mKeyMaterial = keyMaterial; + mPlatformKeyGenerationId = platformKeyGenerationId; } /** @@ -131,8 +137,7 @@ public class WrappedKey { * @hide */ public int getPlatformKeyGenerationId() { - // TODO(robertberry) Implement. See ag/3362855. - return 1; + return mPlatformKeyGenerationId; } /** diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDb.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDb.java new file mode 100644 index 000000000000..79865337d4c1 --- /dev/null +++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDb.java @@ -0,0 +1,186 @@ +/* + * 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.locksettings.recoverablekeystore.storage; + +import android.annotation.Nullable; +import android.content.ContentValues; +import android.content.Context; +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; +import android.util.Log; + +import com.android.server.locksettings.recoverablekeystore.WrappedKey; +import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDbContract.KeysEntry; + +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; + +/** + * Database of recoverable key information. + * + * @hide + */ +public class RecoverableKeyStoreDb { + private static final String TAG = "RecoverableKeyStoreDb"; + private static final int IDLE_TIMEOUT_SECONDS = 30; + + private final RecoverableKeyStoreDbHelper mKeyStoreDbHelper; + + /** + * A new instance, storing the database in the user directory of {@code context}. + * + * @hide + */ + public static RecoverableKeyStoreDb newInstance(Context context) { + RecoverableKeyStoreDbHelper helper = new RecoverableKeyStoreDbHelper(context); + helper.setWriteAheadLoggingEnabled(true); + helper.setIdleConnectionTimeout(IDLE_TIMEOUT_SECONDS); + return new RecoverableKeyStoreDb(helper); + } + + private RecoverableKeyStoreDb(RecoverableKeyStoreDbHelper keyStoreDbHelper) { + this.mKeyStoreDbHelper = keyStoreDbHelper; + } + + /** + * Inserts a key into the database. + * + * @param uid Uid of the application to whom the key belongs. + * @param alias The alias of the key in the AndroidKeyStore. + * @param wrappedKey The wrapped key. + * @return The primary key of the inserted row, or -1 if failed. + * + * @hide + */ + public long insertKey(int uid, String alias, WrappedKey wrappedKey) { + SQLiteDatabase db = mKeyStoreDbHelper.getWritableDatabase(); + ContentValues values = new ContentValues(); + values.put(KeysEntry.COLUMN_NAME_UID, uid); + values.put(KeysEntry.COLUMN_NAME_ALIAS, alias); + values.put(KeysEntry.COLUMN_NAME_NONCE, wrappedKey.getNonce()); + values.put(KeysEntry.COLUMN_NAME_WRAPPED_KEY, wrappedKey.getKeyMaterial()); + values.put(KeysEntry.COLUMN_NAME_LAST_SYNCED_AT, -1); + values.put(KeysEntry.COLUMN_NAME_GENERATION_ID, wrappedKey.getPlatformKeyGenerationId()); + return db.replace(KeysEntry.TABLE_NAME, /*nullColumnHack=*/ null, values); + } + + /** + * Gets the key with {@code alias} for the app with {@code uid}. + * + * @hide + */ + @Nullable public WrappedKey getKey(int uid, String alias) { + SQLiteDatabase db = mKeyStoreDbHelper.getReadableDatabase(); + String[] projection = { + KeysEntry._ID, + KeysEntry.COLUMN_NAME_NONCE, + KeysEntry.COLUMN_NAME_WRAPPED_KEY, + KeysEntry.COLUMN_NAME_GENERATION_ID}; + String selection = + KeysEntry.COLUMN_NAME_UID + " = ? AND " + + KeysEntry.COLUMN_NAME_ALIAS + " = ?"; + String[] selectionArguments = { Integer.toString(uid), alias }; + + try ( + Cursor cursor = db.query( + KeysEntry.TABLE_NAME, + projection, + selection, + selectionArguments, + /*groupBy=*/ null, + /*having=*/ null, + /*orderBy=*/ null) + ) { + int count = cursor.getCount(); + if (count == 0) { + return null; + } + if (count > 1) { + Log.wtf(TAG, + String.format(Locale.US, + "%d WrappedKey entries found for uid=%d alias='%s'. " + + "Should only ever be 0 or 1.", count, uid, alias)); + return null; + } + cursor.moveToFirst(); + byte[] nonce = cursor.getBlob( + cursor.getColumnIndexOrThrow(KeysEntry.COLUMN_NAME_NONCE)); + byte[] keyMaterial = cursor.getBlob( + cursor.getColumnIndexOrThrow(KeysEntry.COLUMN_NAME_WRAPPED_KEY)); + int generationId = cursor.getInt( + cursor.getColumnIndexOrThrow(KeysEntry.COLUMN_NAME_GENERATION_ID)); + return new WrappedKey(nonce, keyMaterial, generationId); + } + } + + /** + * Returns all keys for the given {@code uid} and {@code platformKeyGenerationId}. + * + * @param uid User id of the profile to which all the keys are associated. + * @param platformKeyGenerationId The generation ID of the platform key that wrapped these keys. + * (i.e., this should be the most recent generation ID, as older platform keys are not + * usable.) + * + * @hide + */ + public Map<String, WrappedKey> getAllKeys(int uid, int platformKeyGenerationId) { + SQLiteDatabase db = mKeyStoreDbHelper.getReadableDatabase(); + String[] projection = { + KeysEntry._ID, + KeysEntry.COLUMN_NAME_NONCE, + KeysEntry.COLUMN_NAME_WRAPPED_KEY, + KeysEntry.COLUMN_NAME_ALIAS}; + String selection = + KeysEntry.COLUMN_NAME_UID + " = ? AND " + + KeysEntry.COLUMN_NAME_GENERATION_ID + " = ?"; + String[] selectionArguments = { + Integer.toString(uid), Integer.toString(platformKeyGenerationId) }; + + try ( + Cursor cursor = db.query( + KeysEntry.TABLE_NAME, + projection, + selection, + selectionArguments, + /*groupBy=*/ null, + /*having=*/ null, + /*orderBy=*/ null) + ) { + HashMap<String, WrappedKey> keys = new HashMap<>(); + while (cursor.moveToNext()) { + byte[] nonce = cursor.getBlob( + cursor.getColumnIndexOrThrow(KeysEntry.COLUMN_NAME_NONCE)); + byte[] keyMaterial = cursor.getBlob( + cursor.getColumnIndexOrThrow(KeysEntry.COLUMN_NAME_WRAPPED_KEY)); + String alias = cursor.getString( + cursor.getColumnIndexOrThrow(KeysEntry.COLUMN_NAME_ALIAS)); + keys.put(alias, new WrappedKey(nonce, keyMaterial, platformKeyGenerationId)); + } + return keys; + } + } + + /** + * Closes all open connections to the database. + */ + public void close() { + mKeyStoreDbHelper.close(); + } + + // TODO: Add method for updating the 'last synced' time. +} diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbContract.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbContract.java new file mode 100644 index 000000000000..c54d0a6f4f77 --- /dev/null +++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbContract.java @@ -0,0 +1,61 @@ +/* + * 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.locksettings.recoverablekeystore.storage; + +import android.provider.BaseColumns; + +/** + * Contract for recoverable key database. Describes the tables present. + */ +class RecoverableKeyStoreDbContract { + /** + * Table holding wrapped keys, and information about when they were last synced. + */ + static class KeysEntry implements BaseColumns { + static final String TABLE_NAME = "keys"; + + /** + * The uid of the application that generated the key. + */ + static final String COLUMN_NAME_UID = "uid"; + + /** + * The alias of the key, as set in AndroidKeyStore. + */ + static final String COLUMN_NAME_ALIAS = "alias"; + + /** + * Nonce with which the key was encrypted. + */ + static final String COLUMN_NAME_NONCE = "nonce"; + + /** + * Encrypted bytes of the key. + */ + static final String COLUMN_NAME_WRAPPED_KEY = "wrapped_key"; + + /** + * Generation ID of the platform key that was used to encrypt this key. + */ + static final String COLUMN_NAME_GENERATION_ID = "platform_key_generation_id"; + + /** + * Timestamp of when this key was last synced with remote storage, or -1 if never synced. + */ + static final String COLUMN_NAME_LAST_SYNCED_AT = "last_synced_at"; + } +} diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbHelper.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbHelper.java new file mode 100644 index 000000000000..e3783c496ee2 --- /dev/null +++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbHelper.java @@ -0,0 +1,43 @@ +package com.android.server.locksettings.recoverablekeystore.storage; + +import android.content.Context; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteOpenHelper; + +import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDbContract.KeysEntry; + +/** + * Helper for creating the recoverable key database. + */ +class RecoverableKeyStoreDbHelper extends SQLiteOpenHelper { + private static final int DATABASE_VERSION = 1; + private static final String DATABASE_NAME = "recoverablekeystore.db"; + + private static final String SQL_CREATE_ENTRIES = + "CREATE TABLE " + KeysEntry.TABLE_NAME + "( " + + KeysEntry._ID + " INTEGER PRIMARY KEY," + + KeysEntry.COLUMN_NAME_UID + " INTEGER UNIQUE," + + KeysEntry.COLUMN_NAME_ALIAS + " TEXT UNIQUE," + + KeysEntry.COLUMN_NAME_NONCE + " BLOB," + + KeysEntry.COLUMN_NAME_WRAPPED_KEY + " BLOB," + + KeysEntry.COLUMN_NAME_GENERATION_ID + " INTEGER," + + KeysEntry.COLUMN_NAME_LAST_SYNCED_AT + " INTEGER)"; + + private static final String SQL_DELETE_ENTRIES = + "DROP TABLE IF EXISTS " + KeysEntry.TABLE_NAME; + + RecoverableKeyStoreDbHelper(Context context) { + super(context, DATABASE_NAME, null, DATABASE_VERSION); + } + + @Override + public void onCreate(SQLiteDatabase db) { + db.execSQL(SQL_CREATE_ENTRIES); + } + + @Override + public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { + db.execSQL(SQL_DELETE_ENTRIES); + onCreate(db); + } +} diff --git a/services/core/java/com/android/server/media/MediaRouterService.java b/services/core/java/com/android/server/media/MediaRouterService.java index 0b089fbc6129..384efdda88d7 100644 --- a/services/core/java/com/android/server/media/MediaRouterService.java +++ b/services/core/java/com/android/server/media/MediaRouterService.java @@ -21,6 +21,9 @@ import com.android.server.Watchdog; import android.annotation.NonNull; import android.app.ActivityManager; +import android.bluetooth.BluetoothA2dp; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothProfile; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; @@ -96,21 +99,23 @@ public final class MediaRouterService extends IMediaRouterService.Stub private final ArrayMap<IBinder, ClientRecord> mAllClientRecords = new ArrayMap<IBinder, ClientRecord>(); private int mCurrentUserId = -1; - private boolean mGlobalBluetoothA2dpOn = false; private final IAudioService mAudioService; private final AudioPlayerStateMonitor mAudioPlayerStateMonitor; private final Handler mHandler = new Handler(); - private final AudioRoutesInfo mAudioRoutesInfo = new AudioRoutesInfo(); private final IntArray mActivePlayerMinPriorityQueue = new IntArray(); private final IntArray mActivePlayerUidMinPriorityQueue = new IntArray(); + private final BroadcastReceiver mReceiver = new MediaRouterServiceBroadcastReceiver(); + BluetoothDevice mBluetoothDevice; + int mAudioRouteMainType = AudioRoutesInfo.MAIN_SPEAKER; + boolean mGlobalBluetoothA2dpOn = false; + public MediaRouterService(Context context) { mContext = context; Watchdog.getInstance().addMonitor(this); mAudioService = IAudioService.Stub.asInterface( ServiceManager.getService(Context.AUDIO_SERVICE)); - mAudioPlayerStateMonitor = AudioPlayerStateMonitor.getInstance(); mAudioPlayerStateMonitor.registerListener( new AudioPlayerStateMonitor.OnAudioPlayerActiveStateChangedListener() { @@ -170,44 +175,30 @@ public final class MediaRouterService extends IMediaRouterService.Stub @Override public void dispatchAudioRoutesChanged(final AudioRoutesInfo newRoutes) { synchronized (mLock) { - if (newRoutes.mainType != mAudioRoutesInfo.mainType) { + if (newRoutes.mainType != mAudioRouteMainType) { if ((newRoutes.mainType & (AudioRoutesInfo.MAIN_HEADSET | AudioRoutesInfo.MAIN_HEADPHONES | AudioRoutesInfo.MAIN_USB)) == 0) { // headset was plugged out. - mGlobalBluetoothA2dpOn = newRoutes.bluetoothName != null; + mGlobalBluetoothA2dpOn = mBluetoothDevice != null; } else { // headset was plugged in. mGlobalBluetoothA2dpOn = false; } - mAudioRoutesInfo.mainType = newRoutes.mainType; - } - if (!TextUtils.equals( - newRoutes.bluetoothName, mAudioRoutesInfo.bluetoothName)) { - if (newRoutes.bluetoothName == null) { - // BT was disconnected. - mGlobalBluetoothA2dpOn = false; - } else { - // BT was connected or changed. - mGlobalBluetoothA2dpOn = true; - } - mAudioRoutesInfo.bluetoothName = newRoutes.bluetoothName; + mAudioRouteMainType = newRoutes.mainType; } - // Although a Bluetooth device is connected before a new audio playback is - // started, dispatchAudioRoutChanged() can be called after - // onAudioPlayerActiveStateChanged(). That causes restoreBluetoothA2dp() - // is called before mGlobalBluetoothA2dpOn is updated. - // Calling restoreBluetoothA2dp() here could prevent that. - restoreBluetoothA2dp(); + // The new audio routes info could be delivered with several seconds delay. + // In order to avoid such delay, Bluetooth device info will be updated + // via MediaRouterServiceBroadcastReceiver. } } }); } catch (RemoteException e) { Slog.w(TAG, "RemoteException in the audio service."); } - synchronized (mLock) { - mGlobalBluetoothA2dpOn = (audioRoutes != null && audioRoutes.bluetoothName != null); - } + + IntentFilter intentFilter = new IntentFilter(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED); + context.registerReceiverAsUser(mReceiver, UserHandle.ALL, intentFilter, null, null); } public void systemRunning() { @@ -415,14 +406,12 @@ public final class MediaRouterService extends IMediaRouterService.Stub void restoreBluetoothA2dp() { try { - boolean btConnected = false; boolean a2dpOn = false; synchronized (mLock) { - btConnected = mAudioRoutesInfo.bluetoothName != null; a2dpOn = mGlobalBluetoothA2dpOn; } // We don't need to change a2dp status when bluetooth is not connected. - if (btConnected) { + if (mBluetoothDevice != null) { Slog.v(TAG, "restoreBluetoothA2dp(" + a2dpOn + ")"); mAudioService.setBluetoothA2dpOn(a2dpOn); } @@ -661,6 +650,25 @@ public final class MediaRouterService extends IMediaRouterService.Stub return false; } + final class MediaRouterServiceBroadcastReceiver extends BroadcastReceiver { + @Override + public void onReceive(Context context, Intent intent) { + if (intent.getAction().equals(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED)) { + int state = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, + BluetoothProfile.STATE_DISCONNECTED); + if (state == BluetoothProfile.STATE_DISCONNECTED) { + mGlobalBluetoothA2dpOn = false; + mBluetoothDevice = null; + } else if (state == BluetoothProfile.STATE_CONNECTED) { + mGlobalBluetoothA2dpOn = true; + mBluetoothDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); + // To ensure that BT A2DP is on, call restoreBluetoothA2dp(). + restoreBluetoothA2dp(); + } + } + } + } + /** * Information about a particular client of the media router. * The contents of this object is guarded by mLock. diff --git a/services/core/java/com/android/server/stats/StatsCompanionService.java b/services/core/java/com/android/server/stats/StatsCompanionService.java index 1ce140004bcd..b31f4b3fca63 100644 --- a/services/core/java/com/android/server/stats/StatsCompanionService.java +++ b/services/core/java/com/android/server/stats/StatsCompanionService.java @@ -455,7 +455,7 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { Slog.d(TAG, "Pulling " + tagId); switch (tagId) { - case StatsLog.WIFI_BYTES_TRANSFERRED: { + case StatsLog.WIFI_BYTES_TRANSFER: { long token = Binder.clearCallingIdentity(); try { // TODO: Consider caching the following call to get BatteryStatsInternal. @@ -476,7 +476,7 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { } break; } - case StatsLog.MOBILE_BYTES_TRANSFERRED: { + case StatsLog.MOBILE_BYTES_TRANSFER: { long token = Binder.clearCallingIdentity(); try { BatteryStatsInternal bs = LocalServices.getService(BatteryStatsInternal.class); @@ -496,7 +496,7 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { } break; } - case StatsLog.WIFI_BYTES_TRANSFERRED_BY_FG_BG: { + case StatsLog.WIFI_BYTES_TRANSFER_BY_FG_BG: { long token = Binder.clearCallingIdentity(); try { BatteryStatsInternal bs = LocalServices.getService(BatteryStatsInternal.class); @@ -516,7 +516,7 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { } break; } - case StatsLog.MOBILE_BYTES_TRANSFERRED_BY_FG_BG: { + case StatsLog.MOBILE_BYTES_TRANSFER_BY_FG_BG: { long token = Binder.clearCallingIdentity(); try { BatteryStatsInternal bs = LocalServices.getService(BatteryStatsInternal.class); @@ -536,7 +536,7 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { } break; } - case StatsLog.KERNEL_WAKELOCK_PULLED: { + case StatsLog.KERNEL_WAKELOCK: { final KernelWakelockStats wakelockStats = mKernelWakelockReader.readKernelWakelockStats(mTmpWakelockStats); List<StatsLogEventWrapper> ret = new ArrayList(); @@ -552,7 +552,7 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { } return ret.toArray(new StatsLogEventWrapper[ret.size()]); } - case StatsLog.CPU_TIME_PER_FREQ_PULLED: { + case StatsLog.CPU_TIME_PER_FREQ: { List<StatsLogEventWrapper> ret = new ArrayList(); for (int cluster = 0; cluster < mKernelCpuSpeedReaders.length; cluster++) { long[] clusterTimeMs = mKernelCpuSpeedReaders[cluster].readDelta(); @@ -568,7 +568,7 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { } return ret.toArray(new StatsLogEventWrapper[ret.size()]); } - case StatsLog.WIFI_ACTIVITY_ENERGY_INFO_PULLED: { + case StatsLog.WIFI_ACTIVITY_ENERGY_INFO: { List<StatsLogEventWrapper> ret = new ArrayList(); long token = Binder.clearCallingIdentity(); if (mWifiManager == null) { @@ -596,7 +596,7 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { } break; } - case StatsLog.MODEM_ACTIVITY_INFO_PULLED: { + case StatsLog.MODEM_ACTIVITY_INFO: { List<StatsLogEventWrapper> ret = new ArrayList(); long token = Binder.clearCallingIdentity(); if (mTelephony == null) { diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java index 7b0ed0d06739..8e916ad3180e 100644 --- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java +++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java @@ -675,6 +675,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub final SparseArray<Boolean> mUserRestorecon = new SparseArray<Boolean>(); int mCurrentUserId; + boolean mInAmbientMode; static class WallpaperData { @@ -953,6 +954,13 @@ public class WallpaperManagerService extends IWallpaperManager.Stub } mPaddingChanged = false; } + if (mInfo != null && mInfo.getSupportsAmbientMode()) { + try { + mEngine.setInAmbientMode(mInAmbientMode); + } catch (RemoteException e) { + Slog.w(TAG, "Failed to set ambient mode state", e); + } + } try { // This will trigger onComputeColors in the wallpaper engine. // It's fine to be locked in here since the binder is oneway. @@ -1743,6 +1751,28 @@ public class WallpaperManagerService extends IWallpaperManager.Stub } } + public void setInAmbientMode(boolean inAmbienMode) { + final IWallpaperEngine engine; + synchronized (mLock) { + mInAmbientMode = inAmbienMode; + final WallpaperData data = mWallpaperMap.get(mCurrentUserId); + if (data != null && data.connection != null && data.connection.mInfo != null + && data.connection.mInfo.getSupportsAmbientMode()) { + engine = data.connection.mEngine; + } else { + engine = null; + } + } + + if (engine != null) { + try { + engine.setInAmbientMode(inAmbienMode); + } catch (RemoteException e) { + // Cannot talk to wallpaper engine. + } + } + } + @Override public boolean setLockWallpaperCallback(IWallpaperManagerCallback cb) { checkPermission(android.Manifest.permission.INTERNAL_SYSTEM_WINDOW); diff --git a/services/core/jni/com_android_server_am_BatteryStatsService.cpp b/services/core/jni/com_android_server_am_BatteryStatsService.cpp index 39cc95368d71..02ad6c71b586 100644 --- a/services/core/jni/com_android_server_am_BatteryStatsService.cpp +++ b/services/core/jni/com_android_server_am_BatteryStatsService.cpp @@ -101,7 +101,7 @@ static jint nativeWaitWakeup(JNIEnv *env, jobject clazz, jobject outBuf) return -1; } ALOGV("Registering callback..."); - set_wakeup_callback(&wakeup_callback); + autosuspend_set_wakeup_callback(&wakeup_callback); } // Wait for wakeup. diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/NetworkLoggingHandler.java b/services/devicepolicy/java/com/android/server/devicepolicy/NetworkLoggingHandler.java index 4a6bee5c7365..f91f959d9938 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/NetworkLoggingHandler.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/NetworkLoggingHandler.java @@ -29,10 +29,10 @@ import android.util.LongSparseArray; import android.util.Slog; import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; import java.util.ArrayList; import java.util.List; -import java.util.concurrent.TimeUnit; /** * A Handler class for managing network logging on a background thread. @@ -81,6 +81,7 @@ final class NetworkLoggingHandler extends Handler { } }; + @VisibleForTesting static final int LOG_NETWORK_EVENT_MSG = 1; /** Network events accumulated so far to be finalized into a batch at some point. */ @@ -106,9 +107,15 @@ final class NetworkLoggingHandler extends Handler { private long mLastRetrievedBatchToken; NetworkLoggingHandler(Looper looper, DevicePolicyManagerService dpm) { + this(looper, dpm, 0 /* event id */); + } + + @VisibleForTesting + NetworkLoggingHandler(Looper looper, DevicePolicyManagerService dpm, long id) { super(looper); - mDpm = dpm; - mAlarmManager = mDpm.mInjector.getAlarmManager(); + this.mDpm = dpm; + this.mAlarmManager = mDpm.mInjector.getAlarmManager(); + this.mId = id; } @Override @@ -189,7 +196,13 @@ final class NetworkLoggingHandler extends Handler { if (mNetworkEvents.size() > 0) { // Assign ids to the events. for (NetworkEvent event : mNetworkEvents) { - event.setId(mId++); + event.setId(mId); + if (mId == Long.MAX_VALUE) { + Slog.i(TAG, "Reached maximum id value; wrapping around ." + mCurrentBatchToken); + mId = 0; + } else { + mId++; + } } // Finalize the batch and start a new one from scratch. if (mBatches.size() >= MAX_BATCHES) { diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index 7aa628af4621..4310a98d8000 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -26,6 +26,7 @@ import android.content.Intent; import android.content.pm.PackageManager; import android.content.res.Configuration; import android.content.res.Resources.Theme; +import android.database.sqlite.SQLiteCompatibilityWalFlags; import android.os.BaseBundle; import android.os.Binder; import android.os.Build; @@ -327,6 +328,8 @@ public final class SystemServer { // The system server should never make non-oneway calls Binder.setWarnOnBlocking(true); + // Deactivate SQLiteCompatibilityWalFlags until settings provider is initialized + SQLiteCompatibilityWalFlags.init(null); // Here we go! Slog.i(TAG, "Entered the Android system server!"); @@ -803,6 +806,8 @@ public final class SystemServer { traceBeginAndSlog("InstallSystemProviders"); mActivityManagerService.installSystemProviders(); + // Now that SettingsProvider is ready, reactivate SQLiteCompatibilityWalFlags + SQLiteCompatibilityWalFlags.reset(); traceEnd(); traceBeginAndSlog("StartVibratorService"); diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/NetworkEventTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/NetworkEventTest.java index a92d1db2616b..c698312eedec 100644 --- a/services/tests/servicestests/src/com/android/server/devicepolicy/NetworkEventTest.java +++ b/services/tests/servicestests/src/com/android/server/devicepolicy/NetworkEventTest.java @@ -15,13 +15,131 @@ */ package com.android.server.devicepolicy; +import static com.android.server.devicepolicy.NetworkLoggingHandler.LOG_NETWORK_EVENT_MSG; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; + import android.app.admin.ConnectEvent; +import android.app.admin.DeviceAdminReceiver; +import android.app.admin.DevicePolicyManagerInternal; import android.app.admin.DnsEvent; +import android.app.admin.NetworkEvent; +import android.content.Intent; +import android.os.Bundle; +import android.os.Message; import android.os.Parcel; +import android.os.SystemClock; +import android.os.UserHandle; +import android.os.test.TestLooper; import android.test.suitebuilder.annotation.SmallTest; +import com.android.server.LocalServices; +import com.android.server.SystemService; + +import org.mockito.ArgumentCaptor; + +import java.util.List; + @SmallTest public class NetworkEventTest extends DpmTestBase { + private static final int MAX_EVENTS_PER_BATCH = 1200; + + private DpmMockContext mSpiedDpmMockContext; + private DevicePolicyManagerServiceTestable mDpmTestable; + + @Override + protected void setUp() throws Exception { + super.setUp(); + mSpiedDpmMockContext = spy(mMockContext); + mSpiedDpmMockContext.callerPermissions.add( + android.Manifest.permission.MANAGE_DEVICE_ADMINS); + doNothing().when(mSpiedDpmMockContext).sendBroadcastAsUser(any(Intent.class), + any(UserHandle.class)); + LocalServices.removeServiceForTest(DevicePolicyManagerInternal.class); + mDpmTestable = new DevicePolicyManagerServiceTestable(getServices(), mSpiedDpmMockContext); + setUpPackageManagerForAdmin(admin1, DpmMockContext.CALLER_UID); + mDpmTestable.setActiveAdmin(admin1, true, DpmMockContext.CALLER_USER_HANDLE); + } + + public void testNetworkEventId_monotonicallyIncreasing() throws Exception { + // GIVEN the handler has not processed any events. + long startingId = 0; + + // WHEN the handler has processed the events. + List<NetworkEvent> events = fillHandlerWithFullBatchOfEvents(startingId); + + // THEN the events are in a batch. + assertTrue("Batch not at the returned token.", + events != null && events.size() == MAX_EVENTS_PER_BATCH); + // THEN event ids are monotonically increasing. + long expectedId = startingId; + for (int i = 0; i < MAX_EVENTS_PER_BATCH; i++) { + assertEquals("At index " + i + ", the event has the wrong id.", expectedId, + events.get(i).getId()); + expectedId++; + } + } + + public void testNetworkEventId_wrapsAround() throws Exception { + // GIVEN the handler has almost processed Long.MAX_VALUE events. + int gap = 5; + long startingId = Long.MAX_VALUE - gap; + + // WHEN the handler has processed the events. + List<NetworkEvent> events = fillHandlerWithFullBatchOfEvents(startingId); + + // THEN the events are in a batch. + assertTrue("Batch not at the returned token.", + events != null && events.size() == MAX_EVENTS_PER_BATCH); + // THEN event ids are monotonically increasing. + long expectedId = startingId; + for (int i = 0; i < gap; i++) { + assertEquals("At index " + i + ", the event has the wrong id.", expectedId, + events.get(i).getId()); + expectedId++; + } + // THEN event ids are reset when the id reaches the maximum possible value. + assertEquals("Event was not assigned the maximum id value.", Long.MAX_VALUE, + events.get(gap).getId()); + assertEquals("Event id was not reset.", 0, events.get(gap + 1).getId()); + // THEN event ids are monotonically increasing. + expectedId = 0; + for (int i = gap + 1; i < MAX_EVENTS_PER_BATCH; i++) { + assertEquals("At index " + i + ", the event has the wrong id.", expectedId, + events.get(i).getId()); + expectedId++; + } + } + + private List<NetworkEvent> fillHandlerWithFullBatchOfEvents(long startingId) throws Exception { + // GIVEN a handler with events + NetworkLoggingHandler handler = new NetworkLoggingHandler(new TestLooper().getLooper(), + mDpmTestable, startingId); + // GIVEN network events are sent to the handler. + for (int i = 0; i < MAX_EVENTS_PER_BATCH; i++) { + ConnectEvent event = new ConnectEvent("some_ip_address", 800, "com.google.foo", + SystemClock.currentThreadTimeMillis()); + Message msg = new Message(); + msg.what = LOG_NETWORK_EVENT_MSG; + Bundle bundle = new Bundle(); + bundle.putParcelable(NetworkLoggingHandler.NETWORK_EVENT_KEY, event); + msg.setData(bundle); + handler.handleMessage(msg); + } + + // WHEN the handler processes the events. + ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class); + verify(mSpiedDpmMockContext).sendBroadcastAsUser(intentCaptor.capture(), + any(UserHandle.class)); + assertEquals(intentCaptor.getValue().getAction(), + DeviceAdminReceiver.ACTION_NETWORK_LOGS_AVAILABLE); + long token = intentCaptor.getValue().getExtras().getLong( + DeviceAdminReceiver.EXTRA_NETWORK_LOGS_TOKEN, 0); + return handler.retrieveFullLogBatch(token); + } /** * Test parceling and unparceling of a ConnectEvent. diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncUtilsTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncUtilsTest.java index c918e8c7899d..ac3abedba7f3 100644 --- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncUtilsTest.java +++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncUtilsTest.java @@ -37,6 +37,7 @@ import javax.crypto.SecretKey; public class KeySyncUtilsTest { private static final int RECOVERY_KEY_LENGTH_BITS = 256; private static final int THM_KF_HASH_SIZE = 256; + private static final int KEY_CLAIMANT_LENGTH_BYTES = 16; private static final String SHA_256_ALGORITHM = "SHA-256"; @Test @@ -70,6 +71,32 @@ public class KeySyncUtilsTest { assertFalse(Arrays.equals(a.getEncoded(), b.getEncoded())); } + @Test + public void generateKeyClaimant_returns16Bytes() throws Exception { + byte[] keyClaimant = KeySyncUtils.generateKeyClaimant(); + + assertEquals(KEY_CLAIMANT_LENGTH_BYTES, keyClaimant.length); + } + + @Test + public void generateKeyClaimant_generatesANewClaimantEachTime() { + byte[] a = KeySyncUtils.generateKeyClaimant(); + byte[] b = KeySyncUtils.generateKeyClaimant(); + + assertFalse(Arrays.equals(a, b)); + } + + @Test + public void concat_concatenatesArrays() { + assertArrayEquals( + utf8Bytes("hello, world!"), + KeySyncUtils.concat( + utf8Bytes("hello"), + utf8Bytes(", "), + utf8Bytes("world"), + utf8Bytes("!"))); + } + private static byte[] utf8Bytes(String s) { return s.getBytes(StandardCharsets.UTF_8); } diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/PlatformKeyManagerTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/PlatformKeyManagerTest.java new file mode 100644 index 000000000000..a9977705aad6 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/PlatformKeyManagerTest.java @@ -0,0 +1,275 @@ +/* + * 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.locksettings.recoverablekeystore; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.app.KeyguardManager; +import android.content.Context; +import android.content.SharedPreferences; +import android.security.keystore.KeyProperties; +import android.security.keystore.KeyProtection; +import android.support.test.InstrumentationRegistry; +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.security.KeyStore; +import java.util.List; + +@SmallTest +@RunWith(AndroidJUnit4.class) +public class PlatformKeyManagerTest { + + private static final int USER_AUTHENTICATION_VALIDITY_DURATION_SECONDS = 15; + private static final int USER_ID_FIXTURE = 42; + private static final String TEST_SHARED_PREFS_NAME = "PlatformKeyManagerTestPrefs"; + + @Mock private Context mContext; + @Mock private KeyStoreProxy mKeyStoreProxy; + @Mock private KeyguardManager mKeyguardManager; + + @Captor private ArgumentCaptor<KeyStore.ProtectionParameter> mProtectionParameterCaptor; + @Captor private ArgumentCaptor<KeyStore.Entry> mEntryArgumentCaptor; + + private SharedPreferences mSharedPreferences; + private PlatformKeyManager mPlatformKeyManager; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + + Context testContext = InstrumentationRegistry.getTargetContext(); + mSharedPreferences = testContext.getSharedPreferences( + TEST_SHARED_PREFS_NAME, Context.MODE_PRIVATE); + mPlatformKeyManager = new PlatformKeyManager( + USER_ID_FIXTURE, mContext, mKeyStoreProxy, mSharedPreferences); + + when(mContext.getSystemService(anyString())).thenReturn(mKeyguardManager); + when(mContext.getSystemServiceName(any())).thenReturn("test"); + when(mKeyguardManager.isDeviceSecure(USER_ID_FIXTURE)).thenReturn(true); + } + + @After + public void tearDown() { + mSharedPreferences.edit().clear().commit(); + } + + @Test + public void init_createsEncryptKeyWithCorrectAlias() throws Exception { + mPlatformKeyManager.init(); + + verify(mKeyStoreProxy).setEntry( + eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/encrypt"), + any(), + any()); + } + + @Test + public void init_createsEncryptKeyWithCorrectPurposes() throws Exception { + mPlatformKeyManager.init(); + + assertEquals(KeyProperties.PURPOSE_ENCRYPT, getEncryptKeyProtection().getPurposes()); + } + + @Test + public void init_createsEncryptKeyWithCorrectPaddings() throws Exception { + mPlatformKeyManager.init(); + + assertArrayEquals( + new String[] { KeyProperties.ENCRYPTION_PADDING_NONE }, + getEncryptKeyProtection().getEncryptionPaddings()); + } + + @Test + public void init_createsEncryptKeyWithCorrectBlockModes() throws Exception { + mPlatformKeyManager.init(); + + assertArrayEquals( + new String[] { KeyProperties.BLOCK_MODE_GCM }, + getEncryptKeyProtection().getBlockModes()); + } + + @Test + public void init_createsEncryptKeyWithoutAuthenticationRequired() throws Exception { + mPlatformKeyManager.init(); + + assertFalse(getEncryptKeyProtection().isUserAuthenticationRequired()); + } + + @Test + public void init_createsDecryptKeyWithCorrectAlias() throws Exception { + mPlatformKeyManager.init(); + + verify(mKeyStoreProxy).setEntry( + eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/decrypt"), + any(), + any()); + } + + @Test + public void init_createsDecryptKeyWithCorrectPurposes() throws Exception { + mPlatformKeyManager.init(); + + assertEquals(KeyProperties.PURPOSE_DECRYPT, getDecryptKeyProtection().getPurposes()); + } + + @Test + public void init_createsDecryptKeyWithCorrectPaddings() throws Exception { + mPlatformKeyManager.init(); + + assertArrayEquals( + new String[] { KeyProperties.ENCRYPTION_PADDING_NONE }, + getDecryptKeyProtection().getEncryptionPaddings()); + } + + @Test + public void init_createsDecryptKeyWithCorrectBlockModes() throws Exception { + mPlatformKeyManager.init(); + + assertArrayEquals( + new String[] { KeyProperties.BLOCK_MODE_GCM }, + getDecryptKeyProtection().getBlockModes()); + } + + @Test + public void init_createsDecryptKeyWithAuthenticationRequired() throws Exception { + mPlatformKeyManager.init(); + + assertTrue(getDecryptKeyProtection().isUserAuthenticationRequired()); + } + + @Test + public void init_createsDecryptKeyWithAuthenticationValidFor15Seconds() throws Exception { + mPlatformKeyManager.init(); + + assertEquals( + USER_AUTHENTICATION_VALIDITY_DURATION_SECONDS, + getDecryptKeyProtection().getUserAuthenticationValidityDurationSeconds()); + } + + @Test + public void init_createsDecryptKeyBoundToTheUsersAuthentication() throws Exception { + mPlatformKeyManager.init(); + + assertEquals( + USER_ID_FIXTURE, + getDecryptKeyProtection().getBoundToSpecificSecureUserId()); + } + + @Test + public void init_createsBothKeysWithSameMaterial() throws Exception { + mPlatformKeyManager.init(); + + verify(mKeyStoreProxy, times(2)).setEntry(any(), mEntryArgumentCaptor.capture(), any()); + List<KeyStore.Entry> entries = mEntryArgumentCaptor.getAllValues(); + assertArrayEquals( + ((KeyStore.SecretKeyEntry) entries.get(0)).getSecretKey().getEncoded(), + ((KeyStore.SecretKeyEntry) entries.get(1)).getSecretKey().getEncoded()); + } + + @Test + public void init_setsGenerationIdTo1() throws Exception { + mPlatformKeyManager.init(); + + assertEquals(1, mPlatformKeyManager.getGenerationId()); + } + + @Test + public void getDecryptKey_getsDecryptKeyWithCorrectAlias() throws Exception { + mPlatformKeyManager.getDecryptKey(); + + verify(mKeyStoreProxy).getKey( + eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/decrypt"), + any()); + } + + @Test + public void getEncryptKey_getsDecryptKeyWithCorrectAlias() throws Exception { + mPlatformKeyManager.getEncryptKey(); + + verify(mKeyStoreProxy).getKey( + eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/encrypt"), + any()); + } + + @Test + public void regenerate_incrementsTheGenerationId() throws Exception { + mPlatformKeyManager.init(); + + mPlatformKeyManager.regenerate(); + + assertEquals(2, mPlatformKeyManager.getGenerationId()); + } + + @Test + public void regenerate_generatesANewEncryptKeyWithTheCorrectAlias() throws Exception { + mPlatformKeyManager.init(); + + mPlatformKeyManager.regenerate(); + + verify(mKeyStoreProxy).setEntry( + eq("com.android.server.locksettings.recoverablekeystore/platform/42/2/encrypt"), + any(), + any()); + } + + @Test + public void regenerate_generatesANewDecryptKeyWithTheCorrectAlias() throws Exception { + mPlatformKeyManager.init(); + + mPlatformKeyManager.regenerate(); + + verify(mKeyStoreProxy).setEntry( + eq("com.android.server.locksettings.recoverablekeystore/platform/42/2/decrypt"), + any(), + any()); + } + + private KeyProtection getEncryptKeyProtection() throws Exception { + verify(mKeyStoreProxy).setEntry( + eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/encrypt"), + any(), + mProtectionParameterCaptor.capture()); + return (KeyProtection) mProtectionParameterCaptor.getValue(); + } + + private KeyProtection getDecryptKeyProtection() throws Exception { + verify(mKeyStoreProxy).setEntry( + eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/decrypt"), + any(), + mProtectionParameterCaptor.capture()); + return (KeyProtection) mProtectionParameterCaptor.getValue(); + } +} diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyGeneratorTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyGeneratorTest.java index 298a98822caa..12dbdb3fecb4 100644 --- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyGeneratorTest.java +++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyGeneratorTest.java @@ -48,6 +48,7 @@ import javax.crypto.SecretKey; @SmallTest @RunWith(AndroidJUnit4.class) public class RecoverableKeyGeneratorTest { + private static final int TEST_GENERATION_ID = 3; private static final String ANDROID_KEY_STORE_PROVIDER = "AndroidKeyStore"; private static final String KEY_ALGORITHM = "AES"; private static final String TEST_ALIAS = "karlin"; @@ -58,14 +59,14 @@ public class RecoverableKeyGeneratorTest { @Captor ArgumentCaptor<KeyProtection> mKeyProtectionArgumentCaptor; - private AndroidKeyStoreSecretKey mPlatformKey; + private PlatformEncryptionKey mPlatformKey; private SecretKey mKeyHandle; private RecoverableKeyGenerator mRecoverableKeyGenerator; @Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); - mPlatformKey = generateAndroidKeyStoreKey(); + mPlatformKey = new PlatformEncryptionKey(TEST_GENERATION_ID, generateAndroidKeyStoreKey()); mKeyHandle = generateKey(); mRecoverableKeyGenerator = RecoverableKeyGenerator.newInstance( mPlatformKey, mRecoverableKeyStorage); diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/WrappedKeyTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/WrappedKeyTest.java index 8371fe5e376c..56122a7d6d4c 100644 --- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/WrappedKeyTest.java +++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/WrappedKeyTest.java @@ -61,7 +61,8 @@ public class WrappedKeyTest { @Test public void fromSecretKey_createsWrappedKeyThatCanBeUnwrapped() throws Exception { - SecretKey wrappingKey = generateAndroidKeyStoreKey(); + PlatformEncryptionKey wrappingKey = new PlatformEncryptionKey( + GENERATION_ID, generateAndroidKeyStoreKey()); SecretKey rawKey = generateKey(); WrappedKey wrappedKey = WrappedKey.fromSecretKey(wrappingKey, rawKey); @@ -69,7 +70,7 @@ public class WrappedKeyTest { Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM); cipher.init( Cipher.UNWRAP_MODE, - wrappingKey, + wrappingKey.getKey(), new GCMParameterSpec(GCM_TAG_LENGTH_BITS, wrappedKey.getNonce())); SecretKey unwrappedKey = (SecretKey) cipher.unwrap( wrappedKey.getKeyMaterial(), KEY_ALGORITHM, Cipher.SECRET_KEY); @@ -77,15 +78,28 @@ public class WrappedKeyTest { } @Test + public void fromSecretKey_returnsAKeyWithTheGenerationIdOfTheWrappingKey() throws Exception { + PlatformEncryptionKey wrappingKey = new PlatformEncryptionKey( + GENERATION_ID, generateAndroidKeyStoreKey()); + SecretKey rawKey = generateKey(); + + WrappedKey wrappedKey = WrappedKey.fromSecretKey(wrappingKey, rawKey); + + assertEquals(GENERATION_ID, wrappedKey.getPlatformKeyGenerationId()); + } + + @Test public void decryptWrappedKeys_decryptsWrappedKeys() throws Exception { String alias = "karlin"; - PlatformDecryptionKey platformKey = generatePlatformDecryptionKey(); + AndroidKeyStoreSecretKey platformKey = generateAndroidKeyStoreKey(); SecretKey appKey = generateKey(); - WrappedKey wrappedKey = WrappedKey.fromSecretKey(platformKey.getKey(), appKey); + WrappedKey wrappedKey = WrappedKey.fromSecretKey( + new PlatformEncryptionKey(GENERATION_ID, platformKey), appKey); HashMap<String, WrappedKey> keysByAlias = new HashMap<>(); keysByAlias.put(alias, wrappedKey); - Map<String, SecretKey> unwrappedKeys = WrappedKey.unwrapKeys(platformKey, keysByAlias); + Map<String, SecretKey> unwrappedKeys = WrappedKey.unwrapKeys( + new PlatformDecryptionKey(GENERATION_ID, platformKey), keysByAlias); assertEquals(1, unwrappedKeys.size()); assertTrue(unwrappedKeys.containsKey(alias)); @@ -95,26 +109,32 @@ public class WrappedKeyTest { @Test public void decryptWrappedKeys_doesNotDieIfSomeKeysAreUnwrappable() throws Exception { String alias = "karlin"; + AndroidKeyStoreSecretKey platformKey = generateAndroidKeyStoreKey(); SecretKey appKey = generateKey(); - WrappedKey wrappedKey = WrappedKey.fromSecretKey(generateKey(), appKey); + WrappedKey wrappedKey = WrappedKey.fromSecretKey( + new PlatformEncryptionKey(GENERATION_ID, platformKey), appKey); HashMap<String, WrappedKey> keysByAlias = new HashMap<>(); keysByAlias.put(alias, wrappedKey); Map<String, SecretKey> unwrappedKeys = WrappedKey.unwrapKeys( - generatePlatformDecryptionKey(), keysByAlias); + new PlatformDecryptionKey(GENERATION_ID, generateAndroidKeyStoreKey()), + keysByAlias); assertEquals(0, unwrappedKeys.size()); } @Test public void decryptWrappedKeys_throwsIfPlatformKeyGenerationIdDoesNotMatch() throws Exception { - WrappedKey wrappedKey = WrappedKey.fromSecretKey(generateKey(), generateKey()); + AndroidKeyStoreSecretKey platformKey = generateAndroidKeyStoreKey(); + WrappedKey wrappedKey = WrappedKey.fromSecretKey( + new PlatformEncryptionKey(GENERATION_ID, platformKey), generateKey()); HashMap<String, WrappedKey> keysByAlias = new HashMap<>(); keysByAlias.put("benji", wrappedKey); try { WrappedKey.unwrapKeys( - generatePlatformDecryptionKey(/*generationId=*/ 2), keysByAlias); + new PlatformDecryptionKey(/*generationId=*/ 2, platformKey), + keysByAlias); fail("Should have thrown."); } catch (BadPlatformKeyException e) { assertEquals( diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbTest.java new file mode 100644 index 000000000000..3d5b958170a9 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbTest.java @@ -0,0 +1,163 @@ +/* + * 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.locksettings.recoverablekeystore.storage; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import android.content.Context; +import android.support.test.InstrumentationRegistry; +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; + +import com.android.server.locksettings.recoverablekeystore.WrappedKey; + +import java.io.File; +import java.nio.charset.StandardCharsets; +import java.util.Map; + +@SmallTest +@RunWith(AndroidJUnit4.class) +public class RecoverableKeyStoreDbTest { + private static final String DATABASE_FILE_NAME = "recoverablekeystore.db"; + + private RecoverableKeyStoreDb mRecoverableKeyStoreDb; + private File mDatabaseFile; + + @Before + public void setUp() { + Context context = InstrumentationRegistry.getTargetContext(); + mDatabaseFile = context.getDatabasePath(DATABASE_FILE_NAME); + mRecoverableKeyStoreDb = RecoverableKeyStoreDb.newInstance(context); + } + + @After + public void tearDown() { + mRecoverableKeyStoreDb.close(); + mDatabaseFile.delete(); + } + + @Test + public void insertKey_replacesOldKey() { + int userId = 12; + String alias = "test"; + WrappedKey oldWrappedKey = new WrappedKey( + getUtf8Bytes("nonce1"), + getUtf8Bytes("keymaterial1"), + /*platformKeyGenerationId=*/1); + mRecoverableKeyStoreDb.insertKey( + userId, alias, oldWrappedKey); + byte[] nonce = getUtf8Bytes("nonce2"); + byte[] keyMaterial = getUtf8Bytes("keymaterial2"); + WrappedKey newWrappedKey = new WrappedKey( + nonce, keyMaterial, /*platformKeyGenerationId=*/2); + + mRecoverableKeyStoreDb.insertKey( + userId, alias, newWrappedKey); + + WrappedKey retrievedKey = mRecoverableKeyStoreDb.getKey(userId, alias); + assertArrayEquals(nonce, retrievedKey.getNonce()); + assertArrayEquals(keyMaterial, retrievedKey.getKeyMaterial()); + assertEquals(2, retrievedKey.getPlatformKeyGenerationId()); + } + + @Test + public void getKey_returnsNullIfNoKey() { + WrappedKey key = mRecoverableKeyStoreDb.getKey( + /*userId=*/ 1, /*alias=*/ "hello"); + + assertNull(key); + } + + @Test + public void getKey_returnsInsertedKey() { + int userId = 12; + int generationId = 6; + String alias = "test"; + byte[] nonce = getUtf8Bytes("nonce"); + byte[] keyMaterial = getUtf8Bytes("keymaterial"); + WrappedKey wrappedKey = new WrappedKey(nonce, keyMaterial, generationId); + mRecoverableKeyStoreDb.insertKey(userId, alias, wrappedKey); + + WrappedKey retrievedKey = mRecoverableKeyStoreDb.getKey(userId, alias); + + assertArrayEquals(nonce, retrievedKey.getNonce()); + assertArrayEquals(keyMaterial, retrievedKey.getKeyMaterial()); + assertEquals(generationId, retrievedKey.getPlatformKeyGenerationId()); + } + + @Test + public void getAllKeys_getsKeysWithUserIdAndGenerationId() { + int userId = 12; + int generationId = 6; + String alias = "test"; + byte[] nonce = getUtf8Bytes("nonce"); + byte[] keyMaterial = getUtf8Bytes("keymaterial"); + WrappedKey wrappedKey = new WrappedKey(nonce, keyMaterial, generationId); + mRecoverableKeyStoreDb.insertKey(userId, alias, wrappedKey); + + Map<String, WrappedKey> keys = mRecoverableKeyStoreDb.getAllKeys(userId, generationId); + + assertEquals(1, keys.size()); + assertTrue(keys.containsKey(alias)); + WrappedKey retrievedKey = keys.get(alias); + assertArrayEquals(nonce, retrievedKey.getNonce()); + assertArrayEquals(keyMaterial, retrievedKey.getKeyMaterial()); + assertEquals(generationId, retrievedKey.getPlatformKeyGenerationId()); + } + + @Test + public void getAllKeys_doesNotReturnKeysWithBadGenerationId() { + int userId = 12; + WrappedKey wrappedKey = new WrappedKey( + getUtf8Bytes("nonce"), + getUtf8Bytes("keymaterial"), + /*platformKeyGenerationId=*/ 5); + mRecoverableKeyStoreDb.insertKey( + userId, /*alias=*/ "test", wrappedKey); + + Map<String, WrappedKey> keys = mRecoverableKeyStoreDb.getAllKeys( + userId, /*generationId=*/ 7); + + assertTrue(keys.isEmpty()); + } + + @Test + public void getAllKeys_doesNotReturnKeysWithBadUserId() { + int generationId = 12; + WrappedKey wrappedKey = new WrappedKey( + getUtf8Bytes("nonce"), getUtf8Bytes("keymaterial"), generationId); + mRecoverableKeyStoreDb.insertKey( + /*userId=*/ 1, /*alias=*/ "test", wrappedKey); + + Map<String, WrappedKey> keys = mRecoverableKeyStoreDb.getAllKeys( + /*userId=*/ 2, generationId); + + assertTrue(keys.isEmpty()); + } + + private static byte[] getUtf8Bytes(String s) { + return s.getBytes(StandardCharsets.UTF_8); + } +} diff --git a/services/tests/servicestests/src/com/android/server/net/ConnOnActivityStartTest.java b/services/tests/servicestests/src/com/android/server/net/ConnOnActivityStartTest.java index 7f48b8e1759a..0c35fcb05da6 100644 --- a/services/tests/servicestests/src/com/android/server/net/ConnOnActivityStartTest.java +++ b/services/tests/servicestests/src/com/android/server/net/ConnOnActivityStartTest.java @@ -19,7 +19,6 @@ package com.android.server.net; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; import com.android.frameworks.servicestests.R; import com.android.servicestests.aidl.ICmdReceiverService; @@ -42,6 +41,7 @@ import android.support.test.uiautomator.UiDevice; import android.util.Log; import org.junit.AfterClass; +import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; @@ -81,7 +81,7 @@ public class ConnOnActivityStartTest { private static final long NETWORK_CHECK_TIMEOUT_MS = 4000; // 4 sec - private static final long SCREEN_ON_DELAY_MS = 500; // 0.5 sec + private static final long SCREEN_ON_DELAY_MS = 1000; // 1 sec private static final long BIND_SERVICE_TIMEOUT_SEC = 4; @@ -348,13 +348,17 @@ public class ConnOnActivityStartTest { } private String executeCommand(String cmd) throws IOException { - final String result = mUiDevice.executeShellCommand(cmd).trim(); + final String result = executeSilentCommand(cmd); Log.d(TAG, String.format("Result for '%s': %s", cmd, result)); return result; } + private static String executeSilentCommand(String cmd) throws IOException { + return mUiDevice.executeShellCommand(cmd).trim(); + } + private void assertDelayedCommandResult(String cmd, String expectedResult, - int maxTries, int napTimeMs) throws IOException { + int maxTries, int napTimeMs) throws Exception { String result = ""; for (int i = 1; i <= maxTries; ++i) { result = executeCommand(cmd); @@ -394,6 +398,23 @@ public class ConnOnActivityStartTest { } } + private static void fail(String msg) throws Exception { + dumpOnFailure(); + Assert.fail(msg); + } + + private static void dumpOnFailure() throws Exception { + Log.d(TAG, ">>> Begin network_management dump"); + Log.printlns(Log.LOG_ID_MAIN, Log.DEBUG, + TAG, executeSilentCommand("dumpsys network_management"), null); + Log.d(TAG, "<<< End network_management dump"); + + Log.d(TAG, ">>> Begin netpolicy dump"); + Log.printlns(Log.LOG_ID_MAIN, Log.DEBUG, + TAG, executeSilentCommand("dumpsys netpolicy"), null); + Log.d(TAG, "<<< End netpolicy dump"); + } + private void finishActivity() throws Exception { mCmdReceiverService.finishActivity(); } diff --git a/services/tests/servicestests/test-apps/ConnTestApp/src/com/android/servicestests/apps/conntestapp/ConnTestActivity.java b/services/tests/servicestests/test-apps/ConnTestApp/src/com/android/servicestests/apps/conntestapp/ConnTestActivity.java index b26cc7fd0130..f8d1d03cd7a6 100644 --- a/services/tests/servicestests/test-apps/ConnTestApp/src/com/android/servicestests/apps/conntestapp/ConnTestActivity.java +++ b/services/tests/servicestests/test-apps/ConnTestApp/src/com/android/servicestests/apps/conntestapp/ConnTestActivity.java @@ -77,6 +77,7 @@ public class ConnTestActivity extends Activity { Log.i(TAG, "onStop called"); if (finishCommandReceiver != null) { unregisterReceiver(finishCommandReceiver); + finishCommandReceiver = null; } super.onStop(); } diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java index 07c860b54413..cdce44872cc0 100644 --- a/services/usage/java/com/android/server/usage/UsageStatsService.java +++ b/services/usage/java/com/android/server/usage/UsageStatsService.java @@ -495,8 +495,8 @@ public class UsageStatsService extends SystemService implements flushToDiskLocked(); pw.println("Flushed stats to disk"); return; - } else { - // Anything else is a pkg to filter + } else if (arg != null && !arg.startsWith("-")) { + // Anything else that doesn't start with '-' is a pkg to filter pkg = arg; break; } diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java index dedce1702dfe..6a47d05018d6 100644 --- a/telephony/java/android/telephony/CarrierConfigManager.java +++ b/telephony/java/android/telephony/CarrierConfigManager.java @@ -1717,6 +1717,13 @@ public class CarrierConfigManager { public static final String KEY_SPN_DISPLAY_RULE_USE_ROAMING_FROM_SERVICE_STATE_BOOL = "spn_display_rule_use_roaming_from_service_state_bool"; + /** + * Determines whether any carrier has been identified and its specific config has been applied, + * default to false. + * @hide + */ + public static final String KEY_CARRIER_CONFIG_APPLIED_BOOL = "carrier_config_applied_bool"; + /** The default value for every variable. */ private final static PersistableBundle sDefaults; @@ -2002,6 +2009,7 @@ public class CarrierConfigManager { sDefaults.putBoolean(KEY_IDENTIFY_HIGH_DEFINITION_CALLS_IN_CALL_LOG_BOOL, false); sDefaults.putBoolean(KEY_SPN_DISPLAY_RULE_USE_ROAMING_FROM_SERVICE_STATE_BOOL, false); sDefaults.putBoolean(KEY_ALWAYS_SHOW_DATA_RAT_ICON_BOOL, false); + sDefaults.putBoolean(KEY_CARRIER_CONFIG_APPLIED_BOOL, false); } /** @@ -2047,6 +2055,33 @@ public class CarrierConfigManager { } /** + * Determines whether a configuration {@link PersistableBundle} obtained from + * {@link #getConfig()} or {@link #getConfigForSubId(int)} corresponds to an identified carrier. + * <p> + * When an app receives the {@link CarrierConfigManager#ACTION_CARRIER_CONFIG_CHANGED} + * broadcast which informs it that the carrier configuration has changed, it is possible + * that another reload of the carrier configuration has begun since the intent was sent. + * In this case, the carrier configuration the app fetches (e.g. via {@link #getConfig()}) + * may not represent the configuration for the current carrier. It should be noted that it + * does not necessarily mean the configuration belongs to current carrier when this function + * return true because it may belong to another previous identified carrier. Users should + * always call {@link #getConfig()} or {@link #getConfigForSubId(int)} after receiving the + * broadcast {@link #ACTION_CARRIER_CONFIG_CHANGED}. + * </p> + * <p> + * After using {@link #getConfig()} or {@link #getConfigForSubId(int)} an app should always + * use this method to confirm whether any carrier specific configuration has been applied. + * </p> + * + * @param bundle the configuration bundle to be checked. + * @return boolean true if any carrier specific configuration bundle has been applied, false + * otherwise or the bundle is null. + */ + public static boolean isConfigForIdentifiedCarrier(PersistableBundle bundle) { + return bundle != null && bundle.getBoolean(KEY_CARRIER_CONFIG_APPLIED_BOOL); + } + + /** * Calling this method triggers telephony services to fetch the current carrier configuration. * <p> * Normally this does not need to be called because the platform reloads config on its own. diff --git a/telephony/java/android/telephony/SmsMessage.java b/telephony/java/android/telephony/SmsMessage.java index 710eff0e0110..a6dbc06647d0 100644 --- a/telephony/java/android/telephony/SmsMessage.java +++ b/telephony/java/android/telephony/SmsMessage.java @@ -19,6 +19,7 @@ package android.telephony; import static android.telephony.TelephonyManager.PHONE_TYPE_CDMA; import android.annotation.StringDef; +import android.app.PendingIntent; import android.content.res.Resources; import android.os.Binder; import android.text.TextUtils; @@ -92,11 +93,13 @@ public class SmsMessage { /** * Indicates a 3GPP format SMS message. + * @see SmsManager#injectSmsPdu(byte[], String, PendingIntent) */ public static final String FORMAT_3GPP = "3gpp"; /** * Indicates a 3GPP2 format SMS message. + * @see SmsManager#injectSmsPdu(byte[], String, PendingIntent) */ public static final String FORMAT_3GPP2 = "3gpp2"; diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java index 28ae10bf85f5..93c0c51d8519 100644 --- a/telephony/java/android/telephony/TelephonyManager.java +++ b/telephony/java/android/telephony/TelephonyManager.java @@ -5747,39 +5747,38 @@ public class TelephonyManager { * @param enable Whether to enable mobile data. * * @see #hasCarrierPrivileges + * @deprecated use {@link #setUserMobileDataEnabled(boolean)} instead. */ + @Deprecated @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setDataEnabled(boolean enable) { - setDataEnabled(getSubId(SubscriptionManager.getDefaultDataSubscriptionId()), enable); + setUserMobileDataEnabled(enable); } - /** @hide */ + /** + * @hide + * @deprecated use {@link #setUserMobileDataEnabled(boolean)} instead. + */ @SystemApi + @Deprecated @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setDataEnabled(int subId, boolean enable) { - try { - Log.d(TAG, "setDataEnabled: enabled=" + enable); - ITelephony telephony = getITelephony(); - if (telephony != null) - telephony.setDataEnabled(subId, enable); - } catch (RemoteException e) { - Log.e(TAG, "Error calling ITelephony#setDataEnabled", e); - } + setUserMobileDataEnabled(subId, enable); } - /** - * @deprecated use {@link #isDataEnabled()} instead. + * @deprecated use {@link #isUserMobileDataEnabled()} instead. * @hide */ @SystemApi @Deprecated public boolean getDataEnabled() { - return isDataEnabled(); + return isUserMobileDataEnabled(); } /** - * Returns whether mobile data is enabled or not. + * Returns whether mobile data is enabled or not per user setting. There are other factors + * that could disable mobile data, but they are not considered here. * * If this object has been created with {@link #createForSubscriptionId}, applies to the given * subId. Otherwise, applies to {@link SubscriptionManager#getDefaultDataSubscriptionId()} @@ -5796,28 +5795,21 @@ public class TelephonyManager { * @return true if mobile data is enabled. * * @see #hasCarrierPrivileges + * @deprecated use {@link #isUserMobileDataEnabled()} instead. */ - @SuppressWarnings("deprecation") + @Deprecated public boolean isDataEnabled() { - return getDataEnabled(getSubId(SubscriptionManager.getDefaultDataSubscriptionId())); + return isUserMobileDataEnabled(); } /** - * @deprecated use {@link #isDataEnabled(int)} instead. + * @deprecated use {@link #isUserMobileDataEnabled()} instead. * @hide */ + @Deprecated @SystemApi public boolean getDataEnabled(int subId) { - boolean retVal = false; - try { - ITelephony telephony = getITelephony(); - if (telephony != null) - retVal = telephony.getDataEnabled(subId); - } catch (RemoteException e) { - Log.e(TAG, "Error calling ITelephony#getDataEnabled", e); - } catch (NullPointerException e) { - } - return retVal; + return isUserMobileDataEnabled(subId); } /** @hide */ @@ -7021,4 +7013,101 @@ public class TelephonyManager { } return null; } + + /** + * Turns mobile data on or off. + * If the {@link TelephonyManager} object has been created with + * {@link #createForSubscriptionId}, this API applies to the given subId. + * Otherwise, it applies to {@link SubscriptionManager#getDefaultDataSubscriptionId()} + * + * <p>Requires Permission: + * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE} or that the + * calling app has carrier privileges. + * + * @param enable Whether to enable mobile data. + * + * @see #hasCarrierPrivileges + */ + @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) + public void setUserMobileDataEnabled(boolean enable) { + setUserMobileDataEnabled( + getSubId(SubscriptionManager.getDefaultDataSubscriptionId()), enable); + } + + /** + * Returns whether mobile data is enabled or not per user setting. There are other factors + * that could disable mobile data, but they are not considered here. + * + * If this object has been created with {@link #createForSubscriptionId}, applies to the given + * subId. Otherwise, applies to {@link SubscriptionManager#getDefaultDataSubscriptionId()} + * + * <p>Requires one of the following permissions: + * {@link android.Manifest.permission#ACCESS_NETWORK_STATE ACCESS_NETWORK_STATE}, + * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE}, or that the + * calling app has carrier privileges. + * + * <p>Note that this does not take into account any data restrictions that may be present on the + * calling app. Such restrictions may be inspected with + * {@link ConnectivityManager#getRestrictBackgroundStatus}. + * + * @return true if mobile data is enabled. + * + * @see #hasCarrierPrivileges + */ + @RequiresPermission(anyOf = { + android.Manifest.permission.ACCESS_NETWORK_STATE, + android.Manifest.permission.MODIFY_PHONE_STATE + }) + public boolean isUserMobileDataEnabled() { + return isUserMobileDataEnabled( + getSubId(SubscriptionManager.getDefaultDataSubscriptionId())); + } + + /** + * @hide + * Unlike isUserMobileDataEnabled, this API also evaluates carrierDataEnabled, + * policyDataEnabled etc to give a final decision. + */ + public boolean isMobileDataEnabled() { + boolean retVal = false; + try { + int subId = getSubId(SubscriptionManager.getDefaultDataSubscriptionId()); + ITelephony telephony = getITelephony(); + if (telephony != null) + retVal = telephony.isDataEnabled(subId); + } catch (RemoteException e) { + Log.e(TAG, "Error calling ITelephony#isDataEnabled", e); + } catch (NullPointerException e) { + } + return retVal; + } + + /** + * Utility class of {@link #isUserMobileDataEnabled()}; + */ + private boolean isUserMobileDataEnabled(int subId) { + boolean retVal = false; + try { + ITelephony telephony = getITelephony(); + if (telephony != null) + retVal = telephony.isUserDataEnabled(subId); + } catch (RemoteException e) { + Log.e(TAG, "Error calling ITelephony#isUserDataEnabled", e); + } catch (NullPointerException e) { + } + return retVal; + } + + /** Utility method of {@link #setUserMobileDataEnabled(boolean)} */ + @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) + private void setUserMobileDataEnabled(int subId, boolean enable) { + try { + Log.d(TAG, "setUserMobileDataEnabled: enabled=" + enable); + ITelephony telephony = getITelephony(); + if (telephony != null) + telephony.setUserDataEnabled(subId, enable); + } catch (RemoteException e) { + Log.e(TAG, "Error calling ITelephony#setUserDataEnabled", e); + } + } } diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl index a9f8f45f6c0d..8ea53a594cc2 100644 --- a/telephony/java/com/android/internal/telephony/ITelephony.aidl +++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl @@ -870,16 +870,35 @@ interface ITelephony { * * @param enable true to turn on, else false */ - void setDataEnabled(int subId, boolean enable); + void setUserDataEnabled(int subId, boolean enable); /** * Get the user enabled state of Mobile Data. * + * TODO: remove and use isUserDataEnabled. + * This can't be removed now because some vendor codes + * calls through ITelephony directly while they should + * use TelephonyManager. + * * @return true on enabled */ boolean getDataEnabled(int subId); /** + * Get the user enabled state of Mobile Data. + * + * @return true on enabled + */ + boolean isUserDataEnabled(int subId); + + /** + * Get the overall enabled state of Mobile Data. + * + * @return true on enabled + */ + boolean isDataEnabled(int subId); + + /** * Get P-CSCF address from PCO after data connection is established or modified. * @param apnType the apnType, "ims" for IMS APN, "emergency" for EMERGENCY APN * @param callingPackage The package making the call. diff --git a/tests/Internal/Android.mk b/tests/Internal/Android.mk index 73181ec36e17..b69e3e4fdd4b 100644 --- a/tests/Internal/Android.mk +++ b/tests/Internal/Android.mk @@ -14,6 +14,7 @@ LOCAL_STATIC_JAVA_LIBRARIES := junit \ android-support-test \ mockito-target-minus-junit4 +LOCAL_JAVA_RESOURCE_DIRS := res LOCAL_CERTIFICATE := platform LOCAL_PACKAGE_NAME := InternalTests diff --git a/tests/Internal/AndroidManifest.xml b/tests/Internal/AndroidManifest.xml index a2c95fbbfc0b..e5a56949fe4e 100644 --- a/tests/Internal/AndroidManifest.xml +++ b/tests/Internal/AndroidManifest.xml @@ -18,8 +18,24 @@ <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.android.internal.tests"> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> + <uses-permission android:name="android.permission.BIND_WALLPAPER" /> <application> <uses-library android:name="android.test.runner" /> + + <service android:name="stub.DummyWallpaperService" + android:enabled="true" + android:directBootAware="true" + android:label="Dummy wallpaper" + android:permission="android.permission.BIND_WALLPAPER"> + + <intent-filter> + <action android:name="android.service.wallpaper.WallpaperService" /> + </intent-filter> + + <!-- Link to XML that defines the wallpaper info. --> + <meta-data android:name="android.service.wallpaper" + android:resource="@xml/livewallpaper" /> + </service> </application> <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner" diff --git a/tests/Internal/res/xml/livewallpaper.xml b/tests/Internal/res/xml/livewallpaper.xml new file mode 100644 index 000000000000..dbb0e4761cba --- /dev/null +++ b/tests/Internal/res/xml/livewallpaper.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2017 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License + --> +<wallpaper + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:androidprv="http://schemas.android.com/apk/prv/res/android" + androidprv:supportsAmbientMode="true"/>
\ No newline at end of file diff --git a/tests/Internal/src/android/app/WallpaperInfoTest.java b/tests/Internal/src/android/app/WallpaperInfoTest.java new file mode 100644 index 000000000000..9d26270fb96a --- /dev/null +++ b/tests/Internal/src/android/app/WallpaperInfoTest.java @@ -0,0 +1,68 @@ +/* + * 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.app; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.os.Parcel; +import android.service.wallpaper.WallpaperService; +import android.support.test.InstrumentationRegistry; +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.List; + +/** + * Tests for hidden WallpaperInfo methods. + */ +@RunWith(AndroidJUnit4.class) +@SmallTest +public class WallpaperInfoTest { + + @Test + public void testSupportsAmbientMode() throws Exception { + Context context = InstrumentationRegistry.getTargetContext(); + + Intent intent = new Intent(WallpaperService.SERVICE_INTERFACE); + intent.setPackage("com.android.internal.tests"); + PackageManager pm = context.getPackageManager(); + List<ResolveInfo> result = pm.queryIntentServices(intent, PackageManager.GET_META_DATA); + assertEquals(1, result.size()); + ResolveInfo info = result.get(0); + WallpaperInfo wallpaperInfo = new WallpaperInfo(context, info); + + // Defined as true in the XML + assertTrue("supportsAmbientMode should be true, as defined in the XML.", + wallpaperInfo.getSupportsAmbientMode()); + Parcel parcel = Parcel.obtain(); + wallpaperInfo.writeToParcel(parcel, 0 /* flags */); + parcel.setDataPosition(0); + WallpaperInfo fromParcel = WallpaperInfo.CREATOR.createFromParcel(parcel); + assertTrue("supportsAmbientMode should have been restored from parcelable", + fromParcel.getSupportsAmbientMode()); + parcel.recycle(); + } +} + diff --git a/tests/Internal/src/android/service/wallpaper/WallpaperServiceTest.java b/tests/Internal/src/android/service/wallpaper/WallpaperServiceTest.java new file mode 100644 index 000000000000..f7ce2c72d37e --- /dev/null +++ b/tests/Internal/src/android/service/wallpaper/WallpaperServiceTest.java @@ -0,0 +1,61 @@ +/* + * 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.service.wallpaper; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import android.support.test.filters.SmallTest; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@SmallTest +@RunWith(JUnit4.class) +public class WallpaperServiceTest { + + @Test + public void testDeliversAmbientModeChanged() { + int[] ambientModeChangedCount = {0}; + WallpaperService service = new WallpaperService() { + @Override + public Engine onCreateEngine() { + return new Engine() { + @Override + public void onAmbientModeChanged(boolean inAmbientMode) { + ambientModeChangedCount[0]++; + } + }; + } + }; + WallpaperService.Engine engine = service.onCreateEngine(); + engine.setCreated(true); + + engine.doAmbientModeChanged(false); + assertFalse("ambient mode should be false", engine.isInAmbientMode()); + assertEquals("onAmbientModeChanged should have been called", + ambientModeChangedCount[0], 1); + + engine.doAmbientModeChanged(true); + assertTrue("ambient mode should be false", engine.isInAmbientMode()); + assertEquals("onAmbientModeChanged should have been called", + ambientModeChangedCount[0], 2); + } + +} diff --git a/tests/Internal/src/stub/DummyWallpaperService.java b/tests/Internal/src/stub/DummyWallpaperService.java new file mode 100644 index 000000000000..084c036bea26 --- /dev/null +++ b/tests/Internal/src/stub/DummyWallpaperService.java @@ -0,0 +1,29 @@ +/* + * 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 stub; + +import android.service.wallpaper.WallpaperService; + +/** + * Dummy wallpaper service only for test purposes, won't draw anything. + */ +public class DummyWallpaperService extends WallpaperService { + @Override + public Engine onCreateEngine() { + return new Engine(); + } +} diff --git a/tests/net/java/com/android/server/IpSecServiceTest.java b/tests/net/java/com/android/server/IpSecServiceTest.java index 8683c12e192e..f38a9a346f5b 100644 --- a/tests/net/java/com/android/server/IpSecServiceTest.java +++ b/tests/net/java/com/android/server/IpSecServiceTest.java @@ -27,6 +27,7 @@ import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.mockito.Matchers.anyInt; import static org.mockito.Matchers.anyString; +import static org.mockito.Matchers.argThat; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; @@ -40,10 +41,14 @@ import android.net.IpSecTransform; import android.net.IpSecUdpEncapResponse; import android.os.Binder; import android.os.ParcelFileDescriptor; +import android.os.Process; import android.support.test.filters.SmallTest; import android.support.test.runner.AndroidJUnit4; import android.system.ErrnoException; import android.system.Os; +import android.system.StructStat; + +import dalvik.system.SocketTagger; import java.io.FileDescriptor; import java.net.InetAddress; @@ -56,6 +61,7 @@ import java.util.List; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.ArgumentMatcher; /** Unit tests for {@link IpSecService}. */ @SmallTest @@ -411,4 +417,62 @@ public class IpSecServiceTest { mIpSecService.releaseSecurityParameterIndex(spiResp.resourceId); } } + + @Test + public void testUidFdtagger() throws Exception { + SocketTagger actualSocketTagger = SocketTagger.get(); + + try { + FileDescriptor sockFd = Os.socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); + + // Has to be done after socket creation because BlockGuardOS calls tag on new sockets + SocketTagger mockSocketTagger = mock(SocketTagger.class); + SocketTagger.set(mockSocketTagger); + + mIpSecService.mUidFdTagger.tag(sockFd, Process.LAST_APPLICATION_UID); + verify(mockSocketTagger).tag(eq(sockFd)); + } finally { + SocketTagger.set(actualSocketTagger); + } + } + + /** + * Checks if two file descriptors point to the same file. + * + * <p>According to stat.h documentation, the correct way to check for equivalent or duplicated + * file descriptors is to check their inode and device. These two entries uniquely identify any + * file. + */ + private boolean fileDescriptorsEqual(FileDescriptor fd1, FileDescriptor fd2) { + try { + StructStat fd1Stat = Os.fstat(fd1); + StructStat fd2Stat = Os.fstat(fd2); + + return fd1Stat.st_ino == fd2Stat.st_ino && fd1Stat.st_dev == fd2Stat.st_dev; + } catch (ErrnoException e) { + return false; + } + } + + @Test + public void testOpenUdpEncapSocketTagsSocket() throws Exception { + IpSecService.UidFdTagger mockTagger = mock(IpSecService.UidFdTagger.class); + IpSecService testIpSecService = + new IpSecService(mMockContext, mMockIpSecSrvConfig, mockTagger); + + IpSecUdpEncapResponse udpEncapResp = + testIpSecService.openUdpEncapsulationSocket(0, new Binder()); + assertNotNull(udpEncapResp); + assertEquals(IpSecManager.Status.OK, udpEncapResp.status); + + FileDescriptor sockFd = udpEncapResp.fileDescriptor.getFileDescriptor(); + ArgumentMatcher<FileDescriptor> fdMatcher = + (argFd) -> { + return fileDescriptorsEqual(sockFd, argFd); + }; + verify(mockTagger).tag(argThat(fdMatcher), eq(Os.getuid())); + + testIpSecService.closeUdpEncapsulationSocket(udpEncapResp.resourceId); + udpEncapResp.fileDescriptor.close(); + } } |