diff options
549 files changed, 19502 insertions, 3882 deletions
diff --git a/Android.bp b/Android.bp index d94bd8439349..56d6557ee6e1 100644 --- a/Android.bp +++ b/Android.bp @@ -103,6 +103,8 @@ java_defaults { "core/java/android/app/backup/IRestoreObserver.aidl", "core/java/android/app/backup/IRestoreSession.aidl", "core/java/android/app/backup/ISelectBackupTransportCallback.aidl", + "core/java/android/app/role/IRoleManager.aidl", + "core/java/android/app/role/IRoleManagerCallback.aidl", "core/java/android/app/slice/ISliceManager.aidl", "core/java/android/app/slice/ISliceListener.aidl", "core/java/android/app/timedetector/ITimeDetectorService.aidl", @@ -233,8 +235,7 @@ java_defaults { "core/java/android/os/IDeviceIdentifiersPolicyService.aidl", "core/java/android/os/IDeviceIdleController.aidl", "core/java/android/os/IHardwarePropertiesManager.aidl", - "core/java/android/os/IIncidentManager.aidl", - "core/java/android/os/IIncidentReportStatusListener.aidl", + ":libincident_aidl", "core/java/android/os/IMaintenanceActivityListener.aidl", "core/java/android/os/IMessenger.aidl", "core/java/android/os/INetworkActivityListener.aidl", @@ -247,8 +248,7 @@ java_defaults { "core/java/android/os/IRecoverySystemProgressListener.aidl", "core/java/android/os/IRemoteCallback.aidl", "core/java/android/os/ISchedulingPolicyService.aidl", - "core/java/android/os/IStatsCompanionService.aidl", - "core/java/android/os/IStatsManager.aidl", + ":statsd_aidl", "core/java/android/os/ISystemUpdateManager.aidl", "core/java/android/os/IThermalEventListener.aidl", "core/java/android/os/IThermalService.aidl", @@ -259,6 +259,7 @@ java_defaults { "core/java/android/os/storage/IStorageEventListener.aidl", "core/java/android/os/storage/IStorageShutdownObserver.aidl", "core/java/android/os/storage/IObbActionListener.aidl", + "core/java/android/rolecontrollerservice/IRoleControllerService.aidl", ":keystore_aidl", "core/java/android/security/keymaster/IKeyAttestationApplicationIdProvider.aidl", "core/java/android/service/autofill/IAutoFillService.aidl", @@ -721,6 +722,22 @@ java_defaults { } +filegroup { + name: "libincident_aidl", + srcs: [ + "core/java/android/os/IIncidentManager.aidl", + "core/java/android/os/IIncidentReportStatusListener.aidl", + ], +} + +filegroup { + name: "statsd_aidl", + srcs: [ + "core/java/android/os/IStatsCompanionService.aidl", + "core/java/android/os/IStatsManager.aidl", + ], +} + java_library { name: "framework", defaults: ["framework-defaults"], @@ -931,12 +948,14 @@ gensrcs { "core/proto/android/os/batterytype.proto", "core/proto/android/os/cpufreq.proto", "core/proto/android/os/cpuinfo.proto", + "core/proto/android/os/data.proto", "core/proto/android/os/kernelwake.proto", "core/proto/android/os/pagetypeinfo.proto", "core/proto/android/os/procrank.proto", "core/proto/android/os/ps.proto", "core/proto/android/os/system_properties.proto", "core/proto/android/util/event_log_tags.proto", + "core/proto/android/util/log.proto", ], // Append protoc-gen-cppstream tool's PATH otherwise aprotoc can't find the plugin tool diff --git a/PREUPLOAD.cfg b/PREUPLOAD.cfg index 5c212213f2d1..ec3366c2eb2f 100644 --- a/PREUPLOAD.cfg +++ b/PREUPLOAD.cfg @@ -1,20 +1,5 @@ [Hook Scripts] checkstyle_hook = ${REPO_ROOT}/prebuilts/checkstyle/checkstyle.py --sha ${PREUPLOAD_COMMIT} - -fw core/ - graphics/java/android - packages/PrintRecommendationService/ - packages/PrintSpooler/ - packages/PackageInstaller/ - packages/SystemUI/ - services/print/ - services/usb/ - telephony/ - tests/ActivityViewTest/ - tests/LotsOfApps/ - tests/NativeProcessesMemoryTest/ - tests/OdmApps/ - tests/SystemMemoryTest/ - wifi/ api_lint_hook = ${REPO_ROOT}/frameworks/base/tools/apilint/apilint_sha.sh ${PREUPLOAD_COMMIT} diff --git a/apct-tests/perftests/core/src/android/os/KernelCpuThreadReaderPerfTest.java b/apct-tests/perftests/core/src/android/os/KernelCpuThreadReaderPerfTest.java new file mode 100644 index 000000000000..9034034539e9 --- /dev/null +++ b/apct-tests/perftests/core/src/android/os/KernelCpuThreadReaderPerfTest.java @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.os; + +import static org.junit.Assert.assertNotNull; + +import android.perftests.utils.BenchmarkState; +import android.perftests.utils.PerfStatusReporter; +import android.support.test.filters.LargeTest; +import android.support.test.runner.AndroidJUnit4; + +import com.android.internal.os.KernelCpuThreadReader; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + + +/** + * Performance tests collecting per-thread CPU data. + */ +@RunWith(AndroidJUnit4.class) +@LargeTest +public class KernelCpuThreadReaderPerfTest { + @Rule + public final PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter(); + + private final KernelCpuThreadReader mKernelCpuThreadReader = KernelCpuThreadReader.create(); + + @Test + public void timeReadCurrentProcessCpuUsage() { + final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + assertNotNull(mKernelCpuThreadReader); + while (state.keepRunning()) { + this.mKernelCpuThreadReader.getCurrentProcessCpuUsage(); + } + } +} diff --git a/apct-tests/perftests/core/src/android/text/StaticLayoutPerfTest.java b/apct-tests/perftests/core/src/android/text/StaticLayoutPerfTest.java index e224fa39422c..35d380232bec 100644 --- a/apct-tests/perftests/core/src/android/text/StaticLayoutPerfTest.java +++ b/apct-tests/perftests/core/src/android/text/StaticLayoutPerfTest.java @@ -245,10 +245,11 @@ public class StaticLayoutPerfTest { state.pauseTiming(); final StaticLayout layout = StaticLayout.Builder.obtain(text, 0, text.length(), PAINT, TEXT_WIDTH).build(); - final RecordingCanvas c = node.start(1200, 200); + final RecordingCanvas c = node.startRecording(1200, 200); state.resumeTiming(); layout.draw(c); + node.endRecording(); } } @@ -261,10 +262,11 @@ public class StaticLayoutPerfTest { final CharSequence text = mTextUtil.nextRandomParagraph(WORD_LENGTH, STYLE_TEXT); final StaticLayout layout = StaticLayout.Builder.obtain(text, 0, text.length(), PAINT, TEXT_WIDTH).build(); - final RecordingCanvas c = node.start(1200, 200); + final RecordingCanvas c = node.startRecording(1200, 200); state.resumeTiming(); layout.draw(c); + node.endRecording(); } } @@ -277,10 +279,11 @@ public class StaticLayoutPerfTest { final CharSequence text = mTextUtil.nextRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT); final StaticLayout layout = StaticLayout.Builder.obtain(text, 0, text.length(), PAINT, TEXT_WIDTH).build(); - final RecordingCanvas c = node.start(1200, 200); + final RecordingCanvas c = node.startRecording(1200, 200); state.resumeTiming(); layout.draw(c); + node.endRecording(); } } @@ -293,11 +296,12 @@ public class StaticLayoutPerfTest { final CharSequence text = mTextUtil.nextRandomParagraph(WORD_LENGTH, STYLE_TEXT); final StaticLayout layout = StaticLayout.Builder.obtain(text, 0, text.length(), PAINT, TEXT_WIDTH).build(); - final RecordingCanvas c = node.start(1200, 200); + final RecordingCanvas c = node.startRecording(1200, 200); Canvas.freeTextLayoutCaches(); state.resumeTiming(); layout.draw(c); + node.endRecording(); } } @@ -310,11 +314,12 @@ public class StaticLayoutPerfTest { final CharSequence text = mTextUtil.nextRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT); final StaticLayout layout = StaticLayout.Builder.obtain(text, 0, text.length(), PAINT, TEXT_WIDTH).build(); - final RecordingCanvas c = node.start(1200, 200); + final RecordingCanvas c = node.startRecording(1200, 200); Canvas.freeTextLayoutCaches(); state.resumeTiming(); layout.draw(c); + node.endRecording(); } } @@ -328,10 +333,11 @@ public class StaticLayoutPerfTest { mTextUtil.nextRandomParagraph(WORD_LENGTH, STYLE_TEXT), PAINT); final StaticLayout layout = StaticLayout.Builder.obtain(text, 0, text.length(), PAINT, TEXT_WIDTH).build(); - final RecordingCanvas c = node.start(1200, 200); + final RecordingCanvas c = node.startRecording(1200, 200); state.resumeTiming(); layout.draw(c); + node.endRecording(); } } @@ -345,10 +351,11 @@ public class StaticLayoutPerfTest { mTextUtil.nextRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT), PAINT); final StaticLayout layout = StaticLayout.Builder.obtain(text, 0, text.length(), PAINT, TEXT_WIDTH).build(); - final RecordingCanvas c = node.start(1200, 200); + final RecordingCanvas c = node.startRecording(1200, 200); state.resumeTiming(); layout.draw(c); + node.endRecording(); } } @@ -362,11 +369,12 @@ public class StaticLayoutPerfTest { mTextUtil.nextRandomParagraph(WORD_LENGTH, STYLE_TEXT), PAINT); final StaticLayout layout = StaticLayout.Builder.obtain(text, 0, text.length(), PAINT, TEXT_WIDTH).build(); - final RecordingCanvas c = node.start(1200, 200); + final RecordingCanvas c = node.startRecording(1200, 200); Canvas.freeTextLayoutCaches(); state.resumeTiming(); layout.draw(c); + node.endRecording(); } } @@ -380,11 +388,12 @@ public class StaticLayoutPerfTest { mTextUtil.nextRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT), PAINT); final StaticLayout layout = StaticLayout.Builder.obtain(text, 0, text.length(), PAINT, TEXT_WIDTH).build(); - final RecordingCanvas c = node.start(1200, 200); + final RecordingCanvas c = node.startRecording(1200, 200); Canvas.freeTextLayoutCaches(); state.resumeTiming(); layout.draw(c); + node.endRecording(); } } diff --git a/api/current.txt b/api/current.txt index 12184f140060..2f69e88ca077 100755 --- a/api/current.txt +++ b/api/current.txt @@ -7287,6 +7287,18 @@ package android.app.job { } +package android.app.role { + + public final class RoleManager { + method public android.content.Intent createRequestRoleIntent(java.lang.String); + method public boolean isRoleAvailable(java.lang.String); + method public boolean isRoleHeld(java.lang.String); + field public static final java.lang.String ROLE_DIALER = "android.app.role.DIALER"; + field public static final java.lang.String ROLE_SMS = "android.app.role.SMS"; + } + +} + package android.app.slice { public final class Slice implements android.os.Parcelable { @@ -9631,6 +9643,7 @@ package android.content { field public static final java.lang.String PRINT_SERVICE = "print"; field public static final int RECEIVER_VISIBLE_TO_INSTANT_APPS = 1; // 0x1 field public static final java.lang.String RESTRICTIONS_SERVICE = "restrictions"; + field public static final java.lang.String ROLE_SERVICE = "role"; field public static final java.lang.String SEARCH_SERVICE = "search"; field public static final java.lang.String SENSOR_SERVICE = "sensor"; field public static final java.lang.String SHORTCUT_SERVICE = "shortcut"; @@ -17388,6 +17401,63 @@ package android.icu.lang { field public static final int VOWEL_JAMO = 2; // 0x2 } + public static abstract interface UCharacter.IndicPositionalCategory { + field public static final int BOTTOM = 1; // 0x1 + field public static final int BOTTOM_AND_LEFT = 2; // 0x2 + field public static final int BOTTOM_AND_RIGHT = 3; // 0x3 + field public static final int LEFT = 4; // 0x4 + field public static final int LEFT_AND_RIGHT = 5; // 0x5 + field public static final int NA = 0; // 0x0 + field public static final int OVERSTRUCK = 6; // 0x6 + field public static final int RIGHT = 7; // 0x7 + field public static final int TOP = 8; // 0x8 + field public static final int TOP_AND_BOTTOM = 9; // 0x9 + field public static final int TOP_AND_BOTTOM_AND_RIGHT = 10; // 0xa + field public static final int TOP_AND_LEFT = 11; // 0xb + field public static final int TOP_AND_LEFT_AND_RIGHT = 12; // 0xc + field public static final int TOP_AND_RIGHT = 13; // 0xd + field public static final int VISUAL_ORDER_LEFT = 14; // 0xe + } + + public static abstract interface UCharacter.IndicSyllabicCategory { + field public static final int AVAGRAHA = 1; // 0x1 + field public static final int BINDU = 2; // 0x2 + field public static final int BRAHMI_JOINING_NUMBER = 3; // 0x3 + field public static final int CANTILLATION_MARK = 4; // 0x4 + field public static final int CONSONANT = 5; // 0x5 + field public static final int CONSONANT_DEAD = 6; // 0x6 + field public static final int CONSONANT_FINAL = 7; // 0x7 + field public static final int CONSONANT_HEAD_LETTER = 8; // 0x8 + field public static final int CONSONANT_INITIAL_POSTFIXED = 9; // 0x9 + field public static final int CONSONANT_KILLER = 10; // 0xa + field public static final int CONSONANT_MEDIAL = 11; // 0xb + field public static final int CONSONANT_PLACEHOLDER = 12; // 0xc + field public static final int CONSONANT_PRECEDING_REPHA = 13; // 0xd + field public static final int CONSONANT_PREFIXED = 14; // 0xe + field public static final int CONSONANT_SUBJOINED = 15; // 0xf + field public static final int CONSONANT_SUCCEEDING_REPHA = 16; // 0x10 + field public static final int CONSONANT_WITH_STACKER = 17; // 0x11 + field public static final int GEMINATION_MARK = 18; // 0x12 + field public static final int INVISIBLE_STACKER = 19; // 0x13 + field public static final int JOINER = 20; // 0x14 + field public static final int MODIFYING_LETTER = 21; // 0x15 + field public static final int NON_JOINER = 22; // 0x16 + field public static final int NUKTA = 23; // 0x17 + field public static final int NUMBER = 24; // 0x18 + field public static final int NUMBER_JOINER = 25; // 0x19 + field public static final int OTHER = 0; // 0x0 + field public static final int PURE_KILLER = 26; // 0x1a + field public static final int REGISTER_SHIFTER = 27; // 0x1b + field public static final int SYLLABLE_MODIFIER = 28; // 0x1c + field public static final int TONE_LETTER = 29; // 0x1d + field public static final int TONE_MARK = 30; // 0x1e + field public static final int VIRAMA = 31; // 0x1f + field public static final int VISARGA = 32; // 0x20 + field public static final int VOWEL = 33; // 0x21 + field public static final int VOWEL_DEPENDENT = 34; // 0x22 + field public static final int VOWEL_INDEPENDENT = 35; // 0x23 + } + public static abstract interface UCharacter.JoiningGroup { field public static final int AFRICAN_FEH = 86; // 0x56 field public static final int AFRICAN_NOON = 87; // 0x57 @@ -18171,6 +18241,13 @@ package android.icu.lang { field public static final int ZANABAZAR_SQUARE_ID = 280; // 0x118 } + public static abstract interface UCharacter.VerticalOrientation { + field public static final int ROTATED = 0; // 0x0 + field public static final int TRANSFORMED_ROTATED = 1; // 0x1 + field public static final int TRANSFORMED_UPRIGHT = 2; // 0x2 + field public static final int UPRIGHT = 3; // 0x3 + } + public static abstract interface UCharacter.WordBreak { field public static final int ALETTER = 1; // 0x1 field public static final int CR = 8; // 0x8 @@ -18342,6 +18419,8 @@ package android.icu.lang { field public static final int IDS_TRINARY_OPERATOR = 19; // 0x13 field public static final int ID_CONTINUE = 15; // 0xf field public static final int ID_START = 16; // 0x10 + field public static final int INDIC_POSITIONAL_CATEGORY = 4118; // 0x1016 + field public static final int INDIC_SYLLABIC_CATEGORY = 4119; // 0x1017 field public static final int INT_START = 4096; // 0x1000 field public static final int JOINING_GROUP = 4102; // 0x1006 field public static final int JOINING_TYPE = 4103; // 0x1007 @@ -18395,6 +18474,7 @@ package android.icu.lang { field public static final int UPPERCASE = 30; // 0x1e field public static final int UPPERCASE_MAPPING = 16396; // 0x400c field public static final int VARIATION_SELECTOR = 36; // 0x24 + field public static final int VERTICAL_ORIENTATION = 4120; // 0x1018 field public static final int WHITE_SPACE = 31; // 0x1f field public static final int WORD_BREAK = 4116; // 0x1014 field public static final int XID_CONTINUE = 32; // 0x20 @@ -18925,6 +19005,7 @@ package android.icu.text { method public int preceding(int); method public abstract int previous(); method public void setText(java.lang.String); + method public void setText(java.lang.CharSequence); method public abstract void setText(java.text.CharacterIterator); field public static final int DONE = -1; // 0xffffffff field public static final int KIND_CHARACTER = 0; // 0x0 @@ -18953,24 +19034,31 @@ package android.icu.text { } public static final class CaseMap.Fold extends android.icu.text.CaseMap { + method public java.lang.String apply(java.lang.CharSequence); method public <A extends java.lang.Appendable> A apply(java.lang.CharSequence, A, android.icu.text.Edits); method public android.icu.text.CaseMap.Fold omitUnchangedText(); method public android.icu.text.CaseMap.Fold turkic(); } public static final class CaseMap.Lower extends android.icu.text.CaseMap { + method public java.lang.String apply(java.util.Locale, java.lang.CharSequence); method public <A extends java.lang.Appendable> A apply(java.util.Locale, java.lang.CharSequence, A, android.icu.text.Edits); method public android.icu.text.CaseMap.Lower omitUnchangedText(); } public static final class CaseMap.Title extends android.icu.text.CaseMap { + method public android.icu.text.CaseMap.Title adjustToCased(); + method public java.lang.String apply(java.util.Locale, android.icu.text.BreakIterator, java.lang.CharSequence); method public <A extends java.lang.Appendable> A apply(java.util.Locale, android.icu.text.BreakIterator, java.lang.CharSequence, A, android.icu.text.Edits); method public android.icu.text.CaseMap.Title noBreakAdjustment(); method public android.icu.text.CaseMap.Title noLowercase(); method public android.icu.text.CaseMap.Title omitUnchangedText(); + method public android.icu.text.CaseMap.Title sentences(); + method public android.icu.text.CaseMap.Title wholeString(); } public static final class CaseMap.Upper extends android.icu.text.CaseMap { + method public java.lang.String apply(java.util.Locale, java.lang.CharSequence); method public <A extends java.lang.Appendable> A apply(java.util.Locale, java.lang.CharSequence, A, android.icu.text.Edits); method public android.icu.text.CaseMap.Upper omitUnchangedText(); } @@ -18978,7 +19066,6 @@ package android.icu.text { public final class CollationElementIterator { method public int getMaxExpansion(int); method public int getOffset(); - method public deprecated int hashCode(); method public int next(); method public int previous(); method public static int primaryOrder(int); @@ -19091,7 +19178,6 @@ package android.icu.text { method public static android.icu.text.CurrencyPluralInfo getInstance(android.icu.util.ULocale); method public android.icu.util.ULocale getLocale(); method public android.icu.text.PluralRules getPluralRules(); - method public deprecated int hashCode(); method public void setCurrencyPluralPattern(java.lang.String, java.lang.String); method public void setLocale(android.icu.util.ULocale); method public void setPluralRules(java.lang.String); @@ -19381,7 +19467,6 @@ package android.icu.text { method public boolean firstDateInPtnIsLaterDate(); method public java.lang.String getFirstPart(); method public java.lang.String getSecondPart(); - method public deprecated java.lang.String toString(); } public class DateTimePatternGenerator implements java.lang.Cloneable android.icu.util.Freezable { @@ -19541,6 +19626,8 @@ package android.icu.text { ctor public DecimalFormatSymbols(java.util.Locale); ctor public DecimalFormatSymbols(android.icu.util.ULocale); method public java.lang.Object clone(); + method public static android.icu.text.DecimalFormatSymbols forNumberingSystem(java.util.Locale, android.icu.text.NumberingSystem); + method public static android.icu.text.DecimalFormatSymbols forNumberingSystem(android.icu.util.ULocale, android.icu.text.NumberingSystem); method public static java.util.Locale[] getAvailableLocales(); method public android.icu.util.Currency getCurrency(); method public java.lang.String getCurrencySymbol(); @@ -19650,11 +19737,15 @@ package android.icu.text { method public android.icu.text.Edits.Iterator getFineIterator(); method public boolean hasChanges(); method public int lengthDelta(); + method public android.icu.text.Edits mergeAndAppend(android.icu.text.Edits, android.icu.text.Edits); + method public int numberOfChanges(); method public void reset(); } public static final class Edits.Iterator { method public int destinationIndex(); + method public int destinationIndexFromSourceIndex(int); + method public boolean findDestinationIndex(int); method public boolean findSourceIndex(int); method public boolean hasChange(); method public int newLength(); @@ -19662,6 +19753,7 @@ package android.icu.text { method public int oldLength(); method public int replacementIndex(); method public int sourceIndex(); + method public int sourceIndexFromDestinationIndex(int); } public abstract class IDNA { @@ -20064,6 +20156,7 @@ package android.icu.text { method public int getRadix(); method public boolean isAlgorithmic(); method public static boolean isValidDigitString(java.lang.String); + field public static final android.icu.text.NumberingSystem LATIN; } public class PluralFormat extends android.icu.text.UFormat { @@ -20101,7 +20194,6 @@ package android.icu.text { method public java.util.Set<java.lang.String> getKeywords(); method public java.util.Collection<java.lang.Double> getSamples(java.lang.String); method public double getUniqueKeywordValue(java.lang.String); - method public deprecated int hashCode(); method public static android.icu.text.PluralRules parseDescription(java.lang.String) throws java.text.ParseException; method public java.lang.String select(double); field public static final android.icu.text.PluralRules DEFAULT; @@ -20344,7 +20436,6 @@ package android.icu.text { ctor public StringPrepParseException(java.lang.String, int, java.lang.String, int); ctor public StringPrepParseException(java.lang.String, int, java.lang.String, int, int); method public int getError(); - method public deprecated int hashCode(); field public static final int ACE_PREFIX_ERROR = 6; // 0x6 field public static final int BUFFER_OVERFLOW_ERROR = 9; // 0x9 field public static final int CHECK_BIDI_ERROR = 4; // 0x4 @@ -20975,6 +21066,7 @@ package android.icu.util { public class Currency extends android.icu.util.MeasureUnit { ctor protected Currency(java.lang.String); + method public static android.icu.util.Currency fromJavaCurrency(java.util.Currency); method public static java.util.Set<android.icu.util.Currency> getAvailableCurrencies(); method public static java.lang.String[] getAvailableCurrencyCodes(android.icu.util.ULocale, java.util.Date); method public static java.lang.String[] getAvailableCurrencyCodes(java.util.Locale, java.util.Date); @@ -21000,6 +21092,7 @@ package android.icu.util { method public java.lang.String getSymbol(java.util.Locale); method public java.lang.String getSymbol(android.icu.util.ULocale); method public static boolean isAvailable(java.lang.String, java.util.Date, java.util.Date); + method public java.util.Currency toJavaCurrency(); field public static final int LONG_NAME = 1; // 0x1 field public static final int PLURAL_LONG_NAME = 2; // 0x2 field public static final int SYMBOL_NAME = 0; // 0x0 @@ -21015,6 +21108,8 @@ package android.icu.util { public class CurrencyAmount extends android.icu.util.Measure { ctor public CurrencyAmount(java.lang.Number, android.icu.util.Currency); ctor public CurrencyAmount(double, android.icu.util.Currency); + ctor public CurrencyAmount(java.lang.Number, java.util.Currency); + ctor public CurrencyAmount(double, java.util.Currency); method public android.icu.util.Currency getCurrency(); } @@ -36721,6 +36816,7 @@ package android.provider { method public android.database.Cursor queryChildDocuments(java.lang.String, java.lang.String[], android.os.Bundle) throws java.io.FileNotFoundException; method public abstract android.database.Cursor queryDocument(java.lang.String, java.lang.String[]) throws java.io.FileNotFoundException; method public android.database.Cursor queryRecentDocuments(java.lang.String, java.lang.String[]) throws java.io.FileNotFoundException; + method public android.database.Cursor queryRecentDocuments(java.lang.String, java.lang.String[], android.os.Bundle, android.os.CancellationSignal) throws java.io.FileNotFoundException; method public abstract android.database.Cursor queryRoots(java.lang.String[]) throws java.io.FileNotFoundException; method public android.database.Cursor querySearchDocuments(java.lang.String, java.lang.String, java.lang.String[]) throws java.io.FileNotFoundException; method public void removeDocument(java.lang.String, java.lang.String) throws java.io.FileNotFoundException; @@ -42512,6 +42608,7 @@ package android.telephony { field public static final java.lang.String KEY_SIM_NETWORK_UNLOCK_ALLOW_DISMISS_BOOL = "sim_network_unlock_allow_dismiss_bool"; field public static final java.lang.String KEY_SMS_REQUIRES_DESTINATION_NUMBER_CONVERSION_BOOL = "sms_requires_destination_number_conversion_bool"; field public static final java.lang.String KEY_SUPPORT_3GPP_CALL_FORWARDING_WHILE_ROAMING_BOOL = "support_3gpp_call_forwarding_while_roaming_bool"; + field public static final java.lang.String KEY_SUPPORT_CLIR_NETWORK_DEFAULT_BOOL = "support_clir_network_default_bool"; field public static final java.lang.String KEY_SUPPORT_CONFERENCE_CALL_BOOL = "support_conference_call_bool"; field public static final java.lang.String KEY_SUPPORT_EMERGENCY_SMS_OVER_IMS_BOOL = "support_emergency_sms_over_ims_bool"; field public static final java.lang.String KEY_SUPPORT_PAUSE_IMS_VIDEO_CALLS_BOOL = "support_pause_ims_video_calls_bool"; @@ -42618,6 +42715,7 @@ package android.telephony { field public static final int CONNECTION_SECONDARY_SERVING = 2; // 0x2 field public static final int CONNECTION_UNKNOWN = 2147483647; // 0x7fffffff field public static final android.os.Parcelable.Creator<android.telephony.CellInfo> CREATOR; + field public static final int UNAVAILABLE = 2147483647; // 0x7fffffff } public final class CellInfoCdma extends android.telephony.CellInfo implements android.os.Parcelable { @@ -42914,6 +43012,7 @@ package android.telephony { field public static final int LISTEN_CELL_LOCATION = 16; // 0x10 field public static final int LISTEN_DATA_ACTIVITY = 128; // 0x80 field public static final int LISTEN_DATA_CONNECTION_STATE = 64; // 0x40 + field public static final int LISTEN_EMERGENCY_NUMBER_LIST = 16777216; // 0x1000000 field public static final int LISTEN_MESSAGE_WAITING_INDICATOR = 4; // 0x4 field public static final int LISTEN_NONE = 0; // 0x0 field public static final int LISTEN_SERVICE_STATE = 1; // 0x1 @@ -43224,6 +43323,8 @@ package android.telephony { method public int getCallState(); method public android.os.PersistableBundle getCarrierConfig(); method public deprecated android.telephony.CellLocation getCellLocation(); + method public java.util.Map<java.lang.Integer, java.util.List<android.telephony.emergency.EmergencyNumber>> getCurrentEmergencyNumberList(); + method public java.util.Map<java.lang.Integer, java.util.List<android.telephony.emergency.EmergencyNumber>> getCurrentEmergencyNumberList(int); method public int getDataActivity(); method public int getDataNetworkType(); method public int getDataState(); @@ -43277,6 +43378,7 @@ 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 isCurrentEmergencyNumber(java.lang.String); method public boolean isDataEnabled(); method public boolean isDataRoamingEnabled(); method public boolean isHearingAidCompatibilitySupported(); @@ -45744,6 +45846,7 @@ package android.transition { ctor public deprecated Scene(android.view.ViewGroup, android.view.ViewGroup); method public void enter(); method public void exit(); + method public static android.transition.Scene getCurrentScene(android.view.View); method public static android.transition.Scene getSceneForLayout(android.view.ViewGroup, int, android.content.Context); method public android.view.ViewGroup getSceneRoot(); method public void setEnterAction(java.lang.Runnable); @@ -51332,6 +51435,99 @@ package android.view.inputmethod { package android.view.textclassifier { + public final class ConversationActions implements android.os.Parcelable { + ctor public ConversationActions(java.util.List<android.view.textclassifier.ConversationActions.ConversationAction>); + method public int describeContents(); + method public java.util.List<android.view.textclassifier.ConversationActions.ConversationAction> getConversationActions(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator<android.view.textclassifier.ConversationActions> CREATOR; + field public static final java.lang.String HINT_FOR_IN_APP = "in_app"; + field public static final java.lang.String HINT_FOR_NOTIFICATION = "notification"; + field public static final java.lang.String TYPE_CALL_PHONE = "call_phone"; + field public static final java.lang.String TYPE_CREATE_REMINDER = "create_reminder"; + field public static final java.lang.String TYPE_OPEN_URL = "open_url"; + field public static final java.lang.String TYPE_SEND_EMAIL = "send_email"; + field public static final java.lang.String TYPE_SEND_SMS = "send_sms"; + field public static final java.lang.String TYPE_SHARE_LOCATION = "share_location"; + field public static final java.lang.String TYPE_TEXT_REPLY = "text_reply"; + field public static final java.lang.String TYPE_TRACK_FLIGHT = "track_flight"; + field public static final java.lang.String TYPE_VIEW_CALENDAR = "view_calendar"; + field public static final java.lang.String TYPE_VIEW_MAP = "view_map"; + } + + public static final class ConversationActions.ConversationAction implements android.os.Parcelable { + method public int describeContents(); + method public android.app.RemoteAction getAction(); + method public float getConfidenceScore(); + method public android.os.Bundle getExtras(); + method public java.lang.CharSequence getTextReply(); + method public java.lang.String getType(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator<android.view.textclassifier.ConversationActions.ConversationAction> CREATOR; + } + + public static final class ConversationActions.ConversationAction.Builder { + ctor public ConversationActions.ConversationAction.Builder(java.lang.String); + method public android.view.textclassifier.ConversationActions.ConversationAction build(); + method public android.view.textclassifier.ConversationActions.ConversationAction.Builder setAction(android.app.RemoteAction); + method public android.view.textclassifier.ConversationActions.ConversationAction.Builder setConfidenceScore(float); + method public android.view.textclassifier.ConversationActions.ConversationAction.Builder setExtras(android.os.Bundle); + method public android.view.textclassifier.ConversationActions.ConversationAction.Builder setTextReply(java.lang.CharSequence); + } + + public static final class ConversationActions.Message implements android.os.Parcelable { + method public int describeContents(); + method public android.app.Person getAuthor(); + method public android.os.Bundle getExtras(); + method public java.lang.CharSequence getText(); + method public java.time.ZonedDateTime getTime(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator<android.view.textclassifier.ConversationActions.Message> CREATOR; + } + + public static final class ConversationActions.Message.Builder { + ctor public ConversationActions.Message.Builder(); + method public android.view.textclassifier.ConversationActions.Message build(); + method public android.view.textclassifier.ConversationActions.Message.Builder setAuthor(android.app.Person); + method public android.view.textclassifier.ConversationActions.Message.Builder setComposeTime(java.time.ZonedDateTime); + method public android.view.textclassifier.ConversationActions.Message.Builder setExtras(android.os.Bundle); + method public android.view.textclassifier.ConversationActions.Message.Builder setText(java.lang.CharSequence); + } + + public static final class ConversationActions.Request implements android.os.Parcelable { + method public int describeContents(); + method public java.util.List<android.view.textclassifier.ConversationActions.Message> getConversation(); + method public java.util.List<java.lang.String> getHints(); + method public int getMaxSuggestions(); + method public android.view.textclassifier.ConversationActions.TypeConfig getTypeConfig(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator<android.view.textclassifier.ConversationActions.Request> CREATOR; + } + + public static final class ConversationActions.Request.Builder { + ctor public ConversationActions.Request.Builder(java.util.List<android.view.textclassifier.ConversationActions.Message>); + method public android.view.textclassifier.ConversationActions.Request build(); + method public android.view.textclassifier.ConversationActions.Request.Builder setHints(java.util.List<java.lang.String>); + method public android.view.textclassifier.ConversationActions.Request.Builder setMaxSuggestions(int); + method public android.view.textclassifier.ConversationActions.Request.Builder setTypeConfig(android.view.textclassifier.ConversationActions.TypeConfig); + } + + public static final class ConversationActions.TypeConfig implements android.os.Parcelable { + method public int describeContents(); + method public java.util.Collection<java.lang.String> resolveTypes(java.util.Collection<java.lang.String>); + method public boolean shouldIncludeTypesFromTextClassifier(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator<android.view.textclassifier.ConversationActions.TypeConfig> CREATOR; + } + + public static final class ConversationActions.TypeConfig.Builder { + ctor public ConversationActions.TypeConfig.Builder(); + method public android.view.textclassifier.ConversationActions.TypeConfig build(); + method public android.view.textclassifier.ConversationActions.TypeConfig.Builder includeTypesFromTextClassifier(boolean); + method public android.view.textclassifier.ConversationActions.TypeConfig.Builder setExcludedTypes(java.util.Collection<java.lang.String>); + method public android.view.textclassifier.ConversationActions.TypeConfig.Builder setIncludedTypes(java.util.Collection<java.lang.String>); + } + public final class SelectionEvent implements android.os.Parcelable { method public static android.view.textclassifier.SelectionEvent createSelectionActionEvent(int, int, int); method public static android.view.textclassifier.SelectionEvent createSelectionActionEvent(int, int, int, android.view.textclassifier.TextClassification); @@ -51386,6 +51582,7 @@ package android.view.textclassifier { method public float getConfidenceScore(java.lang.String); method public java.lang.String getEntity(int); method public int getEntityCount(); + method public android.os.Bundle getExtras(); method public deprecated android.graphics.drawable.Drawable getIcon(); method public java.lang.String getId(); method public deprecated android.content.Intent getIntent(); @@ -51401,6 +51598,7 @@ package android.view.textclassifier { method public android.view.textclassifier.TextClassification.Builder addAction(android.app.RemoteAction); method public android.view.textclassifier.TextClassification build(); method public android.view.textclassifier.TextClassification.Builder setEntityType(java.lang.String, float); + method public android.view.textclassifier.TextClassification.Builder setExtras(android.os.Bundle); method public deprecated android.view.textclassifier.TextClassification.Builder setIcon(android.graphics.drawable.Drawable); method public android.view.textclassifier.TextClassification.Builder setId(java.lang.String); method public deprecated android.view.textclassifier.TextClassification.Builder setIntent(android.content.Intent); @@ -51413,6 +51611,7 @@ package android.view.textclassifier { method public int describeContents(); method public android.os.LocaleList getDefaultLocales(); method public int getEndIndex(); + method public android.os.Bundle getExtras(); method public java.time.ZonedDateTime getReferenceTime(); method public int getStartIndex(); method public java.lang.CharSequence getText(); @@ -51424,6 +51623,7 @@ package android.view.textclassifier { ctor public TextClassification.Request.Builder(java.lang.CharSequence, int, int); method public android.view.textclassifier.TextClassification.Request build(); method public android.view.textclassifier.TextClassification.Request.Builder setDefaultLocales(android.os.LocaleList); + method public android.view.textclassifier.TextClassification.Request.Builder setExtras(android.os.Bundle); method public android.view.textclassifier.TextClassification.Request.Builder setReferenceTime(java.time.ZonedDateTime); } @@ -51468,6 +51668,7 @@ package android.view.textclassifier { method public default int getMaxGenerateLinksTextLength(); method public default boolean isDestroyed(); method public default void onSelectionEvent(android.view.textclassifier.SelectionEvent); + method public default android.view.textclassifier.ConversationActions suggestConversationActions(android.view.textclassifier.ConversationActions.Request); method public default android.view.textclassifier.TextSelection suggestSelection(android.view.textclassifier.TextSelection.Request); method public default android.view.textclassifier.TextSelection suggestSelection(java.lang.CharSequence, int, int, android.os.LocaleList); field public static final java.lang.String HINT_TEXT_IS_EDITABLE = "android.text_is_editable"; diff --git a/api/system-current.txt b/api/system-current.txt index 43616cd28807..7e2953512431 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -93,6 +93,7 @@ package android { field public static final java.lang.String LOCATION_HARDWARE = "android.permission.LOCATION_HARDWARE"; field public static final java.lang.String LOCK_DEVICE = "android.permission.LOCK_DEVICE"; field public static final java.lang.String LOOP_RADIO = "android.permission.LOOP_RADIO"; + field public static final java.lang.String MANAGE_ACCESSIBILITY = "android.permission.MANAGE_ACCESSIBILITY"; field public static final java.lang.String MANAGE_ACTIVITY_STACKS = "android.permission.MANAGE_ACTIVITY_STACKS"; field public static final java.lang.String MANAGE_APP_OPS_RESTRICTIONS = "android.permission.MANAGE_APP_OPS_RESTRICTIONS"; field public static final java.lang.String MANAGE_APP_TOKENS = "android.permission.MANAGE_APP_TOKENS"; @@ -101,6 +102,7 @@ package android { field public static final java.lang.String MANAGE_CA_CERTIFICATES = "android.permission.MANAGE_CA_CERTIFICATES"; field public static final java.lang.String MANAGE_DEVICE_ADMINS = "android.permission.MANAGE_DEVICE_ADMINS"; field public static final java.lang.String MANAGE_IPSEC_TUNNELS = "android.permission.MANAGE_IPSEC_TUNNELS"; + field public static final java.lang.String MANAGE_ROLE_HOLDERS = "android.permission.MANAGE_ROLE_HOLDERS"; field public static final java.lang.String MANAGE_SOUND_TRIGGER = "android.permission.MANAGE_SOUND_TRIGGER"; field public static final java.lang.String MANAGE_SUBSCRIPTION_PLANS = "android.permission.MANAGE_SUBSCRIPTION_PLANS"; field public static final java.lang.String MANAGE_USB = "android.permission.MANAGE_USB"; @@ -293,6 +295,8 @@ package android.app { public class AppOpsManager { method public static java.lang.String[] getOpStrs(); method public java.util.List<android.app.AppOpsManager.PackageOps> getOpsForPackage(int, java.lang.String, int[]); + method public static java.lang.String opToPermission(java.lang.String); + method public void resetUidMode(java.lang.String, int, boolean); method public void setMode(java.lang.String, int, java.lang.String, int); method public void setUidMode(java.lang.String, int, int); field public static final java.lang.String OPSTR_ACCEPT_HANDOVER = "android:accept_handover"; @@ -802,6 +806,23 @@ package android.app.job { } +package android.app.role { + + public final class RoleManager { + method public void addRoleHolderAsUser(java.lang.String, java.lang.String, android.os.UserHandle, java.util.concurrent.Executor, android.app.role.RoleManagerCallback); + method public void clearRoleHoldersAsUser(java.lang.String, android.os.UserHandle, java.util.concurrent.Executor, android.app.role.RoleManagerCallback); + method public java.util.Set<java.lang.String> getRoleHoldersAsUser(java.lang.String, android.os.UserHandle); + method public void removeRoleHolderAsUser(java.lang.String, java.lang.String, android.os.UserHandle, java.util.concurrent.Executor, android.app.role.RoleManagerCallback); + field public static final java.lang.String EXTRA_REQUEST_ROLE_NAME = "android.app.role.extra.REQUEST_ROLE_NAME"; + } + + public abstract interface RoleManagerCallback { + method public abstract void onFailure(); + method public abstract void onSuccess(); + } + +} + package android.app.usage { public final class CacheQuotaHint implements android.os.Parcelable { @@ -850,9 +871,11 @@ package android.app.usage { method public int getAppStandbyBucket(java.lang.String); method public java.util.Map<java.lang.String, java.lang.Integer> getAppStandbyBuckets(); method public void registerAppUsageObserver(int, java.lang.String[], long, java.util.concurrent.TimeUnit, android.app.PendingIntent); + method public void registerUsageSessionObserver(int, java.lang.String[], long, java.util.concurrent.TimeUnit, long, java.util.concurrent.TimeUnit, android.app.PendingIntent, android.app.PendingIntent); method public void setAppStandbyBucket(java.lang.String, int); method public void setAppStandbyBuckets(java.util.Map<java.lang.String, java.lang.Integer>); method public void unregisterAppUsageObserver(int); + method public void unregisterUsageSessionObserver(int); method public void whitelistAppTemporarily(java.lang.String, long, android.os.UserHandle); field public static final java.lang.String EXTRA_OBSERVER_ID = "android.app.usage.extra.OBSERVER_ID"; field public static final java.lang.String EXTRA_TIME_LIMIT = "android.app.usage.extra.TIME_LIMIT"; @@ -987,6 +1010,7 @@ package android.content { field public static final java.lang.String ACTION_PRE_BOOT_COMPLETED = "android.intent.action.PRE_BOOT_COMPLETED"; field public static final java.lang.String ACTION_QUERY_PACKAGE_RESTART = "android.intent.action.QUERY_PACKAGE_RESTART"; field public static final java.lang.String ACTION_RESOLVE_INSTANT_APP_PACKAGE = "android.intent.action.RESOLVE_INSTANT_APP_PACKAGE"; + field public static final java.lang.String ACTION_REVIEW_APP_PERMISSION_USAGE = "android.intent.action.REVIEW_APP_PERMISSION_USAGE"; field public static final java.lang.String ACTION_REVIEW_PERMISSIONS = "android.intent.action.REVIEW_PERMISSIONS"; field public static final java.lang.String ACTION_REVIEW_PERMISSION_USAGE = "android.intent.action.REVIEW_PERMISSION_USAGE"; field public static final java.lang.String ACTION_SHOW_SUSPENDED_APP_DETAILS = "android.intent.action.SHOW_SUSPENDED_APP_DETAILS"; @@ -1148,6 +1172,7 @@ package android.content.pm { method public java.util.List<android.content.pm.ResolveInfo> queryBroadcastReceiversAsUser(android.content.Intent, int, android.os.UserHandle); method public abstract void registerDexModule(java.lang.String, android.content.pm.PackageManager.DexModuleRegisterCallback); method public abstract void removeOnPermissionsChangeListener(android.content.pm.PackageManager.OnPermissionsChangedListener); + method public void replacePreferredActivity(android.content.IntentFilter, int, java.util.List<android.content.ComponentName>, android.content.ComponentName); method public abstract void revokeRuntimePermission(java.lang.String, java.lang.String, android.os.UserHandle); method public abstract boolean setDefaultBrowserPackageNameAsUser(java.lang.String, int); method public void setHarmfulAppWarning(java.lang.String, java.lang.CharSequence); @@ -4439,6 +4464,10 @@ package android.provider { public final class Settings { field public static final java.lang.String ACTION_ENTERPRISE_PRIVACY_SETTINGS = "android.settings.ENTERPRISE_PRIVACY_SETTINGS"; field public static final java.lang.String ACTION_SHOW_ADMIN_SUPPORT_DETAILS = "android.settings.SHOW_ADMIN_SUPPORT_DETAILS"; + field public static final int USER_SETUP_PERSONALIZATION_COMPLETE = 10; // 0xa + field public static final int USER_SETUP_PERSONALIZATION_NOT_STARTED = 0; // 0x0 + field public static final int USER_SETUP_PERSONALIZATION_PAUSED = 2; // 0x2 + field public static final int USER_SETUP_PERSONALIZATION_STARTED = 1; // 0x1 } public static final class Settings.Global extends android.provider.Settings.NameValueTable { @@ -4449,10 +4478,13 @@ package android.provider { field public static final java.lang.String CARRIER_APP_NAMES = "carrier_app_names"; field public static final java.lang.String CARRIER_APP_WHITELIST = "carrier_app_whitelist"; field public static final java.lang.String DEFAULT_SM_DP_PLUS = "default_sm_dp_plus"; + field public static final java.lang.String DEVICE_DEMO_MODE = "device_demo_mode"; + field public static final java.lang.String DEVICE_PROVISIONING_MOBILE_DATA_ENABLED = "device_provisioning_mobile_data"; field public static final java.lang.String EUICC_PROVISIONED = "euicc_provisioned"; field public static final java.lang.String INSTALL_CARRIER_APP_NOTIFICATION_PERSISTENT = "install_carrier_app_notification_persistent"; field public static final java.lang.String INSTALL_CARRIER_APP_NOTIFICATION_SLEEP_MILLIS = "install_carrier_app_notification_sleep_millis"; field public static final java.lang.String OTA_DISABLE_AUTOMATIC_UPDATE = "ota_disable_automatic_update"; + field public static final java.lang.String REQUIRE_PASSWORD_TO_DECRYPT = "require_password_to_decrypt"; field public static final java.lang.String SMS_ACCESS_RESTRICTION_ENABLED = "sms_access_restriction_enabled"; field public static final java.lang.String THEATER_MODE_ON = "theater_mode_on"; field public static final java.lang.String WEBVIEW_MULTIPROCESS = "webview_multiprocess"; @@ -4463,15 +4495,23 @@ package android.provider { public static final class Settings.Secure extends android.provider.Settings.NameValueTable { method public static boolean putString(android.content.ContentResolver, java.lang.String, java.lang.String, java.lang.String, boolean); method public static void resetToDefaults(android.content.ContentResolver, java.lang.String); + field public static final java.lang.String ASSIST_GESTURE_SETUP_COMPLETE = "assist_gesture_setup_complete"; field public static final java.lang.String AUTOFILL_FEATURE_FIELD_CLASSIFICATION = "autofill_field_classification"; field public static final java.lang.String AUTOFILL_USER_DATA_MAX_CATEGORY_COUNT = "autofill_user_data_max_category_count"; field public static final java.lang.String AUTOFILL_USER_DATA_MAX_FIELD_CLASSIFICATION_IDS_SIZE = "autofill_user_data_max_field_classification_size"; field public static final java.lang.String AUTOFILL_USER_DATA_MAX_USER_DATA_SIZE = "autofill_user_data_max_user_data_size"; field public static final java.lang.String AUTOFILL_USER_DATA_MAX_VALUE_LENGTH = "autofill_user_data_max_value_length"; field public static final java.lang.String AUTOFILL_USER_DATA_MIN_VALUE_LENGTH = "autofill_user_data_min_value_length"; + field public static final java.lang.String COMPLETED_CATEGORY_PREFIX = "suggested.completed_category."; + field public static final java.lang.String DOZE_ALWAYS_ON = "doze_always_on"; field public static final java.lang.String HUSH_GESTURE_USED = "hush_gesture_used"; field public static final java.lang.String INSTANT_APPS_ENABLED = "instant_apps_enabled"; + field public static final java.lang.String LAST_SETUP_SHOWN = "last_setup_shown"; + field public static final java.lang.String LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS = "lock_screen_allow_private_notifications"; + field public static final java.lang.String LOCK_SCREEN_SHOW_NOTIFICATIONS = "lock_screen_show_notifications"; field public static final java.lang.String MANUAL_RINGER_TOGGLE_COUNT = "manual_ringer_toggle_count"; + field public static final java.lang.String USER_SETUP_COMPLETE = "user_setup_complete"; + field public static final java.lang.String USER_SETUP_PERSONALIZATION_STATE = "user_setup_personalization_state"; field public static final java.lang.String VOLUME_HUSH_GESTURE = "volume_hush_gesture"; } @@ -4510,6 +4550,19 @@ package android.provider { } +package android.rolecontrollerservice { + + public abstract class RoleControllerService extends android.app.Service { + ctor public RoleControllerService(); + method public abstract void onAddRoleHolder(java.lang.String, java.lang.String, android.app.role.RoleManagerCallback); + method public final android.os.IBinder onBind(android.content.Intent); + method public abstract void onClearRoleHolders(java.lang.String, android.app.role.RoleManagerCallback); + method public abstract void onRemoveRoleHolder(java.lang.String, java.lang.String, android.app.role.RoleManagerCallback); + field public static final java.lang.String SERVICE_INTERFACE = "android.rolecontrollerservice.RoleControllerService"; + } + +} + package android.security.keystore { public abstract class AttestationUtils { @@ -5511,6 +5564,8 @@ package android.telephony { method public boolean isVideoCallingEnabled(); method public deprecated boolean isVisualVoicemailEnabled(android.telecom.PhoneAccountHandle); method public boolean needsOtaServiceProvisioning(); + method public boolean rebootRadio(); + method public boolean resetRadioConfig(); method public int setAllowedCarriers(int, java.util.List<android.service.carrier.CarrierIdentifier>); method public void setCarrierDataEnabled(boolean); method public void setDataActivationState(int); @@ -6795,6 +6850,14 @@ package android.view { } +package android.view.accessibility { + + public final class AccessibilityManager { + method public void performAccessibilityShortcut(); + } + +} + package android.webkit { public abstract class CookieManager { diff --git a/api/test-current.txt b/api/test-current.txt index 8f08c7108721..d453395fe9cd 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -960,6 +960,7 @@ package android.provider { public static final class Settings.Secure extends android.provider.Settings.NameValueTable { field public static final java.lang.String ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED = "accessibility_display_magnification_enabled"; + field public static final java.lang.String ACCESSIBILITY_SHORTCUT_TARGET_SERVICE = "accessibility_shortcut_target_service"; field public static final java.lang.String AUTOFILL_FEATURE_FIELD_CLASSIFICATION = "autofill_field_classification"; field public static final java.lang.String AUTOFILL_SERVICE = "autofill_service"; field public static final java.lang.String AUTOFILL_USER_DATA_MAX_CATEGORY_COUNT = "autofill_user_data_max_category_count"; @@ -1611,6 +1612,17 @@ package android.view { package android.view.accessibility { + public final class AccessibilityManager { + method public void addAccessibilityServicesStateChangeListener(android.view.accessibility.AccessibilityManager.AccessibilityServicesStateChangeListener, android.os.Handler); + method public java.lang.String getAccessibilityShortcutService(); + method public void performAccessibilityShortcut(); + method public void removeAccessibilityServicesStateChangeListener(android.view.accessibility.AccessibilityManager.AccessibilityServicesStateChangeListener); + } + + public static abstract interface AccessibilityManager.AccessibilityServicesStateChangeListener { + method public abstract void onAccessibilityServicesStateChanged(android.view.accessibility.AccessibilityManager); + } + public class AccessibilityNodeInfo implements android.os.Parcelable { method public static void setNumInstancesInUseCounter(java.util.concurrent.atomic.AtomicInteger); method public void writeToParcelNoRecycle(android.os.Parcel, int); diff --git a/cmds/idmap2/.clang-format b/cmds/idmap2/.clang-format new file mode 100644 index 000000000000..c91502a257f3 --- /dev/null +++ b/cmds/idmap2/.clang-format @@ -0,0 +1,7 @@ +BasedOnStyle: Google +ColumnLimit: 100 +AllowShortBlocksOnASingleLine: false +AllowShortFunctionsOnASingleLine: false +CommentPragmas: NOLINT:.* +DerivePointerAlignment: false +PointerAlignment: Left diff --git a/cmds/idmap2/Android.bp b/cmds/idmap2/Android.bp new file mode 100644 index 000000000000..5a6c813fd202 --- /dev/null +++ b/cmds/idmap2/Android.bp @@ -0,0 +1,191 @@ +// Copyright (C) 2018 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +cc_library { + name: "libidmap2", + host_supported: true, + tidy: true, + tidy_flags: [ + "-system-headers", + "-warnings-as-errors=*", + ], + srcs: [ + "libidmap2/BinaryStreamVisitor.cpp", + "libidmap2/CommandLineOptions.cpp", + "libidmap2/FileUtils.cpp", + "libidmap2/Idmap.cpp", + "libidmap2/PrettyPrintVisitor.cpp", + "libidmap2/RawPrintVisitor.cpp", + "libidmap2/ResourceUtils.cpp", + "libidmap2/Xml.cpp", + "libidmap2/ZipFile.cpp", + ], + export_include_dirs: ["include"], + target: { + android: { + static: { + enabled: false, + }, + shared_libs: [ + "libandroidfw", + "libbase", + "libutils", + "libziparchive", + ], + }, + host: { + shared: { + enabled: false, + }, + static_libs: [ + "libandroidfw", + "libbase", + "libutils", + "libziparchive", + ], + }, + }, +} + +cc_test { + name: "idmap2_tests", + host_supported: true, + tidy: true, + tidy_flags: [ + "-system-headers", + "-warnings-as-errors=*", + ], + srcs: [ + "tests/BinaryStreamVisitorTests.cpp", + "tests/CommandLineOptionsTests.cpp", + "tests/FileUtilsTests.cpp", + "tests/Idmap2BinaryTests.cpp", + "tests/IdmapTests.cpp", + "tests/Main.cpp", + "tests/PrettyPrintVisitorTests.cpp", + "tests/RawPrintVisitorTests.cpp", + "tests/ResourceUtilsTests.cpp", + "tests/XmlTests.cpp", + "tests/ZipFileTests.cpp", + ], + required: [ + "idmap2", + ], + static_libs: ["libgmock"], + target: { + android: { + shared_libs: [ + "libandroidfw", + "libbase", + "libidmap2", + "liblog", + "libutils", + "libz", + "libziparchive", + ], + }, + host: { + static_libs: [ + "libandroidfw", + "libbase", + "libidmap2", + "liblog", + "libutils", + "libziparchive", + ], + shared_libs: [ + "libz", + ], + }, + }, + data: ["tests/data/**/*.apk"], +} + +cc_binary { + name: "idmap2", + host_supported: true, + tidy: true, + tidy_flags: [ + "-system-headers", + "-warnings-as-errors=*", + ], + srcs: [ + "idmap2/Create.cpp", + "idmap2/Dump.cpp", + "idmap2/Lookup.cpp", + "idmap2/Main.cpp", + "idmap2/Scan.cpp", + "idmap2/Verify.cpp", + ], + target: { + android: { + shared_libs: [ + "libandroidfw", + "libbase", + "libidmap2", + "libutils", + "libziparchive", + ], + }, + host: { + static_libs: [ + "libandroidfw", + "libbase", + "libidmap2", + "liblog", + "libutils", + "libziparchive", + ], + shared_libs: [ + "libz", + ], + }, + }, +} + +cc_binary { + name: "idmap2d", + host_supported: false, + tidy: true, + tidy_checks: [ + // remove google-default-arguments or clang-tidy will complain about + // the auto-generated file IIdmap2.cpp + "-google-default-arguments", + ], + tidy_flags: [ + "-system-headers", + "-warnings-as-errors=*", + ], + srcs: [ + ":idmap2_aidl", + "idmap2d/Idmap2Service.cpp", + "idmap2d/Main.cpp", + ], + shared_libs: [ + "libandroidfw", + "libbase", + "libbinder", + "libcutils", + "libidmap2", + "libutils", + "libziparchive", + ], +} + +filegroup { + name: "idmap2_aidl", + srcs: [ + "idmap2d/aidl/android/os/IIdmap2.aidl", + ], +} diff --git a/cmds/idmap2/AndroidTest.xml b/cmds/idmap2/AndroidTest.xml new file mode 100644 index 000000000000..5147f4e6cb4c --- /dev/null +++ b/cmds/idmap2/AndroidTest.xml @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2018 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<configuration description="Config for idmap2_tests"> + <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer"> + <option name="cleanup" value="true" /> + <option name="push" value="idmap2_tests->/data/local/tmp/idmap2_tests" /> + </target_preparer> + <option name="test-suite-tag" value="idmap2_tests" /> + <test class="com.android.tradefed.testtype.GTest" > + <option name="native-test-device-path" value="/data/local/tmp" /> + <option name="module-name" value="idmap2_tests" /> + </test> +</configuration> diff --git a/cmds/statsd/tools/Android.mk b/cmds/idmap2/CPPLINT.cfg index 7253c9637026..9dc6b4a77380 100644 --- a/cmds/statsd/tools/Android.mk +++ b/cmds/idmap2/CPPLINT.cfg @@ -1,4 +1,4 @@ -# Copyright (C) 2017 The Android Open Source Project +# Copyright (C) 2018 The Android Open Source Project # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -11,10 +11,8 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -# -# -LOCAL_PATH := $(call my-dir) -include $(CLEAR_VARS) -#Include the sub-makefiles -include $(call all-makefiles-under,$(LOCAL_PATH))
\ No newline at end of file +set noparent +linelength=100 +root=.. +filter=+build/include_alpha diff --git a/cmds/idmap2/OWNERS b/cmds/idmap2/OWNERS new file mode 100644 index 000000000000..23ec5ab0d1f3 --- /dev/null +++ b/cmds/idmap2/OWNERS @@ -0,0 +1,2 @@ +set noparent +toddke@google.com diff --git a/cmds/idmap2/TEST_MAPPING b/cmds/idmap2/TEST_MAPPING new file mode 100644 index 000000000000..26ccf038cba2 --- /dev/null +++ b/cmds/idmap2/TEST_MAPPING @@ -0,0 +1,7 @@ +{ + "presubmit" : [ + { + "name" : "idmap2_tests" + } + ] +} diff --git a/cmds/idmap2/idmap2/Commands.h b/cmds/idmap2/idmap2/Commands.h new file mode 100644 index 000000000000..dcc69b30743d --- /dev/null +++ b/cmds/idmap2/idmap2/Commands.h @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef IDMAP2_IDMAP2_COMMANDS_H_ +#define IDMAP2_IDMAP2_COMMANDS_H_ + +#include <string> +#include <vector> + +bool Create(const std::vector<std::string>& args, std::ostream& out_error); +bool Dump(const std::vector<std::string>& args, std::ostream& out_error); +bool Lookup(const std::vector<std::string>& args, std::ostream& out_error); +bool Scan(const std::vector<std::string>& args, std::ostream& out_error); +bool Verify(const std::vector<std::string>& args, std::ostream& out_error); + +#endif // IDMAP2_IDMAP2_COMMANDS_H_ diff --git a/cmds/idmap2/idmap2/Create.cpp b/cmds/idmap2/idmap2/Create.cpp new file mode 100644 index 000000000000..291eaeb9c211 --- /dev/null +++ b/cmds/idmap2/idmap2/Create.cpp @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <sys/stat.h> // umask +#include <sys/types.h> // umask +#include <fstream> +#include <memory> +#include <ostream> +#include <sstream> +#include <string> +#include <vector> + +#include "idmap2/BinaryStreamVisitor.h" +#include "idmap2/CommandLineOptions.h" +#include "idmap2/FileUtils.h" +#include "idmap2/Idmap.h" + +using android::ApkAssets; +using android::idmap2::BinaryStreamVisitor; +using android::idmap2::CommandLineOptions; +using android::idmap2::Idmap; + +bool Create(const std::vector<std::string>& args, std::ostream& out_error) { + std::string target_apk_path, overlay_apk_path, idmap_path; + + const CommandLineOptions opts = + CommandLineOptions("idmap2 create") + .MandatoryOption("--target-apk-path", + "input: path to apk which will have its resources overlaid", + &target_apk_path) + .MandatoryOption("--overlay-apk-path", + "input: path to apk which contains the new resource values", + &overlay_apk_path) + .MandatoryOption("--idmap-path", "output: path to where to write idmap file", + &idmap_path); + if (!opts.Parse(args, out_error)) { + return false; + } + + const std::unique_ptr<const ApkAssets> target_apk = ApkAssets::Load(target_apk_path); + if (!target_apk) { + out_error << "error: failed to load apk " << target_apk_path << std::endl; + return false; + } + + const std::unique_ptr<const ApkAssets> overlay_apk = ApkAssets::Load(overlay_apk_path); + if (!overlay_apk) { + out_error << "error: failed to load apk " << overlay_apk_path << std::endl; + return false; + } + + const std::unique_ptr<const Idmap> idmap = + Idmap::FromApkAssets(target_apk_path, *target_apk, overlay_apk_path, *overlay_apk, out_error); + if (!idmap) { + return false; + } + + umask(0133); // u=rw,g=r,o=r + std::ofstream fout(idmap_path); + if (fout.fail()) { + out_error << "failed to open idmap path " << idmap_path << std::endl; + return false; + } + BinaryStreamVisitor visitor(fout); + idmap->accept(&visitor); + fout.close(); + if (fout.fail()) { + out_error << "failed to write to idmap path " << idmap_path << std::endl; + return false; + } + + return true; +} diff --git a/cmds/idmap2/idmap2/Dump.cpp b/cmds/idmap2/idmap2/Dump.cpp new file mode 100644 index 000000000000..c8cdcfa6d3dc --- /dev/null +++ b/cmds/idmap2/idmap2/Dump.cpp @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <fstream> +#include <iostream> +#include <memory> +#include <string> +#include <vector> + +#include "idmap2/CommandLineOptions.h" +#include "idmap2/Idmap.h" +#include "idmap2/PrettyPrintVisitor.h" +#include "idmap2/RawPrintVisitor.h" + +using android::idmap2::CommandLineOptions; +using android::idmap2::Idmap; +using android::idmap2::PrettyPrintVisitor; +using android::idmap2::RawPrintVisitor; + +bool Dump(const std::vector<std::string>& args, std::ostream& out_error) { + std::string idmap_path; + bool verbose; + + const CommandLineOptions opts = + CommandLineOptions("idmap2 dump") + .MandatoryOption("--idmap-path", "input: path to idmap file to pretty-print", &idmap_path) + .OptionalFlag("--verbose", "annotate every byte of the idmap", &verbose); + if (!opts.Parse(args, out_error)) { + return false; + } + std::ifstream fin(idmap_path); + const std::unique_ptr<const Idmap> idmap = Idmap::FromBinaryStream(fin, out_error); + fin.close(); + if (!idmap) { + return false; + } + + if (verbose) { + RawPrintVisitor visitor(std::cout); + idmap->accept(&visitor); + } else { + PrettyPrintVisitor visitor(std::cout); + idmap->accept(&visitor); + } + + return true; +} diff --git a/cmds/idmap2/idmap2/Lookup.cpp b/cmds/idmap2/idmap2/Lookup.cpp new file mode 100644 index 000000000000..1191e6a27b07 --- /dev/null +++ b/cmds/idmap2/idmap2/Lookup.cpp @@ -0,0 +1,249 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <algorithm> +#include <fstream> +#include <iterator> +#include <memory> +#include <ostream> +#include <string> +#include <utility> +#include <vector> + +#include "android-base/macros.h" +#include "android-base/stringprintf.h" +#include "androidfw/ApkAssets.h" +#include "androidfw/AssetManager2.h" +#include "androidfw/ConfigDescription.h" +#include "androidfw/ResourceUtils.h" +#include "androidfw/StringPiece.h" +#include "androidfw/Util.h" +#include "utils/String16.h" +#include "utils/String8.h" + +#include "idmap2/CommandLineOptions.h" +#include "idmap2/Idmap.h" +#include "idmap2/Xml.h" +#include "idmap2/ZipFile.h" + +using android::ApkAssets; +using android::ApkAssetsCookie; +using android::AssetManager2; +using android::ConfigDescription; +using android::is_valid_resid; +using android::kInvalidCookie; +using android::Res_value; +using android::ResStringPool; +using android::ResTable_config; +using android::String16; +using android::String8; +using android::StringPiece16; +using android::base::StringPrintf; +using android::idmap2::CommandLineOptions; +using android::idmap2::IdmapHeader; +using android::idmap2::ResourceId; +using android::idmap2::Xml; +using android::idmap2::ZipFile; +using android::util::Utf16ToUtf8; + +namespace { +std::pair<bool, ResourceId> WARN_UNUSED ParseResReference(const AssetManager2& am, + const std::string& res, + const std::string& fallback_package) { + // first, try to parse as a hex number + char* endptr = nullptr; + ResourceId resid; + resid = strtol(res.c_str(), &endptr, 16); + if (*endptr == '\0') { + return std::make_pair(true, resid); + } + + // next, try to parse as a package:type/name string + resid = am.GetResourceId(res, "", fallback_package); + if (is_valid_resid(resid)) { + return std::make_pair(true, resid); + } + + // end of the road: res could not be parsed + return std::make_pair(false, 0); +} + +std::pair<bool, std::string> WARN_UNUSED GetValue(const AssetManager2& am, ResourceId resid) { + Res_value value; + ResTable_config config; + uint32_t flags; + ApkAssetsCookie cookie = am.GetResource(resid, false, 0, &value, &config, &flags); + if (cookie == kInvalidCookie) { + return std::make_pair(false, ""); + } + + std::string out; + + // TODO(martenkongstad): use optional parameter GetResource(..., std::string* + // stacktrace = NULL) instead + out.append(StringPrintf("cookie=%d ", cookie)); + + out.append("config='"); + out.append(config.toString().c_str()); + out.append("' value="); + + switch (value.dataType) { + case Res_value::TYPE_INT_DEC: + out.append(StringPrintf("%d", value.data)); + break; + case Res_value::TYPE_INT_HEX: + out.append(StringPrintf("0x%08x", value.data)); + break; + case Res_value::TYPE_INT_BOOLEAN: + out.append(value.data != 0 ? "true" : "false"); + break; + case Res_value::TYPE_STRING: { + const ResStringPool* pool = am.GetStringPoolForCookie(cookie); + size_t len; + if (pool->isUTF8()) { + const char* str = pool->string8At(value.data, &len); + out.append(str, len); + } else { + const char16_t* str16 = pool->stringAt(value.data, &len); + out += Utf16ToUtf8(StringPiece16(str16, len)); + } + } break; + default: + out.append(StringPrintf("dataType=0x%02x data=0x%08x", value.dataType, value.data)); + break; + } + return std::make_pair(true, out); +} + +std::pair<bool, std::string> GetTargetPackageNameFromManifest(const std::string& apk_path) { + const auto zip = ZipFile::Open(apk_path); + if (!zip) { + return std::make_pair(false, ""); + } + const auto entry = zip->Uncompress("AndroidManifest.xml"); + if (!entry) { + return std::make_pair(false, ""); + } + const auto xml = Xml::Create(entry->buf, entry->size); + if (!xml) { + return std::make_pair(false, ""); + } + const auto tag = xml->FindTag("overlay"); + if (!tag) { + return std::make_pair(false, ""); + } + const auto iter = tag->find("targetPackage"); + if (iter == tag->end()) { + return std::make_pair(false, ""); + } + return std::make_pair(true, iter->second); +} +} // namespace + +bool Lookup(const std::vector<std::string>& args, std::ostream& out_error) { + std::vector<std::string> idmap_paths; + std::string config_str, resid_str; + const CommandLineOptions opts = + CommandLineOptions("idmap2 lookup") + .MandatoryOption("--idmap-path", "input: path to idmap file to load", &idmap_paths) + .MandatoryOption("--config", "configuration to use", &config_str) + .MandatoryOption("--resid", + "Resource ID (in the target package; '0xpptteeee' or " + "'[package:]type/name') to look up", + &resid_str); + + if (!opts.Parse(args, out_error)) { + return false; + } + + ConfigDescription config; + if (!ConfigDescription::Parse(config_str, &config)) { + out_error << "error: failed to parse config" << std::endl; + return false; + } + + std::vector<std::unique_ptr<const ApkAssets>> apk_assets; + std::string target_path; + std::string target_package_name; + for (size_t i = 0; i < idmap_paths.size(); i++) { + const auto& idmap_path = idmap_paths[i]; + std::fstream fin(idmap_path); + auto idmap_header = IdmapHeader::FromBinaryStream(fin); + fin.close(); + if (!idmap_header) { + out_error << "error: failed to read idmap from " << idmap_path << std::endl; + return false; + } + + if (i == 0) { + target_path = idmap_header->GetTargetPath().to_string(); + auto target_apk = ApkAssets::Load(target_path); + if (!target_apk) { + out_error << "error: failed to read target apk from " << target_path << std::endl; + return false; + } + apk_assets.push_back(std::move(target_apk)); + + bool lookup_ok; + std::tie(lookup_ok, target_package_name) = + GetTargetPackageNameFromManifest(idmap_header->GetOverlayPath().to_string()); + if (!lookup_ok) { + out_error << "error: failed to parse android:targetPackage from overlay manifest" + << std::endl; + return false; + } + } else if (target_path != idmap_header->GetTargetPath()) { + out_error << "error: different target APKs (expected target APK " << target_path << " but " + << idmap_path << " has target APK " << idmap_header->GetTargetPath() << ")" + << std::endl; + return false; + } + + auto overlay_apk = ApkAssets::LoadOverlay(idmap_path); + if (!overlay_apk) { + out_error << "error: failed to read overlay apk from " << idmap_header->GetOverlayPath() + << std::endl; + return false; + } + apk_assets.push_back(std::move(overlay_apk)); + } + + // AssetManager2::SetApkAssets requires raw ApkAssets pointers, not unique_ptrs + std::vector<const ApkAssets*> raw_pointer_apk_assets; + std::transform(apk_assets.cbegin(), apk_assets.cend(), std::back_inserter(raw_pointer_apk_assets), + [](const auto& p) -> const ApkAssets* { return p.get(); }); + AssetManager2 am; + am.SetApkAssets(raw_pointer_apk_assets); + am.SetConfiguration(config); + + ResourceId resid; + bool lookup_ok; + std::tie(lookup_ok, resid) = ParseResReference(am, resid_str, target_package_name); + if (!lookup_ok) { + out_error << "error: failed to parse resource ID" << std::endl; + return false; + } + + std::string value; + std::tie(lookup_ok, value) = GetValue(am, resid); + if (!lookup_ok) { + out_error << StringPrintf("error: resource 0x%08x not found", resid) << std::endl; + return false; + } + std::cout << value << std::endl; + + return true; +} diff --git a/cmds/idmap2/idmap2/Main.cpp b/cmds/idmap2/idmap2/Main.cpp new file mode 100644 index 000000000000..5d9ea778915a --- /dev/null +++ b/cmds/idmap2/idmap2/Main.cpp @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <cstdlib> // EXIT_{FAILURE,SUCCESS} +#include <functional> +#include <iostream> +#include <map> +#include <memory> +#include <ostream> +#include <string> +#include <vector> + +#include "idmap2/CommandLineOptions.h" + +#include "Commands.h" + +using android::idmap2::CommandLineOptions; + +typedef std::map<std::string, std::function<int(const std::vector<std::string>&, std::ostream&)>> + NameToFunctionMap; + +static void PrintUsage(const NameToFunctionMap& commands, std::ostream& out) { + out << "usage: idmap2 ["; + for (auto iter = commands.cbegin(); iter != commands.cend(); iter++) { + if (iter != commands.cbegin()) { + out << "|"; + } + out << iter->first; + } + out << "]" << std::endl; +} + +int main(int argc, char** argv) { + const NameToFunctionMap commands = { + {"create", Create}, {"dump", Dump}, {"lookup", Lookup}, {"scan", Scan}, {"verify", Verify}, + }; + if (argc <= 1) { + PrintUsage(commands, std::cerr); + return EXIT_FAILURE; + } + const std::unique_ptr<std::vector<std::string>> args = + CommandLineOptions::ConvertArgvToVector(argc - 1, const_cast<const char**>(argv + 1)); + if (!args) { + std::cerr << "error: failed to parse command line options" << std::endl; + return EXIT_FAILURE; + } + const auto iter = commands.find(argv[1]); + if (iter == commands.end()) { + std::cerr << argv[1] << ": command not found" << std::endl; + PrintUsage(commands, std::cerr); + return EXIT_FAILURE; + } + return iter->second(*args, std::cerr) ? EXIT_SUCCESS : EXIT_FAILURE; +} diff --git a/cmds/idmap2/idmap2/Scan.cpp b/cmds/idmap2/idmap2/Scan.cpp new file mode 100644 index 000000000000..33c274e7fd35 --- /dev/null +++ b/cmds/idmap2/idmap2/Scan.cpp @@ -0,0 +1,159 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <dirent.h> +#include <fstream> +#include <memory> +#include <ostream> +#include <set> +#include <sstream> +#include <string> +#include <vector> + +#include "idmap2/CommandLineOptions.h" +#include "idmap2/FileUtils.h" +#include "idmap2/Idmap.h" +#include "idmap2/Xml.h" +#include "idmap2/ZipFile.h" + +#include "Commands.h" + +using android::idmap2::CommandLineOptions; +using android::idmap2::Idmap; +using android::idmap2::MemoryChunk; +using android::idmap2::Xml; +using android::idmap2::ZipFile; +using android::idmap2::utils::FindFiles; + +namespace { +std::unique_ptr<std::vector<std::string>> FindApkFiles(const std::vector<std::string>& dirs, + bool recursive, std::ostream& out_error) { + const auto predicate = [](unsigned char type, const std::string& path) -> bool { + static constexpr size_t kExtLen = 4; // strlen(".apk") + return type == DT_REG && path.size() > kExtLen && + !path.compare(path.size() - kExtLen, kExtLen, ".apk"); + }; + // pass apk paths through a set to filter out duplicates + std::set<std::string> paths; + for (const auto& dir : dirs) { + const auto apk_paths = FindFiles(dir, recursive, predicate); + if (!apk_paths) { + out_error << "error: failed to open directory " << dir << std::endl; + return nullptr; + } + paths.insert(apk_paths->cbegin(), apk_paths->cend()); + } + return std::unique_ptr<std::vector<std::string>>( + new std::vector<std::string>(paths.cbegin(), paths.cend())); +} +} // namespace + +bool Scan(const std::vector<std::string>& args, std::ostream& out_error) { + std::vector<std::string> input_directories; + std::string target_package_name, target_apk_path, output_directory; + bool recursive = false; + + const CommandLineOptions opts = + CommandLineOptions("idmap2 scan") + .MandatoryOption("--input-directory", "directory containing overlay apks to scan", + &input_directories) + .OptionalFlag("--recursive", "also scan subfolders of overlay-directory", &recursive) + .MandatoryOption("--target-package-name", "package name of target package", + &target_package_name) + .MandatoryOption("--target-apk-path", "path to target apk", &target_apk_path) + .MandatoryOption("--output-directory", + "directory in which to write artifacts (idmap files and overlays.list)", + &output_directory); + if (!opts.Parse(args, out_error)) { + return false; + } + + const auto apk_paths = FindApkFiles(input_directories, recursive, out_error); + if (!apk_paths) { + return false; + } + + std::vector<std::string> interesting_apks; + for (const std::string& path : *apk_paths) { + std::unique_ptr<const ZipFile> zip = ZipFile::Open(path); + if (!zip) { + out_error << "error: failed to open " << path << " as a zip file" << std::endl; + return false; + } + + std::unique_ptr<const MemoryChunk> entry = zip->Uncompress("AndroidManifest.xml"); + if (!entry) { + out_error << "error: failed to uncompress AndroidManifest.xml from " << path << std::endl; + return false; + } + + std::unique_ptr<const Xml> xml = Xml::Create(entry->buf, entry->size); + if (!xml) { + out_error << "error: failed to parse AndroidManifest.xml from " << path << std::endl; + continue; + } + + const auto tag = xml->FindTag("overlay"); + if (!tag) { + continue; + } + + auto iter = tag->find("isStatic"); + if (iter == tag->end() || std::stoul(iter->second) == 0u) { + continue; + } + + iter = tag->find("targetPackage"); + if (iter == tag->end() || iter->second != target_package_name) { + continue; + } + + iter = tag->find("priority"); + if (iter == tag->end()) { + continue; + } + + const int priority = std::stoi(iter->second); + if (priority < 0) { + continue; + } + + interesting_apks.insert( + std::lower_bound(interesting_apks.begin(), interesting_apks.end(), path), path); + } + + std::stringstream stream; + for (auto iter = interesting_apks.cbegin(); iter != interesting_apks.cend(); ++iter) { + const std::string idmap_path = Idmap::CanonicalIdmapPathFor(output_directory, *iter); + if (!Verify(std::vector<std::string>({"--idmap-path", idmap_path}), out_error) && + !Create(std::vector<std::string>({ + "--target-apk-path", + target_apk_path, + "--overlay-apk-path", + *iter, + "--idmap-path", + idmap_path, + }), + out_error)) { + return false; + } + stream << idmap_path << std::endl; + } + + std::cout << stream.str(); + + return true; +} diff --git a/cmds/idmap2/idmap2/Verify.cpp b/cmds/idmap2/idmap2/Verify.cpp new file mode 100644 index 000000000000..b5fa438b5b9f --- /dev/null +++ b/cmds/idmap2/idmap2/Verify.cpp @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <fstream> +#include <memory> +#include <string> +#include <vector> + +#include "idmap2/CommandLineOptions.h" +#include "idmap2/Idmap.h" + +using android::idmap2::CommandLineOptions; +using android::idmap2::IdmapHeader; + +bool Verify(const std::vector<std::string>& args, std::ostream& out_error) { + std::string idmap_path; + const CommandLineOptions opts = + CommandLineOptions("idmap2 verify") + .MandatoryOption("--idmap-path", "input: path to idmap file to verify", &idmap_path); + if (!opts.Parse(args, out_error)) { + return false; + } + + std::ifstream fin(idmap_path); + const std::unique_ptr<const IdmapHeader> header = IdmapHeader::FromBinaryStream(fin); + fin.close(); + if (!header) { + out_error << "error: failed to parse idmap header" << std::endl; + return false; + } + + return header->IsUpToDate(out_error); +} diff --git a/cmds/idmap2/idmap2d/Idmap2Service.cpp b/cmds/idmap2/idmap2d/Idmap2Service.cpp new file mode 100644 index 000000000000..cf72cb94da2c --- /dev/null +++ b/cmds/idmap2/idmap2d/Idmap2Service.cpp @@ -0,0 +1,138 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <sys/stat.h> // umask +#include <sys/types.h> // umask +#include <unistd.h> + +#include <cerrno> +#include <cstring> +#include <fstream> +#include <memory> +#include <ostream> +#include <string> + +#include "android-base/macros.h" +#include "utils/String8.h" +#include "utils/Trace.h" + +#include "idmap2/BinaryStreamVisitor.h" +#include "idmap2/FileUtils.h" +#include "idmap2/Idmap.h" + +#include "idmap2d/Idmap2Service.h" + +using android::binder::Status; +using android::idmap2::BinaryStreamVisitor; +using android::idmap2::Idmap; +using android::idmap2::IdmapHeader; + +namespace { + +static constexpr const char* kIdmapCacheDir = "/data/resource-cache"; + +Status ok() { + return Status::ok(); +} + +Status error(const std::string& msg) { + LOG(ERROR) << msg; + return Status::fromExceptionCode(Status::EX_NONE, msg.c_str()); +} + +} // namespace + +namespace android { +namespace os { + +Status Idmap2Service::getIdmapPath(const std::string& overlay_apk_path, + int32_t user_id ATTRIBUTE_UNUSED, std::string* _aidl_return) { + assert(_aidl_return); + *_aidl_return = Idmap::CanonicalIdmapPathFor(kIdmapCacheDir, overlay_apk_path); + return ok(); +} + +Status Idmap2Service::removeIdmap(const std::string& overlay_apk_path, + int32_t user_id ATTRIBUTE_UNUSED, bool* _aidl_return) { + assert(_aidl_return); + const std::string idmap_path = Idmap::CanonicalIdmapPathFor(kIdmapCacheDir, overlay_apk_path); + if (unlink(idmap_path.c_str()) == 0) { + *_aidl_return = true; + return ok(); + } else { + *_aidl_return = false; + return error("failed to unlink " + idmap_path + ": " + strerror(errno)); + } +} + +Status Idmap2Service::createIdmap(const std::string& target_apk_path, + const std::string& overlay_apk_path, int32_t user_id, + std::unique_ptr<std::string>* _aidl_return) { + assert(_aidl_return); + std::stringstream trace; + trace << __FUNCTION__ << " " << target_apk_path << " " << overlay_apk_path << " " + << std::to_string(user_id); + ATRACE_NAME(trace.str().c_str()); + std::cout << trace.str() << std::endl; + + _aidl_return->reset(nullptr); + + const std::string idmap_path = Idmap::CanonicalIdmapPathFor(kIdmapCacheDir, overlay_apk_path); + std::ifstream fin(idmap_path); + const std::unique_ptr<const IdmapHeader> header = IdmapHeader::FromBinaryStream(fin); + fin.close(); + // do not reuse error stream from IsUpToDate below, or error messages will be + // polluted with irrelevant data + std::stringstream dev_null; + if (header && header->IsUpToDate(dev_null)) { + return ok(); + } + + const std::unique_ptr<const ApkAssets> target_apk = ApkAssets::Load(target_apk_path); + if (!target_apk) { + return error("failed to load apk " + target_apk_path); + } + + const std::unique_ptr<const ApkAssets> overlay_apk = ApkAssets::Load(overlay_apk_path); + if (!overlay_apk) { + return error("failed to load apk " + overlay_apk_path); + } + + std::stringstream err; + const std::unique_ptr<const Idmap> idmap = + Idmap::FromApkAssets(target_apk_path, *target_apk, overlay_apk_path, *overlay_apk, err); + if (!idmap) { + return error(err.str()); + } + + umask(0133); // u=rw,g=r,o=r + std::ofstream fout(idmap_path); + if (fout.fail()) { + return error("failed to open idmap path " + idmap_path); + } + BinaryStreamVisitor visitor(fout); + idmap->accept(&visitor); + fout.close(); + if (fout.fail()) { + return error("failed to write to idmap path " + idmap_path); + } + + _aidl_return->reset(new std::string(idmap_path)); + return ok(); +} + +} // namespace os +} // namespace android diff --git a/cmds/idmap2/idmap2d/Idmap2Service.h b/cmds/idmap2/idmap2d/Idmap2Service.h new file mode 100644 index 000000000000..2b32042d6aa3 --- /dev/null +++ b/cmds/idmap2/idmap2d/Idmap2Service.h @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef IDMAP2_IDMAP2D_IDMAP2SERVICE_H_ +#define IDMAP2_IDMAP2D_IDMAP2SERVICE_H_ + +#include <android-base/unique_fd.h> +#include <binder/BinderService.h> + +#include <memory> +#include <string> + +#include "android/os/BnIdmap2.h" + +namespace android { +namespace os { +class Idmap2Service : public BinderService<Idmap2Service>, public BnIdmap2 { + public: + static char const* getServiceName() { + return "idmap"; + } + + binder::Status getIdmapPath(const std::string& overlay_apk_path, int32_t user_id, + std::string* _aidl_return); + + binder::Status removeIdmap(const std::string& overlay_apk_path, int32_t user_id, + bool* _aidl_return); + + binder::Status createIdmap(const std::string& target_apk_path, + const std::string& overlay_apk_path, int32_t user_id, + std::unique_ptr<std::string>* _aidl_return); +}; +} // namespace os +} // namespace android + +#endif // IDMAP2_IDMAP2D_IDMAP2SERVICE_H_ diff --git a/cmds/idmap2/idmap2d/Main.cpp b/cmds/idmap2/idmap2d/Main.cpp new file mode 100644 index 000000000000..d64a87b8ee15 --- /dev/null +++ b/cmds/idmap2/idmap2d/Main.cpp @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define ATRACE_TAG ATRACE_TAG_RESOURCES + +#include <binder/BinderService.h> +#include <binder/IPCThreadState.h> +#include <binder/ProcessState.h> + +#include <cstdlib> // EXIT_{FAILURE,SUCCESS} + +#include <iostream> +#include <sstream> + +#include "android-base/macros.h" + +#include "Idmap2Service.h" + +using android::BinderService; +using android::IPCThreadState; +using android::ProcessState; +using android::sp; +using android::status_t; +using android::os::Idmap2Service; + +int main(int argc ATTRIBUTE_UNUSED, char** argv ATTRIBUTE_UNUSED) { + IPCThreadState::self()->disableBackgroundScheduling(true); + status_t ret = BinderService<Idmap2Service>::publish(); + if (ret != android::OK) { + return EXIT_FAILURE; + } + sp<ProcessState> ps(ProcessState::self()); + ps->startThreadPool(); + ps->giveThreadPoolName(); + IPCThreadState::self()->joinThreadPool(); + return EXIT_SUCCESS; +} diff --git a/cmds/idmap2/idmap2d/aidl/android/os/IIdmap2.aidl b/cmds/idmap2/idmap2d/aidl/android/os/IIdmap2.aidl new file mode 100644 index 000000000000..5d196101a7a6 --- /dev/null +++ b/cmds/idmap2/idmap2d/aidl/android/os/IIdmap2.aidl @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.os; + +/** + * @hide + */ +interface IIdmap2 { + @utf8InCpp String getIdmapPath(@utf8InCpp String overlayApkPath, int userId); + boolean removeIdmap(@utf8InCpp String overlayApkPath, int userId); + @nullable @utf8InCpp String createIdmap(@utf8InCpp String targetApkPath, + @utf8InCpp String overlayApkPath, int userId); +} diff --git a/cmds/idmap2/include/idmap2/BinaryStreamVisitor.h b/cmds/idmap2/include/idmap2/BinaryStreamVisitor.h new file mode 100644 index 000000000000..2368aeadbc9f --- /dev/null +++ b/cmds/idmap2/include/idmap2/BinaryStreamVisitor.h @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef IDMAP2_INCLUDE_IDMAP2_BINARYSTREAMVISITOR_H_ +#define IDMAP2_INCLUDE_IDMAP2_BINARYSTREAMVISITOR_H_ + +#include <cstdint> +#include <iostream> +#include <string> + +#include "idmap2/Idmap.h" + +namespace android { +namespace idmap2 { + +class BinaryStreamVisitor : public Visitor { + public: + explicit BinaryStreamVisitor(std::ostream& stream) : stream_(stream) { + } + virtual void visit(const Idmap& idmap); + virtual void visit(const IdmapHeader& header); + virtual void visit(const IdmapData& data); + virtual void visit(const IdmapData::Header& header); + virtual void visit(const IdmapData::TypeEntry& type_entry); + + private: + void Write16(uint16_t value); + void Write32(uint32_t value); + void WriteString(const StringPiece& value); + std::ostream& stream_; +}; + +} // namespace idmap2 +} // namespace android + +#endif // IDMAP2_INCLUDE_IDMAP2_BINARYSTREAMVISITOR_H_ diff --git a/cmds/idmap2/include/idmap2/CommandLineOptions.h b/cmds/idmap2/include/idmap2/CommandLineOptions.h new file mode 100644 index 000000000000..f3aa68b8d1a2 --- /dev/null +++ b/cmds/idmap2/include/idmap2/CommandLineOptions.h @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef IDMAP2_INCLUDE_IDMAP2_COMMANDLINEOPTIONS_H_ +#define IDMAP2_INCLUDE_IDMAP2_COMMANDLINEOPTIONS_H_ + +#include <functional> +#include <memory> +#include <ostream> +#include <string> +#include <vector> + +namespace android { +namespace idmap2 { + +/* + * Utility class to convert a command line, including options (--path foo.txt), + * into data structures (options.path = "foo.txt"). + */ +class CommandLineOptions { + public: + static std::unique_ptr<std::vector<std::string>> ConvertArgvToVector(int argc, const char** argv); + + explicit CommandLineOptions(const std::string& name) : name_(name) { + } + + CommandLineOptions& OptionalFlag(const std::string& name, const std::string& description, + bool* value); + CommandLineOptions& MandatoryOption(const std::string& name, const std::string& description, + std::string* value); + CommandLineOptions& MandatoryOption(const std::string& name, const std::string& description, + std::vector<std::string>* value); + CommandLineOptions& OptionalOption(const std::string& name, const std::string& description, + std::string* value); + bool Parse(const std::vector<std::string>& argv, std::ostream& outError) const; + void Usage(std::ostream& out) const; + + private: + struct Option { + std::string name; + std::string description; + std::function<void(const std::string& value)> action; + enum { + COUNT_OPTIONAL, + COUNT_EXACTLY_ONCE, + COUNT_ONCE_OR_MORE, + } count; + bool argument; + }; + + mutable std::vector<Option> options_; + std::string name_; +}; + +} // namespace idmap2 +} // namespace android + +#endif // IDMAP2_INCLUDE_IDMAP2_COMMANDLINEOPTIONS_H_ diff --git a/cmds/idmap2/include/idmap2/FileUtils.h b/cmds/idmap2/include/idmap2/FileUtils.h new file mode 100644 index 000000000000..05c6d31d395d --- /dev/null +++ b/cmds/idmap2/include/idmap2/FileUtils.h @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef IDMAP2_INCLUDE_IDMAP2_FILEUTILS_H_ +#define IDMAP2_INCLUDE_IDMAP2_FILEUTILS_H_ + +#include <functional> +#include <memory> +#include <string> +#include <vector> + +namespace android { +namespace idmap2 { +namespace utils { +typedef std::function<bool(unsigned char type /* DT_* from dirent.h */, const std::string& path)> + FindFilesPredicate; +std::unique_ptr<std::vector<std::string>> FindFiles(const std::string& root, bool recurse, + const FindFilesPredicate& predicate); + +std::unique_ptr<std::string> ReadFile(int fd); + +std::unique_ptr<std::string> ReadFile(const std::string& path); + +} // namespace utils +} // namespace idmap2 +} // namespace android + +#endif // IDMAP2_INCLUDE_IDMAP2_FILEUTILS_H_ diff --git a/cmds/idmap2/include/idmap2/Idmap.h b/cmds/idmap2/include/idmap2/Idmap.h new file mode 100644 index 000000000000..837b7c5264d5 --- /dev/null +++ b/cmds/idmap2/include/idmap2/Idmap.h @@ -0,0 +1,277 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * # idmap file format (current version) + * + * idmap := header data* + * header := magic version target_crc overlay_crc target_path overlay_path + * data := data_header data_block* + * data_header := target_package_id types_count + * data_block := target_type overlay_type entry_count entry_offset entry* + * overlay_path := string + * target_path := string + * entry := <uint32_t> + * entry_count := <uint16_t> + * entry_offset := <uint16_t> + * magic := <uint32_t> + * overlay_crc := <uint32_t> + * overlay_type := <uint16_t> + * string := <uint8_t>[256] + * target_crc := <uint32_t> + * target_package_id := <uint16_t> + * target_type := <uint16_t> + * types_count := <uint16_t> + * version := <uint32_t> + * + * + * # idmap file format changelog + * ## v1 + * - Identical to idmap v1. + */ + +#ifndef IDMAP2_INCLUDE_IDMAP2_IDMAP_H_ +#define IDMAP2_INCLUDE_IDMAP2_IDMAP_H_ + +#include <iostream> +#include <memory> +#include <string> +#include <vector> + +#include "android-base/macros.h" + +#include "androidfw/ApkAssets.h" +#include "androidfw/ResourceTypes.h" +#include "androidfw/StringPiece.h" + +namespace android { +namespace idmap2 { + +class Idmap; +class Visitor; + +// use typedefs to let the compiler warn us about implicit casts +typedef uint32_t ResourceId; // 0xpptteeee +typedef uint8_t PackageId; // pp in 0xpptteeee +typedef uint8_t TypeId; // tt in 0xpptteeee +typedef uint16_t EntryId; // eeee in 0xpptteeee + +static constexpr const ResourceId kPadding = 0xffffffffu; + +static constexpr const EntryId kNoEntry = 0xffffu; + +// magic number: all idmap files start with this +static constexpr const uint32_t kIdmapMagic = android::kIdmapMagic; + +// current version of the idmap binary format; must be incremented when the format is changed +static constexpr const uint32_t kIdmapCurrentVersion = android::kIdmapCurrentVersion; + +// strings in the idmap are encoded char arrays of length 'kIdmapStringLength' (including mandatory +// terminating null) +static constexpr const size_t kIdmapStringLength = 256; + +class IdmapHeader { + public: + static std::unique_ptr<const IdmapHeader> FromBinaryStream(std::istream& stream); + + inline uint32_t GetMagic() const { + return magic_; + } + + inline uint32_t GetVersion() const { + return version_; + } + + inline uint32_t GetTargetCrc() const { + return target_crc_; + } + + inline uint32_t GetOverlayCrc() const { + return overlay_crc_; + } + + inline StringPiece GetTargetPath() const { + return StringPiece(target_path_); + } + + inline StringPiece GetOverlayPath() const { + return StringPiece(overlay_path_); + } + + // Invariant: anytime the idmap data encoding is changed, the idmap version + // field *must* be incremented. Because of this, we know that if the idmap + // header is up-to-date the entire file is up-to-date. + bool IsUpToDate(std::ostream& out_error) const; + + void accept(Visitor* v) const; + + private: + IdmapHeader() { + } + + uint32_t magic_; + uint32_t version_; + uint32_t target_crc_; + uint32_t overlay_crc_; + char target_path_[kIdmapStringLength]; + char overlay_path_[kIdmapStringLength]; + + friend Idmap; + DISALLOW_COPY_AND_ASSIGN(IdmapHeader); +}; + +class IdmapData { + public: + class Header { + public: + static std::unique_ptr<const Header> FromBinaryStream(std::istream& stream); + + inline PackageId GetTargetPackageId() const { + return target_package_id_; + } + + inline uint16_t GetTypeCount() const { + return type_count_; + } + + void accept(Visitor* v) const; + + private: + Header() { + } + + PackageId target_package_id_; + uint16_t type_count_; + + friend Idmap; + DISALLOW_COPY_AND_ASSIGN(Header); + }; + + class TypeEntry { + public: + static std::unique_ptr<const TypeEntry> FromBinaryStream(std::istream& stream); + + inline TypeId GetTargetTypeId() const { + return target_type_id_; + } + + inline TypeId GetOverlayTypeId() const { + return overlay_type_id_; + } + + inline uint16_t GetEntryCount() const { + return entries_.size(); + } + + inline uint16_t GetEntryOffset() const { + return entry_offset_; + } + + inline EntryId GetEntry(size_t i) const { + return i < entries_.size() ? entries_[i] : 0xffffu; + } + + void accept(Visitor* v) const; + + private: + TypeEntry() { + } + + TypeId target_type_id_; + TypeId overlay_type_id_; + uint16_t entry_offset_; + std::vector<EntryId> entries_; + + friend Idmap; + DISALLOW_COPY_AND_ASSIGN(TypeEntry); + }; + + static std::unique_ptr<const IdmapData> FromBinaryStream(std::istream& stream); + + inline const std::unique_ptr<const Header>& GetHeader() const { + return header_; + } + + inline const std::vector<std::unique_ptr<const TypeEntry>>& GetTypeEntries() const { + return type_entries_; + } + + void accept(Visitor* v) const; + + private: + IdmapData() { + } + + std::unique_ptr<const Header> header_; + std::vector<std::unique_ptr<const TypeEntry>> type_entries_; + + friend Idmap; + DISALLOW_COPY_AND_ASSIGN(IdmapData); +}; + +class Idmap { + public: + static std::string CanonicalIdmapPathFor(const std::string& absolute_dir, + const std::string& absolute_apk_path); + + static std::unique_ptr<const Idmap> FromBinaryStream(std::istream& stream, + std::ostream& out_error); + + // In the current version of idmap, the first package in each resources.arsc + // file is used; change this in the next version of idmap to use a named + // package instead; also update FromApkAssets to take additional parameters: + // the target and overlay package names + static std::unique_ptr<const Idmap> FromApkAssets(const std::string& target_apk_path, + const ApkAssets& target_apk_assets, + const std::string& overlay_apk_path, + const ApkAssets& overlay_apk_assets, + std::ostream& out_error); + + inline const std::unique_ptr<const IdmapHeader>& GetHeader() const { + return header_; + } + + inline const std::vector<std::unique_ptr<const IdmapData>>& GetData() const { + return data_; + } + + void accept(Visitor* v) const; + + private: + Idmap() { + } + + std::unique_ptr<const IdmapHeader> header_; + std::vector<std::unique_ptr<const IdmapData>> data_; + + DISALLOW_COPY_AND_ASSIGN(Idmap); +}; + +class Visitor { + public: + virtual ~Visitor() { + } + virtual void visit(const Idmap& idmap) = 0; + virtual void visit(const IdmapHeader& header) = 0; + virtual void visit(const IdmapData& data) = 0; + virtual void visit(const IdmapData::Header& header) = 0; + virtual void visit(const IdmapData::TypeEntry& type_entry) = 0; +}; + +} // namespace idmap2 +} // namespace android + +#endif // IDMAP2_INCLUDE_IDMAP2_IDMAP_H_ diff --git a/cmds/idmap2/include/idmap2/PrettyPrintVisitor.h b/cmds/idmap2/include/idmap2/PrettyPrintVisitor.h new file mode 100644 index 000000000000..c388f4b94251 --- /dev/null +++ b/cmds/idmap2/include/idmap2/PrettyPrintVisitor.h @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef IDMAP2_INCLUDE_IDMAP2_PRETTYPRINTVISITOR_H_ +#define IDMAP2_INCLUDE_IDMAP2_PRETTYPRINTVISITOR_H_ + +#include <iostream> +#include <memory> + +#include "androidfw/AssetManager2.h" + +#include "idmap2/Idmap.h" + +namespace android { + +class ApkAssets; + +namespace idmap2 { + +class PrettyPrintVisitor : public Visitor { + public: + explicit PrettyPrintVisitor(std::ostream& stream) : stream_(stream) { + } + virtual void visit(const Idmap& idmap); + virtual void visit(const IdmapHeader& header); + virtual void visit(const IdmapData& data); + virtual void visit(const IdmapData::Header& header); + virtual void visit(const IdmapData::TypeEntry& type_entry); + + private: + std::ostream& stream_; + std::unique_ptr<const ApkAssets> target_apk_; + AssetManager2 target_am_; + PackageId last_seen_package_id_; +}; + +} // namespace idmap2 +} // namespace android + +#endif // IDMAP2_INCLUDE_IDMAP2_PRETTYPRINTVISITOR_H_ diff --git a/cmds/idmap2/include/idmap2/RawPrintVisitor.h b/cmds/idmap2/include/idmap2/RawPrintVisitor.h new file mode 100644 index 000000000000..7e33b3b06fc3 --- /dev/null +++ b/cmds/idmap2/include/idmap2/RawPrintVisitor.h @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef IDMAP2_INCLUDE_IDMAP2_RAWPRINTVISITOR_H_ +#define IDMAP2_INCLUDE_IDMAP2_RAWPRINTVISITOR_H_ + +#include <iostream> +#include <memory> +#include <string> + +#include "androidfw/AssetManager2.h" + +#include "idmap2/Idmap.h" + +namespace android { + +class ApkAssets; + +namespace idmap2 { + +class RawPrintVisitor : public Visitor { + public: + explicit RawPrintVisitor(std::ostream& stream) : stream_(stream), offset_(0) { + } + virtual void visit(const Idmap& idmap); + virtual void visit(const IdmapHeader& header); + virtual void visit(const IdmapData& data); + virtual void visit(const IdmapData::Header& header); + virtual void visit(const IdmapData::TypeEntry& type_entry); + + private: + void print(uint16_t value, const char* fmt, ...); + void print(uint32_t value, const char* fmt, ...); + void print(const std::string& value, const char* fmt, ...); + + std::ostream& stream_; + std::unique_ptr<const ApkAssets> target_apk_; + AssetManager2 target_am_; + size_t offset_; + PackageId last_seen_package_id_; +}; + +} // namespace idmap2 +} // namespace android + +#endif // IDMAP2_INCLUDE_IDMAP2_RAWPRINTVISITOR_H_ diff --git a/cmds/idmap2/include/idmap2/ResourceUtils.h b/cmds/idmap2/include/idmap2/ResourceUtils.h new file mode 100644 index 000000000000..88a835b6439c --- /dev/null +++ b/cmds/idmap2/include/idmap2/ResourceUtils.h @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef IDMAP2_INCLUDE_IDMAP2_RESOURCEUTILS_H_ +#define IDMAP2_INCLUDE_IDMAP2_RESOURCEUTILS_H_ + +#include <string> +#include <utility> + +#include "android-base/macros.h" +#include "androidfw/AssetManager2.h" + +#include "idmap2/Idmap.h" + +namespace android { +namespace idmap2 { +namespace utils { + +std::pair<bool, std::string> WARN_UNUSED ResToTypeEntryName(const AssetManager2& am, + ResourceId resid); + +} // namespace utils +} // namespace idmap2 +} // namespace android + +#endif // IDMAP2_INCLUDE_IDMAP2_RESOURCEUTILS_H_ diff --git a/cmds/idmap2/include/idmap2/Xml.h b/cmds/idmap2/include/idmap2/Xml.h new file mode 100644 index 000000000000..9ab5ec454750 --- /dev/null +++ b/cmds/idmap2/include/idmap2/Xml.h @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef IDMAP2_INCLUDE_IDMAP2_XML_H_ +#define IDMAP2_INCLUDE_IDMAP2_XML_H_ + +#include <map> +#include <memory> +#include <string> + +#include "android-base/macros.h" +#include "androidfw/ResourceTypes.h" +#include "utils/String16.h" + +namespace android { +namespace idmap2 { + +class Xml { + public: + static std::unique_ptr<const Xml> Create(const uint8_t* data, size_t size, bool copyData = false); + + std::unique_ptr<std::map<std::string, std::string>> FindTag(const std::string& name) const; + + ~Xml(); + + private: + Xml() { + } + + mutable ResXMLTree xml_; + + DISALLOW_COPY_AND_ASSIGN(Xml); +}; + +} // namespace idmap2 +} // namespace android + +#endif // IDMAP2_INCLUDE_IDMAP2_XML_H_ diff --git a/cmds/idmap2/include/idmap2/ZipFile.h b/cmds/idmap2/include/idmap2/ZipFile.h new file mode 100644 index 000000000000..328bd367adfc --- /dev/null +++ b/cmds/idmap2/include/idmap2/ZipFile.h @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef IDMAP2_INCLUDE_IDMAP2_ZIPFILE_H_ +#define IDMAP2_INCLUDE_IDMAP2_ZIPFILE_H_ + +#include <memory> +#include <string> +#include <utility> + +#include "android-base/macros.h" +#include "ziparchive/zip_archive.h" + +namespace android { +namespace idmap2 { + +struct MemoryChunk { + size_t size; + uint8_t buf[0]; + + static std::unique_ptr<MemoryChunk> Allocate(size_t size); + + private: + MemoryChunk() { + } +}; + +class ZipFile { + public: + static std::unique_ptr<const ZipFile> Open(const std::string& path); + + std::unique_ptr<const MemoryChunk> Uncompress(const std::string& entryPath) const; + std::pair<bool, uint32_t> Crc(const std::string& entryPath) const; + + ~ZipFile(); + + private: + explicit ZipFile(const ::ZipArchiveHandle handle) : handle_(handle) { + } + + const ::ZipArchiveHandle handle_; + + DISALLOW_COPY_AND_ASSIGN(ZipFile); +}; + +} // namespace idmap2 +} // namespace android + +#endif // IDMAP2_INCLUDE_IDMAP2_ZIPFILE_H_ diff --git a/cmds/idmap2/libidmap2/BinaryStreamVisitor.cpp b/cmds/idmap2/libidmap2/BinaryStreamVisitor.cpp new file mode 100644 index 000000000000..29969a23250b --- /dev/null +++ b/cmds/idmap2/libidmap2/BinaryStreamVisitor.cpp @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <algorithm> +#include <cstring> +#include <string> + +#include "android-base/macros.h" + +#include "idmap2/BinaryStreamVisitor.h" + +namespace android { +namespace idmap2 { + +void BinaryStreamVisitor::Write16(uint16_t value) { + uint16_t x = htodl(value); + stream_.write(reinterpret_cast<char*>(&x), sizeof(uint16_t)); +} + +void BinaryStreamVisitor::Write32(uint32_t value) { + uint32_t x = htodl(value); + stream_.write(reinterpret_cast<char*>(&x), sizeof(uint32_t)); +} + +void BinaryStreamVisitor::WriteString(const StringPiece& value) { + char buf[kIdmapStringLength]; + memset(buf, 0, sizeof(buf)); + memcpy(buf, value.data(), std::min(value.size(), sizeof(buf))); + stream_.write(buf, sizeof(buf)); +} + +void BinaryStreamVisitor::visit(const Idmap& idmap ATTRIBUTE_UNUSED) { + // nothing to do +} + +void BinaryStreamVisitor::visit(const IdmapHeader& header) { + Write32(header.GetMagic()); + Write32(header.GetVersion()); + Write32(header.GetTargetCrc()); + Write32(header.GetOverlayCrc()); + WriteString(header.GetTargetPath()); + WriteString(header.GetOverlayPath()); +} + +void BinaryStreamVisitor::visit(const IdmapData& data ATTRIBUTE_UNUSED) { + // nothing to do +} + +void BinaryStreamVisitor::visit(const IdmapData::Header& header) { + Write16(header.GetTargetPackageId()); + Write16(header.GetTypeCount()); +} + +void BinaryStreamVisitor::visit(const IdmapData::TypeEntry& te) { + const uint16_t entryCount = te.GetEntryCount(); + + Write16(te.GetTargetTypeId()); + Write16(te.GetOverlayTypeId()); + Write16(entryCount); + Write16(te.GetEntryOffset()); + for (uint16_t i = 0; i < entryCount; i++) { + EntryId entry_id = te.GetEntry(i); + Write32(entry_id != kNoEntry ? static_cast<uint32_t>(entry_id) : kPadding); + } +} + +} // namespace idmap2 +} // namespace android diff --git a/cmds/idmap2/libidmap2/CommandLineOptions.cpp b/cmds/idmap2/libidmap2/CommandLineOptions.cpp new file mode 100644 index 000000000000..28c3797226e1 --- /dev/null +++ b/cmds/idmap2/libidmap2/CommandLineOptions.cpp @@ -0,0 +1,163 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <algorithm> +#include <iomanip> +#include <iostream> +#include <memory> +#include <set> +#include <string> +#include <vector> + +#include "android-base/macros.h" + +#include "idmap2/CommandLineOptions.h" + +namespace android { +namespace idmap2 { + +std::unique_ptr<std::vector<std::string>> CommandLineOptions::ConvertArgvToVector( + int argc, const char** argv) { + return std::unique_ptr<std::vector<std::string>>( + new std::vector<std::string>(argv + 1, argv + argc)); +} + +CommandLineOptions& CommandLineOptions::OptionalFlag(const std::string& name, + const std::string& description, bool* value) { + assert(value != nullptr); + auto func = [value](const std::string& arg ATTRIBUTE_UNUSED) -> void { *value = true; }; + options_.push_back(Option{name, description, func, Option::COUNT_OPTIONAL, false}); + return *this; +} + +CommandLineOptions& CommandLineOptions::MandatoryOption(const std::string& name, + const std::string& description, + std::string* value) { + assert(value != nullptr); + auto func = [value](const std::string& arg) -> void { *value = arg; }; + options_.push_back(Option{name, description, func, Option::COUNT_EXACTLY_ONCE, true}); + return *this; +} + +CommandLineOptions& CommandLineOptions::MandatoryOption(const std::string& name, + const std::string& description, + std::vector<std::string>* value) { + assert(value != nullptr); + auto func = [value](const std::string& arg) -> void { value->push_back(arg); }; + options_.push_back(Option{name, description, func, Option::COUNT_ONCE_OR_MORE, true}); + return *this; +} + +CommandLineOptions& CommandLineOptions::OptionalOption(const std::string& name, + const std::string& description, + std::string* value) { + assert(value != nullptr); + auto func = [value](const std::string& arg) -> void { *value = arg; }; + options_.push_back(Option{name, description, func, Option::COUNT_OPTIONAL, true}); + return *this; +} + +bool CommandLineOptions::Parse(const std::vector<std::string>& argv, std::ostream& outError) const { + const auto pivot = std::partition(options_.begin(), options_.end(), [](const Option& opt) { + return opt.count != Option::COUNT_OPTIONAL; + }); + std::set<std::string> mandatory_opts; + std::transform(options_.begin(), pivot, std::inserter(mandatory_opts, mandatory_opts.end()), + [](const Option& opt) -> std::string { return opt.name; }); + + const size_t argv_size = argv.size(); + for (size_t i = 0; i < argv_size; i++) { + const std::string arg = argv[i]; + if ("--help" == arg || "-h" == arg) { + Usage(outError); + return false; + } + bool match = false; + for (const Option& opt : options_) { + if (opt.name == arg) { + match = true; + + if (opt.argument) { + i++; + if (i >= argv_size) { + outError << "error: " << opt.name << ": missing argument" << std::endl; + Usage(outError); + return false; + } + } + opt.action(argv[i]); + mandatory_opts.erase(opt.name); + break; + } + } + if (!match) { + outError << "error: " << arg << ": unknown option" << std::endl; + Usage(outError); + return false; + } + } + + if (!mandatory_opts.empty()) { + for (auto iter = mandatory_opts.cbegin(); iter != mandatory_opts.cend(); ++iter) { + outError << "error: " << *iter << ": missing mandatory option" << std::endl; + } + Usage(outError); + return false; + } + return true; +} + +void CommandLineOptions::Usage(std::ostream& out) const { + size_t maxLength = 0; + out << "usage: " << name_; + for (const Option& opt : options_) { + const bool mandatory = opt.count != Option::COUNT_OPTIONAL; + out << " "; + if (!mandatory) { + out << "["; + } + if (opt.argument) { + out << opt.name << " arg"; + maxLength = std::max(maxLength, opt.name.size() + 4); + } else { + out << opt.name; + maxLength = std::max(maxLength, opt.name.size()); + } + if (!mandatory) { + out << "]"; + } + if (opt.count == Option::COUNT_ONCE_OR_MORE) { + out << " [" << opt.name << " arg [..]]"; + } + } + out << std::endl << std::endl; + for (const Option& opt : options_) { + out << std::left << std::setw(maxLength); + if (opt.argument) { + out << (opt.name + " arg"); + } else { + out << opt.name; + } + out << " " << opt.description; + if (opt.count == Option::COUNT_ONCE_OR_MORE) { + out << " (can be provided multiple times)"; + } + out << std::endl; + } +} + +} // namespace idmap2 +} // namespace android diff --git a/cmds/idmap2/libidmap2/FileUtils.cpp b/cmds/idmap2/libidmap2/FileUtils.cpp new file mode 100644 index 000000000000..4ac4c04d0bfc --- /dev/null +++ b/cmds/idmap2/libidmap2/FileUtils.cpp @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <dirent.h> +#include <errno.h> +#include <sys/types.h> +#include <unistd.h> + +#include <fstream> +#include <memory> +#include <string> +#include <utility> +#include <vector> + +#include "idmap2/FileUtils.h" + +namespace android { +namespace idmap2 { +namespace utils { + +std::unique_ptr<std::vector<std::string>> FindFiles(const std::string& root, bool recurse, + const FindFilesPredicate& predicate) { + DIR* dir = opendir(root.c_str()); + if (!dir) { + return nullptr; + } + std::unique_ptr<std::vector<std::string>> vector(new std::vector<std::string>()); + struct dirent* dirent; + while ((dirent = readdir(dir))) { + const std::string path = root + "/" + dirent->d_name; + if (predicate(dirent->d_type, path)) { + vector->push_back(path); + } + if (recurse && dirent->d_type == DT_DIR && strcmp(dirent->d_name, ".") != 0 && + strcmp(dirent->d_name, "..") != 0) { + auto sub_vector = FindFiles(path, recurse, predicate); + if (!sub_vector) { + closedir(dir); + return nullptr; + } + vector->insert(vector->end(), sub_vector->begin(), sub_vector->end()); + } + } + closedir(dir); + + return vector; +} + +std::unique_ptr<std::string> ReadFile(const std::string& path) { + std::unique_ptr<std::string> str(new std::string()); + std::ifstream fin(path); + str->append({std::istreambuf_iterator<char>(fin), std::istreambuf_iterator<char>()}); + fin.close(); + return str; +} + +std::unique_ptr<std::string> ReadFile(int fd) { + std::unique_ptr<std::string> str(new std::string()); + char buf[1024]; + ssize_t r; + while ((r = read(fd, buf, sizeof(buf))) > 0) { + str->append(buf, r); + } + return r == 0 ? std::move(str) : nullptr; +} + +} // namespace utils +} // namespace idmap2 +} // namespace android diff --git a/cmds/idmap2/libidmap2/Idmap.cpp b/cmds/idmap2/libidmap2/Idmap.cpp new file mode 100644 index 000000000000..5a47e301b66c --- /dev/null +++ b/cmds/idmap2/libidmap2/Idmap.cpp @@ -0,0 +1,443 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <algorithm> +#include <iostream> +#include <iterator> +#include <limits> +#include <map> +#include <memory> +#include <set> +#include <string> +#include <utility> +#include <vector> + +#include "android-base/macros.h" +#include "android-base/stringprintf.h" +#include "androidfw/AssetManager2.h" +#include "utils/String16.h" +#include "utils/String8.h" + +#include "idmap2/Idmap.h" +#include "idmap2/ResourceUtils.h" +#include "idmap2/ZipFile.h" + +namespace android { +namespace idmap2 { + +#define EXTRACT_TYPE(resid) ((0x00ff0000 & (resid)) >> 16) + +#define EXTRACT_ENTRY(resid) (0x0000ffff & (resid)) + +struct MatchingResources { + void Add(ResourceId target_resid, ResourceId overlay_resid) { + TypeId target_typeid = EXTRACT_TYPE(target_resid); + if (map.find(target_typeid) == map.end()) { + map.emplace(target_typeid, std::set<std::pair<ResourceId, ResourceId>>()); + } + map[target_typeid].insert(std::make_pair(target_resid, overlay_resid)); + } + + // target type id -> set { pair { overlay entry id, overlay entry id } } + std::map<TypeId, std::set<std::pair<ResourceId, ResourceId>>> map; +}; + +static bool WARN_UNUSED Read16(std::istream& stream, uint16_t* out) { + uint16_t value; + if (stream.read(reinterpret_cast<char*>(&value), sizeof(uint16_t))) { + *out = dtohl(value); + return true; + } + return false; +} + +static bool WARN_UNUSED Read32(std::istream& stream, uint32_t* out) { + uint32_t value; + if (stream.read(reinterpret_cast<char*>(&value), sizeof(uint32_t))) { + *out = dtohl(value); + return true; + } + return false; +} + +// a string is encoded as a kIdmapStringLength char array; the array is always null-terminated +static bool WARN_UNUSED ReadString(std::istream& stream, char out[kIdmapStringLength]) { + char buf[kIdmapStringLength]; + memset(buf, 0, sizeof(buf)); + if (!stream.read(buf, sizeof(buf))) { + return false; + } + if (buf[sizeof(buf) - 1] != '\0') { + return false; + } + memcpy(out, buf, sizeof(buf)); + return true; +} + +static ResourceId NameToResid(const AssetManager2& am, const std::string& name) { + return am.GetResourceId(name); +} + +// TODO(martenkongstad): scan for package name instead of assuming package at index 0 +// +// idmap version 0x01 naively assumes that the package to use is always the first ResTable_package +// in the resources.arsc blob. In most cases, there is only a single ResTable_package anyway, so +// this assumption tends to work out. That said, the correct thing to do is to scan +// resources.arsc for a package with a given name as read from the package manifest instead of +// relying on a hard-coded index. This however requires storing the package name in the idmap +// header, which in turn requires incrementing the idmap version. Because the initial version of +// idmap2 is compatible with idmap, this will have to wait for now. +static const LoadedPackage* GetPackageAtIndex0(const LoadedArsc& loaded_arsc) { + const std::vector<std::unique_ptr<const LoadedPackage>>& packages = loaded_arsc.GetPackages(); + if (packages.empty()) { + return nullptr; + } + int id = packages[0]->GetPackageId(); + return loaded_arsc.GetPackageById(id); +} + +std::unique_ptr<const IdmapHeader> IdmapHeader::FromBinaryStream(std::istream& stream) { + std::unique_ptr<IdmapHeader> idmap_header(new IdmapHeader()); + + if (!Read32(stream, &idmap_header->magic_) || !Read32(stream, &idmap_header->version_) || + !Read32(stream, &idmap_header->target_crc_) || !Read32(stream, &idmap_header->overlay_crc_) || + !ReadString(stream, idmap_header->target_path_) || + !ReadString(stream, idmap_header->overlay_path_)) { + return nullptr; + } + + return std::move(idmap_header); +} + +bool IdmapHeader::IsUpToDate(std::ostream& out_error) const { + if (magic_ != kIdmapMagic) { + out_error << base::StringPrintf("error: bad magic: actual 0x%08x, expected 0x%08x", magic_, + kIdmapMagic) + << std::endl; + return false; + } + + if (version_ != kIdmapCurrentVersion) { + out_error << base::StringPrintf("error: bad version: actual 0x%08x, expected 0x%08x", version_, + kIdmapCurrentVersion) + << std::endl; + return false; + } + + const std::unique_ptr<const ZipFile> target_zip = ZipFile::Open(target_path_); + if (!target_zip) { + out_error << "error: failed to open target " << target_path_ << std::endl; + return false; + } + + bool status; + uint32_t target_crc; + std::tie(status, target_crc) = target_zip->Crc("resources.arsc"); + if (!status) { + out_error << "error: failed to get target crc" << std::endl; + return false; + } + + if (target_crc_ != target_crc) { + out_error << base::StringPrintf( + "error: bad target crc: idmap version 0x%08x, file system version 0x%08x", + target_crc_, target_crc) + << std::endl; + return false; + } + + const std::unique_ptr<const ZipFile> overlay_zip = ZipFile::Open(overlay_path_); + if (!overlay_zip) { + out_error << "error: failed to open overlay " << overlay_path_ << std::endl; + return false; + } + + uint32_t overlay_crc; + std::tie(status, overlay_crc) = overlay_zip->Crc("resources.arsc"); + if (!status) { + out_error << "error: failed to get overlay crc" << std::endl; + return false; + } + + if (overlay_crc_ != overlay_crc) { + out_error << base::StringPrintf( + "error: bad overlay crc: idmap version 0x%08x, file system version 0x%08x", + overlay_crc_, overlay_crc) + << std::endl; + return false; + } + + return true; +} + +std::unique_ptr<const IdmapData::Header> IdmapData::Header::FromBinaryStream(std::istream& stream) { + std::unique_ptr<IdmapData::Header> idmap_data_header(new IdmapData::Header()); + + uint16_t target_package_id16; + if (!Read16(stream, &target_package_id16) || !Read16(stream, &idmap_data_header->type_count_)) { + return nullptr; + } + idmap_data_header->target_package_id_ = target_package_id16; + + return std::move(idmap_data_header); +} + +std::unique_ptr<const IdmapData::TypeEntry> IdmapData::TypeEntry::FromBinaryStream( + std::istream& stream) { + std::unique_ptr<IdmapData::TypeEntry> data(new IdmapData::TypeEntry()); + + uint16_t target_type16, overlay_type16, entry_count; + if (!Read16(stream, &target_type16) || !Read16(stream, &overlay_type16) || + !Read16(stream, &entry_count) || !Read16(stream, &data->entry_offset_)) { + return nullptr; + } + data->target_type_id_ = target_type16; + data->overlay_type_id_ = overlay_type16; + for (uint16_t i = 0; i < entry_count; i++) { + ResourceId resid; + if (!Read32(stream, &resid)) { + return nullptr; + } + data->entries_.push_back(resid); + } + + return std::move(data); +} + +std::unique_ptr<const IdmapData> IdmapData::FromBinaryStream(std::istream& stream) { + std::unique_ptr<IdmapData> data(new IdmapData()); + data->header_ = IdmapData::Header::FromBinaryStream(stream); + if (!data->header_) { + return nullptr; + } + for (size_t type_count = 0; type_count < data->header_->GetTypeCount(); type_count++) { + std::unique_ptr<const TypeEntry> type = IdmapData::TypeEntry::FromBinaryStream(stream); + if (!type) { + return nullptr; + } + data->type_entries_.push_back(std::move(type)); + } + return std::move(data); +} + +std::string Idmap::CanonicalIdmapPathFor(const std::string& absolute_dir, + const std::string& absolute_apk_path) { + assert(absolute_dir.size() > 0 && absolute_dir[0] == "/"); + assert(absolute_apk_path.size() > 0 && absolute_apk_path[0] == "/"); + std::string copy(++absolute_apk_path.cbegin(), absolute_apk_path.cend()); + replace(copy.begin(), copy.end(), '/', '@'); + return absolute_dir + "/" + copy + "@idmap"; +} + +std::unique_ptr<const Idmap> Idmap::FromBinaryStream(std::istream& stream, + std::ostream& out_error) { + std::unique_ptr<Idmap> idmap(new Idmap()); + + idmap->header_ = IdmapHeader::FromBinaryStream(stream); + if (!idmap->header_) { + out_error << "error: failed to parse idmap header" << std::endl; + return nullptr; + } + + // idmap version 0x01 does not specify the number of data blocks that follow + // the idmap header; assume exactly one data block + for (int i = 0; i < 1; i++) { + std::unique_ptr<const IdmapData> data = IdmapData::FromBinaryStream(stream); + if (!data) { + out_error << "error: failed to parse data block " << i << std::endl; + return nullptr; + } + idmap->data_.push_back(std::move(data)); + } + + return std::move(idmap); +} + +std::unique_ptr<const Idmap> Idmap::FromApkAssets(const std::string& target_apk_path, + const ApkAssets& target_apk_assets, + const std::string& overlay_apk_path, + const ApkAssets& overlay_apk_assets, + std::ostream& out_error) { + AssetManager2 target_asset_manager; + if (!target_asset_manager.SetApkAssets({&target_apk_assets}, true, false)) { + out_error << "error: failed to create target asset manager" << std::endl; + return nullptr; + } + + AssetManager2 overlay_asset_manager; + if (!overlay_asset_manager.SetApkAssets({&overlay_apk_assets}, true, false)) { + out_error << "error: failed to create overlay asset manager" << std::endl; + return nullptr; + } + + const LoadedArsc* target_arsc = target_apk_assets.GetLoadedArsc(); + if (!target_arsc) { + out_error << "error: failed to load target resources.arsc" << std::endl; + return nullptr; + } + + const LoadedArsc* overlay_arsc = overlay_apk_assets.GetLoadedArsc(); + if (!overlay_arsc) { + out_error << "error: failed to load overlay resources.arsc" << std::endl; + return nullptr; + } + + const LoadedPackage* target_pkg = GetPackageAtIndex0(*target_arsc); + if (!target_pkg) { + out_error << "error: failed to load target package from resources.arsc" << std::endl; + return nullptr; + } + + const LoadedPackage* overlay_pkg = GetPackageAtIndex0(*overlay_arsc); + if (!overlay_pkg) { + out_error << "error: failed to load overlay package from resources.arsc" << std::endl; + return nullptr; + } + + const std::unique_ptr<const ZipFile> target_zip = ZipFile::Open(target_apk_path); + if (!target_zip) { + out_error << "error: failed to open target as zip" << std::endl; + return nullptr; + } + + const std::unique_ptr<const ZipFile> overlay_zip = ZipFile::Open(overlay_apk_path); + if (!overlay_zip) { + out_error << "error: failed to open overlay as zip" << std::endl; + return nullptr; + } + + std::unique_ptr<IdmapHeader> header(new IdmapHeader()); + header->magic_ = kIdmapMagic; + header->version_ = kIdmapCurrentVersion; + bool crc_status; + std::tie(crc_status, header->target_crc_) = target_zip->Crc("resources.arsc"); + if (!crc_status) { + out_error << "error: failed to get zip crc for target" << std::endl; + return nullptr; + } + std::tie(crc_status, header->overlay_crc_) = overlay_zip->Crc("resources.arsc"); + if (!crc_status) { + out_error << "error: failed to get zip crc for overlay" << std::endl; + return nullptr; + } + + if (target_apk_path.size() > sizeof(header->target_path_)) { + out_error << "error: target apk path \"" << target_apk_path << "\" longer that maximum size " + << sizeof(header->target_path_) << std::endl; + return nullptr; + } + memset(header->target_path_, 0, sizeof(header->target_path_)); + memcpy(header->target_path_, target_apk_path.data(), target_apk_path.size()); + + if (overlay_apk_path.size() > sizeof(header->overlay_path_)) { + out_error << "error: overlay apk path \"" << overlay_apk_path << "\" longer that maximum size " + << sizeof(header->overlay_path_) << std::endl; + return nullptr; + } + memset(header->overlay_path_, 0, sizeof(header->overlay_path_)); + memcpy(header->overlay_path_, overlay_apk_path.data(), overlay_apk_path.size()); + + std::unique_ptr<Idmap> idmap(new Idmap()); + idmap->header_ = std::move(header); + + // find the resources that exist in both packages + MatchingResources matching_resources; + const auto end = overlay_pkg->end(); + for (auto iter = overlay_pkg->begin(); iter != end; ++iter) { + const ResourceId overlay_resid = *iter; + bool lookup_ok; + std::string name; + std::tie(lookup_ok, name) = utils::ResToTypeEntryName(overlay_asset_manager, overlay_resid); + if (!lookup_ok) { + continue; + } + // prepend "<package>:" to turn name into "<package>:<type>/<name>" + name = base::StringPrintf("%s:%s", target_pkg->GetPackageName().c_str(), name.c_str()); + const ResourceId target_resid = NameToResid(target_asset_manager, name); + if (target_resid == 0) { + continue; + } + matching_resources.Add(target_resid, overlay_resid); + } + + // encode idmap data + std::unique_ptr<IdmapData> data(new IdmapData()); + const auto types_end = matching_resources.map.cend(); + for (auto ti = matching_resources.map.cbegin(); ti != types_end; ++ti) { + auto ei = ti->second.cbegin(); + std::unique_ptr<IdmapData::TypeEntry> type(new IdmapData::TypeEntry()); + type->target_type_id_ = EXTRACT_TYPE(ei->first); + type->overlay_type_id_ = EXTRACT_TYPE(ei->second); + type->entry_offset_ = EXTRACT_ENTRY(ei->first); + EntryId last_target_entry = kNoEntry; + for (; ei != ti->second.cend(); ++ei) { + if (last_target_entry != kNoEntry) { + int count = EXTRACT_ENTRY(ei->first) - last_target_entry - 1; + type->entries_.insert(type->entries_.end(), count, kNoEntry); + } + type->entries_.push_back(EXTRACT_ENTRY(ei->second)); + last_target_entry = EXTRACT_ENTRY(ei->first); + } + data->type_entries_.push_back(std::move(type)); + } + + std::unique_ptr<IdmapData::Header> data_header(new IdmapData::Header()); + data_header->target_package_id_ = target_pkg->GetPackageId(); + data_header->type_count_ = data->type_entries_.size(); + data->header_ = std::move(data_header); + + idmap->data_.push_back(std::move(data)); + + return std::move(idmap); +} + +void IdmapHeader::accept(Visitor* v) const { + assert(v != nullptr); + v->visit(*this); +} + +void IdmapData::Header::accept(Visitor* v) const { + assert(v != nullptr); + v->visit(*this); +} + +void IdmapData::TypeEntry::accept(Visitor* v) const { + assert(v != nullptr); + v->visit(*this); +} + +void IdmapData::accept(Visitor* v) const { + assert(v != nullptr); + v->visit(*this); + header_->accept(v); + auto end = type_entries_.cend(); + for (auto iter = type_entries_.cbegin(); iter != end; ++iter) { + (*iter)->accept(v); + } +} + +void Idmap::accept(Visitor* v) const { + assert(v != nullptr); + v->visit(*this); + header_->accept(v); + auto end = data_.cend(); + for (auto iter = data_.cbegin(); iter != end; ++iter) { + (*iter)->accept(v); + } +} + +} // namespace idmap2 +} // namespace android diff --git a/cmds/idmap2/libidmap2/PrettyPrintVisitor.cpp b/cmds/idmap2/libidmap2/PrettyPrintVisitor.cpp new file mode 100644 index 000000000000..492e6f049d68 --- /dev/null +++ b/cmds/idmap2/libidmap2/PrettyPrintVisitor.cpp @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <string> +#include <utility> + +#include "android-base/macros.h" +#include "android-base/stringprintf.h" +#include "androidfw/ApkAssets.h" + +#include "idmap2/PrettyPrintVisitor.h" +#include "idmap2/ResourceUtils.h" + +namespace android { +namespace idmap2 { + +#define RESID(pkg, type, entry) (((pkg) << 24) | ((type) << 16) | (entry)) + +void PrettyPrintVisitor::visit(const Idmap& idmap ATTRIBUTE_UNUSED) { +} + +void PrettyPrintVisitor::visit(const IdmapHeader& header) { + stream_ << "target apk path : " << header.GetTargetPath() << std::endl + << "overlay apk path : " << header.GetOverlayPath() << std::endl; + + target_apk_ = ApkAssets::Load(header.GetTargetPath().to_string()); + if (target_apk_) { + target_am_.SetApkAssets({target_apk_.get()}); + } +} + +void PrettyPrintVisitor::visit(const IdmapData& data ATTRIBUTE_UNUSED) { +} + +void PrettyPrintVisitor::visit(const IdmapData::Header& header ATTRIBUTE_UNUSED) { + last_seen_package_id_ = header.GetTargetPackageId(); +} + +void PrettyPrintVisitor::visit(const IdmapData::TypeEntry& te) { + const bool target_package_loaded = !target_am_.GetApkAssets().empty(); + for (uint16_t i = 0; i < te.GetEntryCount(); i++) { + const EntryId entry = te.GetEntry(i); + if (entry == kNoEntry) { + continue; + } + + const ResourceId target_resid = + RESID(last_seen_package_id_, te.GetTargetTypeId(), te.GetEntryOffset() + i); + const ResourceId overlay_resid = RESID(last_seen_package_id_, te.GetOverlayTypeId(), entry); + + stream_ << base::StringPrintf("0x%08x -> 0x%08x", target_resid, overlay_resid); + if (target_package_loaded) { + bool lookup_ok; + std::string name; + std::tie(lookup_ok, name) = utils::ResToTypeEntryName(target_am_, target_resid); + if (lookup_ok) { + stream_ << " " << name; + } + } + stream_ << std::endl; + } +} + +} // namespace idmap2 +} // namespace android diff --git a/cmds/idmap2/libidmap2/RawPrintVisitor.cpp b/cmds/idmap2/libidmap2/RawPrintVisitor.cpp new file mode 100644 index 000000000000..57cfc8ef85b4 --- /dev/null +++ b/cmds/idmap2/libidmap2/RawPrintVisitor.cpp @@ -0,0 +1,131 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <cstdarg> +#include <string> +#include <utility> + +#include "android-base/macros.h" +#include "android-base/stringprintf.h" +#include "androidfw/ApkAssets.h" + +#include "idmap2/RawPrintVisitor.h" +#include "idmap2/ResourceUtils.h" + +using android::ApkAssets; + +namespace android { +namespace idmap2 { + +// verbatim copy fomr PrettyPrintVisitor.cpp, move to common utils +#define RESID(pkg, type, entry) (((pkg) << 24) | ((type) << 16) | (entry)) + +void RawPrintVisitor::visit(const Idmap& idmap ATTRIBUTE_UNUSED) { +} + +void RawPrintVisitor::visit(const IdmapHeader& header) { + print(header.GetMagic(), "magic"); + print(header.GetVersion(), "version"); + print(header.GetTargetCrc(), "target crc"); + print(header.GetOverlayCrc(), "overlay crc"); + print(header.GetTargetPath().to_string(), "target path"); + print(header.GetOverlayPath().to_string(), "overlay path"); + + target_apk_ = ApkAssets::Load(header.GetTargetPath().to_string()); + if (target_apk_) { + target_am_.SetApkAssets({target_apk_.get()}); + } +} + +void RawPrintVisitor::visit(const IdmapData& data ATTRIBUTE_UNUSED) { +} + +void RawPrintVisitor::visit(const IdmapData::Header& header) { + print(static_cast<uint16_t>(header.GetTargetPackageId()), "target package id"); + print(header.GetTypeCount(), "type count"); + last_seen_package_id_ = header.GetTargetPackageId(); +} + +void RawPrintVisitor::visit(const IdmapData::TypeEntry& te) { + const bool target_package_loaded = !target_am_.GetApkAssets().empty(); + + print(static_cast<uint16_t>(te.GetTargetTypeId()), "target type"); + print(static_cast<uint16_t>(te.GetOverlayTypeId()), "overlay type"); + print(static_cast<uint16_t>(te.GetEntryCount()), "entry count"); + print(static_cast<uint16_t>(te.GetEntryOffset()), "entry offset"); + + for (uint16_t i = 0; i < te.GetEntryCount(); i++) { + const EntryId entry = te.GetEntry(i); + if (entry == kNoEntry) { + print(kPadding, "no entry"); + } else { + const ResourceId target_resid = + RESID(last_seen_package_id_, te.GetTargetTypeId(), te.GetEntryOffset() + i); + const ResourceId overlay_resid = RESID(last_seen_package_id_, te.GetOverlayTypeId(), entry); + bool lookup_ok = false; + std::string name; + if (target_package_loaded) { + std::tie(lookup_ok, name) = utils::ResToTypeEntryName(target_am_, target_resid); + } + if (lookup_ok) { + print(static_cast<uint32_t>(entry), "0x%08x -> 0x%08x %s", target_resid, overlay_resid, + name.c_str()); + } else { + print(static_cast<uint32_t>(entry), "0x%08x -> 0x%08x", target_resid, overlay_resid); + } + } + } +} + +void RawPrintVisitor::print(uint16_t value, const char* fmt, ...) { + va_list ap; + va_start(ap, fmt); + std::string comment; + base::StringAppendV(&comment, fmt, ap); + va_end(ap); + + stream_ << base::StringPrintf("%08zx: %04x", offset_, value) << " " << comment << std::endl; + + offset_ += sizeof(uint16_t); +} + +void RawPrintVisitor::print(uint32_t value, const char* fmt, ...) { + va_list ap; + va_start(ap, fmt); + std::string comment; + base::StringAppendV(&comment, fmt, ap); + va_end(ap); + + stream_ << base::StringPrintf("%08zx: %08x", offset_, value) << " " << comment << std::endl; + + offset_ += sizeof(uint32_t); +} + +void RawPrintVisitor::print(const std::string& value, const char* fmt, ...) { + va_list ap; + va_start(ap, fmt); + std::string comment; + base::StringAppendV(&comment, fmt, ap); + va_end(ap); + + stream_ << base::StringPrintf("%08zx: ", offset_) << "........ " << comment << ": " << value + << std::endl; + + offset_ += kIdmapStringLength; +} + +} // namespace idmap2 +} // namespace android diff --git a/cmds/idmap2/libidmap2/ResourceUtils.cpp b/cmds/idmap2/libidmap2/ResourceUtils.cpp new file mode 100644 index 000000000000..e98f843931c8 --- /dev/null +++ b/cmds/idmap2/libidmap2/ResourceUtils.cpp @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <string> +#include <utility> + +#include "androidfw/StringPiece.h" +#include "androidfw/Util.h" + +#include "idmap2/ResourceUtils.h" + +using android::StringPiece16; +using android::util::Utf16ToUtf8; + +namespace android { +namespace idmap2 { +namespace utils { + +std::pair<bool, std::string> WARN_UNUSED ResToTypeEntryName(const AssetManager2& am, + ResourceId resid) { + AssetManager2::ResourceName name; + if (!am.GetResourceName(resid, &name)) { + return std::make_pair(false, ""); + } + std::string out; + if (name.type != nullptr) { + out.append(name.type, name.type_len); + } else { + out += Utf16ToUtf8(StringPiece16(name.type16, name.type_len)); + } + out.append("/"); + if (name.entry != nullptr) { + out.append(name.entry, name.entry_len); + } else { + out += Utf16ToUtf8(StringPiece16(name.entry16, name.entry_len)); + } + return std::make_pair(true, out); +} + +} // namespace utils +} // namespace idmap2 +} // namespace android diff --git a/cmds/idmap2/libidmap2/Xml.cpp b/cmds/idmap2/libidmap2/Xml.cpp new file mode 100644 index 000000000000..5543722ce4fe --- /dev/null +++ b/cmds/idmap2/libidmap2/Xml.cpp @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <map> +#include <memory> +#include <string> +#include <utility> + +#include "idmap2/Xml.h" + +namespace android { +namespace idmap2 { + +std::unique_ptr<const Xml> Xml::Create(const uint8_t* data, size_t size, bool copyData) { + std::unique_ptr<Xml> xml(new Xml()); + if (xml->xml_.setTo(data, size, copyData) != NO_ERROR) { + return nullptr; + } + return xml; +} + +std::unique_ptr<std::map<std::string, std::string>> Xml::FindTag(const std::string& name) const { + const String16 tag_to_find(name.c_str(), name.size()); + xml_.restart(); + ResXMLParser::event_code_t type; + do { + type = xml_.next(); + if (type == ResXMLParser::START_TAG) { + size_t len; + const String16 tag(xml_.getElementName(&len)); + if (tag == tag_to_find) { + std::unique_ptr<std::map<std::string, std::string>> map( + new std::map<std::string, std::string>()); + for (size_t i = 0; i < xml_.getAttributeCount(); i++) { + const String16 key16(xml_.getAttributeName(i, &len)); + std::string key = String8(key16).c_str(); + + std::string value; + switch (xml_.getAttributeDataType(i)) { + case Res_value::TYPE_STRING: { + const String16 value16(xml_.getAttributeStringValue(i, &len)); + value = String8(value16).c_str(); + } break; + case Res_value::TYPE_INT_DEC: + case Res_value::TYPE_INT_HEX: + case Res_value::TYPE_INT_BOOLEAN: { + Res_value resValue; + xml_.getAttributeValue(i, &resValue); + value = std::to_string(resValue.data); + } break; + default: + return nullptr; + } + + map->emplace(std::make_pair(key, value)); + } + return map; + } + } + } while (type != ResXMLParser::BAD_DOCUMENT && type != ResXMLParser::END_DOCUMENT); + return nullptr; +} + +Xml::~Xml() { + xml_.uninit(); +} + +} // namespace idmap2 +} // namespace android diff --git a/cmds/idmap2/libidmap2/ZipFile.cpp b/cmds/idmap2/libidmap2/ZipFile.cpp new file mode 100644 index 000000000000..3f2079a380d6 --- /dev/null +++ b/cmds/idmap2/libidmap2/ZipFile.cpp @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <memory> +#include <string> +#include <utility> + +#include "idmap2/ZipFile.h" + +namespace android { +namespace idmap2 { + +std::unique_ptr<MemoryChunk> MemoryChunk::Allocate(size_t size) { + void* ptr = ::operator new(sizeof(MemoryChunk) + size); + std::unique_ptr<MemoryChunk> chunk(reinterpret_cast<MemoryChunk*>(ptr)); + chunk->size = size; + return chunk; +} + +std::unique_ptr<const ZipFile> ZipFile::Open(const std::string& path) { + ::ZipArchiveHandle handle; + int32_t status = ::OpenArchive(path.c_str(), &handle); + if (status != 0) { + return nullptr; + } + return std::unique_ptr<ZipFile>(new ZipFile(handle)); +} + +ZipFile::~ZipFile() { + ::CloseArchive(handle_); +} + +std::unique_ptr<const MemoryChunk> ZipFile::Uncompress(const std::string& entryPath) const { + ::ZipEntry entry; + int32_t status = ::FindEntry(handle_, ::ZipString(entryPath.c_str()), &entry); + if (status != 0) { + return nullptr; + } + std::unique_ptr<MemoryChunk> chunk = MemoryChunk::Allocate(entry.uncompressed_length); + status = ::ExtractToMemory(handle_, &entry, chunk->buf, chunk->size); + if (status != 0) { + return nullptr; + } + return chunk; +} + +std::pair<bool, uint32_t> ZipFile::Crc(const std::string& entryPath) const { + ::ZipEntry entry; + int32_t status = ::FindEntry(handle_, ::ZipString(entryPath.c_str()), &entry); + return std::make_pair(status == 0, entry.crc32); +} + +} // namespace idmap2 +} // namespace android diff --git a/cmds/idmap2/static-checks.sh b/cmds/idmap2/static-checks.sh new file mode 100755 index 000000000000..560ccb692bb1 --- /dev/null +++ b/cmds/idmap2/static-checks.sh @@ -0,0 +1,121 @@ +#!/bin/bash +# +# Copyright (C) 2018 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +function _log() +{ + echo -e "$*" >&2 +} + +function _eval() +{ + local label="$1" + local cmd="$2" + local red="\e[31m" + local green="\e[32m" + local reset="\e[0m" + + _log "${green}[ RUN ]${reset} ${label}" + local output="$(eval "$cmd")" + if [[ -z "${output}" ]]; then + _log "${green}[ OK ]${reset} ${label}" + return 0 + else + echo "${output}" + _log "${red}[ FAILED ]${reset} ${label}" + errors=$((errors + 1)) + return 1 + fi +} + +function _clang_format() +{ + local path + local errors=0 + + for path in $cpp_files; do + local output="$(clang-format -style=file "$path" | diff $path -)" + if [[ "$output" ]]; then + echo "$path" + echo "$output" + errors=1 + fi + done + return $errors +} + +function _bpfmt() +{ + local output="$(bpfmt -s -d $bp_files)" + if [[ "$output" ]]; then + echo "$output" + return 1 + fi + return 0 +} + +function _cpplint() +{ + local cpplint="${ANDROID_BUILD_TOP}/tools/repohooks/tools/cpplint.py" + $cpplint --quiet $cpp_files +} + +function _parse_args() +{ + local opts + + opts="$(getopt -o cfh --long check,fix,help -- "$@")" + if [[ $? -ne 0 ]]; then + exit 1 + fi + eval set -- "$opts" + while true; do + case "$1" in + -c|--check) opt_mode="check"; shift ;; + -f|--fix) opt_mode="fix"; shift ;; + -h|--help) opt_mode="help"; shift ;; + *) break ;; + esac + done +} + +errors=0 +script="$(readlink -f "$BASH_SOURCE")" +prefix="$(dirname "$script")" +cpp_files="$(find "$prefix" -name '*.cpp' -or -name '*.h')" +bp_files="$(find "$prefix" -name 'Android.bp')" +opt_mode="check" + +_parse_args "$@" +if [[ $opt_mode == "check" ]]; then + _eval "clang-format" "_clang_format" + _eval "bpfmt" "_bpfmt" + _eval "cpplint" "_cpplint" + exit $errors +elif [[ $opt_mode == "fix" ]]; then + clang-format -style=file -i $cpp_files + bpfmt -s -w $bp_files + exit 0 +elif [[ $opt_mode == "help" ]]; then + echo "Run static analysis tools such as clang-format and cpplint on the idmap2" + echo "module. Optionally fix some of the issues found (--fix). Intended to be run" + echo "before merging any changes." + echo + echo "usage: $(basename $script) [--check|--fix|--help]" + exit 0 +else + exit 1 +fi diff --git a/cmds/idmap2/tests/BinaryStreamVisitorTests.cpp b/cmds/idmap2/tests/BinaryStreamVisitorTests.cpp new file mode 100644 index 000000000000..8b552dcc1265 --- /dev/null +++ b/cmds/idmap2/tests/BinaryStreamVisitorTests.cpp @@ -0,0 +1,129 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <memory> +#include <sstream> +#include <string> + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +#include "androidfw/ApkAssets.h" +#include "androidfw/Idmap.h" + +#include "idmap2/BinaryStreamVisitor.h" +#include "idmap2/Idmap.h" + +#include "TestHelpers.h" + +using ::testing::IsNull; +using ::testing::NotNull; + +namespace android { +namespace idmap2 { + +TEST(BinaryStreamVisitorTests, CreateBinaryStreamViaBinaryStreamVisitor) { + std::string raw(reinterpret_cast<const char*>(idmap_raw_data), idmap_raw_data_len); + std::istringstream raw_stream(raw); + + std::stringstream error; + std::unique_ptr<const Idmap> idmap1 = Idmap::FromBinaryStream(raw_stream, error); + ASSERT_THAT(idmap1, NotNull()); + + std::stringstream stream; + BinaryStreamVisitor visitor(stream); + idmap1->accept(&visitor); + + std::unique_ptr<const Idmap> idmap2 = Idmap::FromBinaryStream(stream, error); + ASSERT_THAT(idmap2, NotNull()); + + ASSERT_EQ(idmap1->GetHeader()->GetTargetCrc(), idmap2->GetHeader()->GetTargetCrc()); + ASSERT_EQ(idmap1->GetHeader()->GetTargetPath(), idmap2->GetHeader()->GetTargetPath()); + ASSERT_EQ(idmap1->GetData().size(), 1u); + ASSERT_EQ(idmap1->GetData().size(), idmap2->GetData().size()); + + const auto& data1 = idmap1->GetData()[0]; + const auto& data2 = idmap2->GetData()[0]; + + ASSERT_EQ(data1->GetHeader()->GetTargetPackageId(), data2->GetHeader()->GetTargetPackageId()); + ASSERT_EQ(data1->GetTypeEntries().size(), 2u); + ASSERT_EQ(data1->GetTypeEntries().size(), data2->GetTypeEntries().size()); + ASSERT_EQ(data1->GetTypeEntries()[0]->GetEntry(0), data2->GetTypeEntries()[0]->GetEntry(0)); + ASSERT_EQ(data1->GetTypeEntries()[0]->GetEntry(1), data2->GetTypeEntries()[0]->GetEntry(1)); + ASSERT_EQ(data1->GetTypeEntries()[0]->GetEntry(2), data2->GetTypeEntries()[0]->GetEntry(2)); + ASSERT_EQ(data1->GetTypeEntries()[1]->GetEntry(0), data2->GetTypeEntries()[1]->GetEntry(0)); + ASSERT_EQ(data1->GetTypeEntries()[1]->GetEntry(1), data2->GetTypeEntries()[1]->GetEntry(1)); + ASSERT_EQ(data1->GetTypeEntries()[1]->GetEntry(2), data2->GetTypeEntries()[1]->GetEntry(2)); +} + +TEST(BinaryStreamVisitorTests, CreateIdmapFromApkAssetsInteropWithLoadedIdmap) { + const std::string target_apk_path(GetTestDataPath() + "/target/target.apk"); + std::unique_ptr<const ApkAssets> target_apk = ApkAssets::Load(target_apk_path); + ASSERT_THAT(target_apk, NotNull()); + + const std::string overlay_apk_path(GetTestDataPath() + "/overlay/overlay.apk"); + std::unique_ptr<const ApkAssets> overlay_apk = ApkAssets::Load(overlay_apk_path); + ASSERT_THAT(overlay_apk, NotNull()); + + std::stringstream error; + std::unique_ptr<const Idmap> idmap = + Idmap::FromApkAssets(target_apk_path, *target_apk, overlay_apk_path, *overlay_apk, error); + ASSERT_THAT(idmap, NotNull()); + + std::stringstream stream; + BinaryStreamVisitor visitor(stream); + idmap->accept(&visitor); + const std::string str = stream.str(); + const StringPiece data(str); + std::unique_ptr<const LoadedIdmap> loaded_idmap = LoadedIdmap::Load(data); + ASSERT_THAT(loaded_idmap, NotNull()); + ASSERT_EQ(loaded_idmap->TargetPackageId(), 0x7f); + + const IdmapEntry_header* header = loaded_idmap->GetEntryMapForType(0x01); + ASSERT_THAT(header, NotNull()); + + EntryId entry; + bool success = LoadedIdmap::Lookup(header, 0x0000, &entry); + ASSERT_TRUE(success); + ASSERT_EQ(entry, 0x0000); + + header = loaded_idmap->GetEntryMapForType(0x02); + ASSERT_THAT(header, NotNull()); + + success = LoadedIdmap::Lookup(header, 0x0002, &entry); + ASSERT_FALSE(success); + + success = LoadedIdmap::Lookup(header, 0x0003, &entry); + ASSERT_TRUE(success); + ASSERT_EQ(entry, 0x0000); + + success = LoadedIdmap::Lookup(header, 0x0004, &entry); + ASSERT_FALSE(success); + + success = LoadedIdmap::Lookup(header, 0x0005, &entry); + ASSERT_TRUE(success); + ASSERT_EQ(entry, 0x0001); + + success = LoadedIdmap::Lookup(header, 0x0006, &entry); + ASSERT_TRUE(success); + ASSERT_EQ(entry, 0x0002); + + success = LoadedIdmap::Lookup(header, 0x0007, &entry); + ASSERT_FALSE(success); +} + +} // namespace idmap2 +} // namespace android diff --git a/cmds/idmap2/tests/CommandLineOptionsTests.cpp b/cmds/idmap2/tests/CommandLineOptionsTests.cpp new file mode 100644 index 000000000000..b04b25660ee4 --- /dev/null +++ b/cmds/idmap2/tests/CommandLineOptionsTests.cpp @@ -0,0 +1,192 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <sys/mman.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <unistd.h> + +#include <fstream> +#include <memory> +#include <sstream> +#include <string> +#include <vector> + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +#include "android-base/file.h" +#include "androidfw/ApkAssets.h" +#include "androidfw/Idmap.h" +#include "androidfw/LoadedArsc.h" + +#include "idmap2/CommandLineOptions.h" +#include "idmap2/Idmap.h" + +#include "TestHelpers.h" + +using ::testing::NotNull; + +namespace android { +namespace idmap2 { + +TEST(CommandLineOptionsTests, Flag) { + bool foo = true, bar = false; + + CommandLineOptions opts = + CommandLineOptions("test").OptionalFlag("--foo", "", &foo).OptionalFlag("--bar", "", &bar); + + std::ostream fakeStdErr(nullptr); + bool success = opts.Parse({"--foo", "--bar"}, fakeStdErr); + ASSERT_TRUE(success); + ASSERT_TRUE(foo); + ASSERT_TRUE(bar); + + foo = bar = false; + success = opts.Parse({"--foo"}, fakeStdErr); + ASSERT_TRUE(success); + ASSERT_TRUE(foo); + ASSERT_FALSE(bar); +} + +TEST(CommandLineOptionsTests, MandatoryOption) { + std::string foo, bar; + CommandLineOptions opts = CommandLineOptions("test") + .MandatoryOption("--foo", "", &foo) + .MandatoryOption("--bar", "", &bar); + std::ostream fakeStdErr(nullptr); + bool success = opts.Parse({"--foo", "FOO", "--bar", "BAR"}, fakeStdErr); + ASSERT_TRUE(success); + ASSERT_EQ(foo, "FOO"); + ASSERT_EQ(bar, "BAR"); + + success = opts.Parse({"--foo"}, fakeStdErr); + ASSERT_FALSE(success); +} + +TEST(CommandLineOptionsTests, MandatoryOptionMultipleArgsButExpectedOnce) { + std::string foo; + CommandLineOptions opts = CommandLineOptions("test").MandatoryOption("--foo", "", &foo); + std::ostream fakeStdErr(nullptr); + bool success = opts.Parse({"--foo", "FIRST", "--foo", "SECOND"}, fakeStdErr); + ASSERT_TRUE(success); + ASSERT_EQ(foo, "SECOND"); +} + +TEST(CommandLineOptionsTests, MandatoryOptionMultipleArgsAndExpectedOnceOrMore) { + std::vector<std::string> args; + CommandLineOptions opts = CommandLineOptions("test").MandatoryOption("--foo", "", &args); + std::ostream fakeStdErr(nullptr); + bool success = opts.Parse({"--foo", "FOO", "--foo", "BAR"}, fakeStdErr); + ASSERT_TRUE(success); + ASSERT_EQ(args.size(), 2u); + ASSERT_EQ(args[0], "FOO"); + ASSERT_EQ(args[1], "BAR"); +} + +TEST(CommandLineOptionsTests, OptionalOption) { + std::string foo, bar; + CommandLineOptions opts = CommandLineOptions("test") + .OptionalOption("--foo", "", &foo) + .OptionalOption("--bar", "", &bar); + std::ostream fakeStdErr(nullptr); + bool success = opts.Parse({"--foo", "FOO", "--bar", "BAR"}, fakeStdErr); + ASSERT_TRUE(success); + ASSERT_EQ(foo, "FOO"); + ASSERT_EQ(bar, "BAR"); + + success = opts.Parse({"--foo", "BAZ"}, fakeStdErr); + ASSERT_TRUE(success); + ASSERT_EQ(foo, "BAZ"); + + success = opts.Parse({"--foo"}, fakeStdErr); + ASSERT_FALSE(success); + + success = opts.Parse({"--foo", "--bar", "BAR"}, fakeStdErr); + ASSERT_FALSE(success); + + success = opts.Parse({"--foo", "FOO", "--bar"}, fakeStdErr); + ASSERT_FALSE(success); +} + +TEST(CommandLineOptionsTests, CornerCases) { + std::string foo, bar; + bool baz = false; + CommandLineOptions opts = CommandLineOptions("test") + .MandatoryOption("--foo", "", &foo) + .OptionalFlag("--baz", "", &baz) + .OptionalOption("--bar", "", &bar); + std::ostream fakeStdErr(nullptr); + bool success = opts.Parse({"--unexpected"}, fakeStdErr); + ASSERT_FALSE(success); + + success = opts.Parse({"--bar", "BAR"}, fakeStdErr); + ASSERT_FALSE(success); + + success = opts.Parse({"--baz", "--foo", "FOO"}, fakeStdErr); + ASSERT_TRUE(success); + ASSERT_TRUE(baz); + ASSERT_EQ(foo, "FOO"); +} + +TEST(CommandLineOptionsTests, ConvertArgvToVector) { + const char* argv[] = { + "program-name", + "--foo", + "FOO", + nullptr, + }; + std::unique_ptr<std::vector<std::string>> v = CommandLineOptions::ConvertArgvToVector(3, argv); + ASSERT_EQ(v->size(), 2ul); + ASSERT_EQ((*v)[0], "--foo"); + ASSERT_EQ((*v)[1], "FOO"); +} + +TEST(CommandLineOptionsTests, ConvertArgvToVectorNoArgs) { + const char* argv[] = { + "program-name", + nullptr, + }; + std::unique_ptr<std::vector<std::string>> v = CommandLineOptions::ConvertArgvToVector(1, argv); + ASSERT_EQ(v->size(), 0ul); +} + +TEST(CommandLineOptionsTests, Usage) { + std::string arg1, arg2, arg3, arg4; + bool arg5 = false, arg6 = false; + std::vector<std::string> arg7; + CommandLineOptions opts = CommandLineOptions("test") + .MandatoryOption("--aa", "description-aa", &arg1) + .OptionalFlag("--bb", "description-bb", &arg5) + .OptionalOption("--cc", "description-cc", &arg2) + .OptionalOption("--dd", "description-dd", &arg3) + .MandatoryOption("--ee", "description-ee", &arg4) + .OptionalFlag("--ff", "description-ff", &arg6) + .MandatoryOption("--gg", "description-gg", &arg7); + std::stringstream stream; + opts.Usage(stream); + const std::string s = stream.str(); + ASSERT_NE(s.find("usage: test --aa arg [--bb] [--cc arg] [--dd arg] --ee arg [--ff] --gg arg " + "[--gg arg [..]]"), + std::string::npos); + ASSERT_NE(s.find("--aa arg description-aa"), std::string::npos); + ASSERT_NE(s.find("--ff description-ff"), std::string::npos); + ASSERT_NE(s.find("--gg arg description-gg (can be provided multiple times)"), + std::string::npos); +} + +} // namespace idmap2 +} // namespace android diff --git a/cmds/idmap2/tests/FileUtilsTests.cpp b/cmds/idmap2/tests/FileUtilsTests.cpp new file mode 100644 index 000000000000..0c6439ab8c0c --- /dev/null +++ b/cmds/idmap2/tests/FileUtilsTests.cpp @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <dirent.h> +#include <set> +#include <string> + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +#include "android-base/macros.h" + +#include "idmap2/FileUtils.h" + +#include "TestHelpers.h" + +using ::testing::NotNull; + +namespace android { +namespace idmap2 { +namespace utils { + +TEST(FileUtilsTests, FindFilesFindEverythingNonRecursive) { + const auto& root = GetTestDataPath(); + auto v = utils::FindFiles(root, false, + [](unsigned char type ATTRIBUTE_UNUSED, + const std::string& path ATTRIBUTE_UNUSED) -> bool { return true; }); + ASSERT_THAT(v, NotNull()); + ASSERT_EQ(v->size(), 4u); + ASSERT_EQ( + std::set<std::string>(v->begin(), v->end()), + std::set<std::string>({root + "/.", root + "/..", root + "/overlay", root + "/target"})); +} + +TEST(FileUtilsTests, FindFilesFindApkFilesRecursive) { + const auto& root = GetTestDataPath(); + auto v = utils::FindFiles(root, true, [](unsigned char type, const std::string& path) -> bool { + return type == DT_REG && path.size() > 4 && !path.compare(path.size() - 4, 4, ".apk"); + }); + ASSERT_THAT(v, NotNull()); + ASSERT_EQ(v->size(), 4u); + ASSERT_EQ(std::set<std::string>(v->begin(), v->end()), + std::set<std::string>({root + "/target/target.apk", root + "/overlay/overlay.apk", + root + "/overlay/overlay-static-1.apk", + root + "/overlay/overlay-static-2.apk"})); +} + +TEST(FileUtilsTests, ReadFile) { + int pipefd[2]; + ASSERT_EQ(pipe(pipefd), 0); + + ASSERT_EQ(write(pipefd[1], "foobar", 6), 6); + close(pipefd[1]); + + auto data = ReadFile(pipefd[0]); + ASSERT_THAT(data, NotNull()); + ASSERT_EQ(*data, "foobar"); + close(pipefd[0]); +} + +} // namespace utils +} // namespace idmap2 +} // namespace android diff --git a/cmds/idmap2/tests/Idmap2BinaryTests.cpp b/cmds/idmap2/tests/Idmap2BinaryTests.cpp new file mode 100644 index 000000000000..5c4e8576985b --- /dev/null +++ b/cmds/idmap2/tests/Idmap2BinaryTests.cpp @@ -0,0 +1,313 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * The tests in this file operate on a higher level than the tests in the other + * files. Here, all tests execute the idmap2 binary and only depend on + * libidmap2 to verify the output of idmap2. + */ +#include <fcntl.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <unistd.h> + +#include <cerrno> +#include <cstdlib> +#include <cstring> // strerror +#include <fstream> +#include <memory> +#include <sstream> +#include <string> +#include <vector> + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +#include "androidfw/PosixUtils.h" +#include "idmap2/FileUtils.h" +#include "idmap2/Idmap.h" + +#include "TestHelpers.h" + +using ::android::util::ExecuteBinary; +using ::testing::NotNull; + +namespace android { +namespace idmap2 { + +class Idmap2BinaryTests : public Idmap2Tests {}; + +static void AssertIdmap(const Idmap& idmap, const std::string& target_apk_path, + const std::string& overlay_apk_path) { + // check that the idmap file looks reasonable (IdmapTests is responsible for + // more in-depth verification) + ASSERT_EQ(idmap.GetHeader()->GetMagic(), kIdmapMagic); + ASSERT_EQ(idmap.GetHeader()->GetVersion(), kIdmapCurrentVersion); + ASSERT_EQ(idmap.GetHeader()->GetTargetPath(), target_apk_path); + ASSERT_EQ(idmap.GetHeader()->GetOverlayPath(), overlay_apk_path); + ASSERT_EQ(idmap.GetData().size(), 1u); +} + +#define ASSERT_IDMAP(idmap_ref, target_apk_path, overlay_apk_path) \ + do { \ + ASSERT_NO_FATAL_FAILURE(AssertIdmap(idmap_ref, target_apk_path, overlay_apk_path)); \ + } while (0) + +TEST_F(Idmap2BinaryTests, Create) { + // clang-format off + auto result = ExecuteBinary({"idmap2", + "create", + "--target-apk-path", GetTargetApkPath(), + "--overlay-apk-path", GetOverlayApkPath(), + "--idmap-path", GetIdmapPath()}); + // clang-format on + ASSERT_THAT(result, NotNull()); + ASSERT_EQ(result->status, EXIT_SUCCESS) << result->stderr; + + struct stat st; + ASSERT_EQ(stat(GetIdmapPath().c_str(), &st), 0); + + std::stringstream error; + std::ifstream fin(GetIdmapPath()); + std::unique_ptr<const Idmap> idmap = Idmap::FromBinaryStream(fin, error); + fin.close(); + + ASSERT_THAT(idmap, NotNull()); + ASSERT_IDMAP(*idmap, GetTargetApkPath(), GetOverlayApkPath()); + + unlink(GetIdmapPath().c_str()); +} + +TEST_F(Idmap2BinaryTests, Dump) { + // clang-format off + auto result = ExecuteBinary({"idmap2", + "create", + "--target-apk-path", GetTargetApkPath(), + "--overlay-apk-path", GetOverlayApkPath(), + "--idmap-path", GetIdmapPath()}); + // clang-format on + ASSERT_THAT(result, NotNull()); + ASSERT_EQ(result->status, EXIT_SUCCESS) << result->stderr; + + // clang-format off + result = ExecuteBinary({"idmap2", + "dump", + "--idmap-path", GetIdmapPath()}); + // clang-format on + ASSERT_THAT(result, NotNull()); + ASSERT_EQ(result->status, EXIT_SUCCESS) << result->stderr; + ASSERT_NE(result->stdout.find("0x7f010000 -> 0x7f010000 integer/int1"), std::string::npos); + ASSERT_NE(result->stdout.find("0x7f020003 -> 0x7f020000 string/str1"), std::string::npos); + ASSERT_NE(result->stdout.find("0x7f020005 -> 0x7f020001 string/str3"), std::string::npos); + ASSERT_EQ(result->stdout.find("00000210: 007f target package id"), std::string::npos); + + // clang-format off + result = ExecuteBinary({"idmap2", + "dump", + "--verbose", + "--idmap-path", GetIdmapPath()}); + // clang-format on + ASSERT_THAT(result, NotNull()); + ASSERT_EQ(result->status, EXIT_SUCCESS) << result->stderr; + ASSERT_NE(result->stdout.find("00000000: 504d4449 magic"), std::string::npos); + ASSERT_NE(result->stdout.find("00000210: 007f target package id"), std::string::npos); + + // clang-format off + result = ExecuteBinary({"idmap2", + "dump", + "--verbose", + "--idmap-path", GetTestDataPath() + "/DOES-NOT-EXIST"}); + // clang-format on + ASSERT_THAT(result, NotNull()); + ASSERT_NE(result->status, EXIT_SUCCESS); + + unlink(GetIdmapPath().c_str()); +} + +TEST_F(Idmap2BinaryTests, Scan) { + const std::string overlay_static_1_apk_path = GetTestDataPath() + "/overlay/overlay-static-1.apk"; + const std::string overlay_static_2_apk_path = GetTestDataPath() + "/overlay/overlay-static-2.apk"; + const std::string idmap_static_1_path = + Idmap::CanonicalIdmapPathFor(GetTempDirPath(), overlay_static_1_apk_path); + const std::string idmap_static_2_path = + Idmap::CanonicalIdmapPathFor(GetTempDirPath(), overlay_static_2_apk_path); + + // single input directory, recursive + // clang-format off + auto result = ExecuteBinary({"idmap2", + "scan", + "--input-directory", GetTestDataPath(), + "--recursive", + "--target-package-name", "test.target", + "--target-apk-path", GetTargetApkPath(), + "--output-directory", GetTempDirPath()}); + // clang-format on + ASSERT_THAT(result, NotNull()); + ASSERT_EQ(result->status, EXIT_SUCCESS) << result->stderr; + std::stringstream expected; + expected << idmap_static_1_path << std::endl; + expected << idmap_static_2_path << std::endl; + ASSERT_EQ(result->stdout, expected.str()); + + std::stringstream error; + auto idmap_static_1_raw_string = utils::ReadFile(idmap_static_1_path); + auto idmap_static_1_raw_stream = std::istringstream(*idmap_static_1_raw_string); + auto idmap_static_1 = Idmap::FromBinaryStream(idmap_static_1_raw_stream, error); + ASSERT_THAT(idmap_static_1, NotNull()); + ASSERT_IDMAP(*idmap_static_1, GetTargetApkPath(), overlay_static_1_apk_path); + + auto idmap_static_2_raw_string = utils::ReadFile(idmap_static_2_path); + auto idmap_static_2_raw_stream = std::istringstream(*idmap_static_2_raw_string); + auto idmap_static_2 = Idmap::FromBinaryStream(idmap_static_2_raw_stream, error); + ASSERT_THAT(idmap_static_2, NotNull()); + ASSERT_IDMAP(*idmap_static_2, GetTargetApkPath(), overlay_static_2_apk_path); + + unlink(idmap_static_2_path.c_str()); + unlink(idmap_static_1_path.c_str()); + + // multiple input directories, non-recursive + // clang-format off + result = ExecuteBinary({"idmap2", + "scan", + "--input-directory", GetTestDataPath() + "/target", + "--input-directory", GetTestDataPath() + "/overlay", + "--target-package-name", "test.target", + "--target-apk-path", GetTargetApkPath(), + "--output-directory", GetTempDirPath()}); + // clang-format on + ASSERT_THAT(result, NotNull()); + ASSERT_EQ(result->status, EXIT_SUCCESS) << result->stderr; + ASSERT_EQ(result->stdout, expected.str()); + unlink(idmap_static_2_path.c_str()); + unlink(idmap_static_1_path.c_str()); + + // the same input directory given twice, but no duplicate entries + // clang-format off + result = ExecuteBinary({"idmap2", + "scan", + "--input-directory", GetTestDataPath(), + "--input-directory", GetTestDataPath(), + "--recursive", + "--target-package-name", "test.target", + "--target-apk-path", GetTargetApkPath(), + "--output-directory", GetTempDirPath()}); + // clang-format on + ASSERT_THAT(result, NotNull()); + ASSERT_EQ(result->status, EXIT_SUCCESS) << result->stderr; + ASSERT_EQ(result->stdout, expected.str()); + unlink(idmap_static_2_path.c_str()); + unlink(idmap_static_1_path.c_str()); + + // no APKs in input-directory: ok, but no output + // clang-format off + result = ExecuteBinary({"idmap2", + "scan", + "--input-directory", GetTempDirPath(), + "--target-package-name", "test.target", + "--target-apk-path", GetTargetApkPath(), + "--output-directory", GetTempDirPath()}); + // clang-format on + ASSERT_THAT(result, NotNull()); + ASSERT_EQ(result->status, EXIT_SUCCESS) << result->stderr; + ASSERT_EQ(result->stdout, ""); +} + +TEST_F(Idmap2BinaryTests, Lookup) { + // clang-format off + auto result = ExecuteBinary({"idmap2", + "create", + "--target-apk-path", GetTargetApkPath(), + "--overlay-apk-path", GetOverlayApkPath(), + "--idmap-path", GetIdmapPath()}); + // clang-format on + ASSERT_THAT(result, NotNull()); + ASSERT_EQ(result->status, EXIT_SUCCESS) << result->stderr; + + // clang-format off + result = ExecuteBinary({"idmap2", + "lookup", + "--idmap-path", GetIdmapPath(), + "--config", "", + "--resid", "0x7f020003"}); // string/str1 + // clang-format on + ASSERT_THAT(result, NotNull()); + ASSERT_EQ(result->status, EXIT_SUCCESS) << result->stderr; + ASSERT_NE(result->stdout.find("overlay-1"), std::string::npos); + ASSERT_EQ(result->stdout.find("overlay-1-sv"), std::string::npos); + + // clang-format off + result = ExecuteBinary({"idmap2", + "lookup", + "--idmap-path", GetIdmapPath(), + "--config", "", + "--resid", "test.target:string/str1"}); + // clang-format on + ASSERT_THAT(result, NotNull()); + ASSERT_EQ(result->status, EXIT_SUCCESS) << result->stderr; + ASSERT_NE(result->stdout.find("overlay-1"), std::string::npos); + ASSERT_EQ(result->stdout.find("overlay-1-sv"), std::string::npos); + + // clang-format off + result = ExecuteBinary({"idmap2", + "lookup", + "--idmap-path", GetIdmapPath(), + "--config", "sv", + "--resid", "test.target:string/str1"}); + // clang-format on + ASSERT_THAT(result, NotNull()); + ASSERT_EQ(result->status, EXIT_SUCCESS) << result->stderr; + ASSERT_NE(result->stdout.find("overlay-1-sv"), std::string::npos); + + unlink(GetIdmapPath().c_str()); +} + +TEST_F(Idmap2BinaryTests, InvalidCommandLineOptions) { + const std::string invalid_target_apk_path = GetTestDataPath() + "/DOES-NOT-EXIST"; + + // missing mandatory options + // clang-format off + auto result = ExecuteBinary({"idmap2", + "create"}); + // clang-format on + ASSERT_THAT(result, NotNull()); + ASSERT_NE(result->status, EXIT_SUCCESS); + + // missing argument to option + // clang-format off + result = ExecuteBinary({"idmap2", + "create", + "--target-apk-path", GetTargetApkPath(), + "--overlay-apk-path", GetOverlayApkPath(), + "--idmap-path"}); + // clang-format on + ASSERT_THAT(result, NotNull()); + ASSERT_NE(result->status, EXIT_SUCCESS); + + // invalid target apk path + // clang-format off + result = ExecuteBinary({"idmap2", + "create", + "--target-apk-path", invalid_target_apk_path, + "--overlay-apk-path", GetOverlayApkPath(), + "--idmap-path", GetIdmapPath()}); + // clang-format on + ASSERT_THAT(result, NotNull()); + ASSERT_NE(result->status, EXIT_SUCCESS); +} + +} // namespace idmap2 +} // namespace android diff --git a/cmds/idmap2/tests/IdmapTests.cpp b/cmds/idmap2/tests/IdmapTests.cpp new file mode 100644 index 000000000000..0379aa491682 --- /dev/null +++ b/cmds/idmap2/tests/IdmapTests.cpp @@ -0,0 +1,395 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <cstdio> // fclose + +#include <fstream> +#include <memory> +#include <sstream> +#include <string> +#include <vector> + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +#include "android-base/macros.h" +#include "androidfw/ApkAssets.h" + +#include "idmap2/BinaryStreamVisitor.h" +#include "idmap2/CommandLineOptions.h" +#include "idmap2/Idmap.h" + +#include "TestHelpers.h" + +using ::testing::IsNull; +using ::testing::NotNull; + +namespace android { +namespace idmap2 { + +TEST(IdmapTests, TestCanonicalIdmapPathFor) { + ASSERT_EQ(Idmap::CanonicalIdmapPathFor("/foo", "/vendor/overlay/bar.apk"), + "/foo/vendor@overlay@bar.apk@idmap"); +} + +TEST(IdmapTests, CreateIdmapHeaderFromBinaryStream) { + std::string raw(reinterpret_cast<const char*>(idmap_raw_data), idmap_raw_data_len); + std::istringstream stream(raw); + std::unique_ptr<const IdmapHeader> header = IdmapHeader::FromBinaryStream(stream); + ASSERT_THAT(header, NotNull()); + ASSERT_EQ(header->GetMagic(), 0x504d4449u); + ASSERT_EQ(header->GetVersion(), 0x01u); + ASSERT_EQ(header->GetTargetCrc(), 0x1234u); + ASSERT_EQ(header->GetOverlayCrc(), 0x5678u); + ASSERT_EQ(header->GetTargetPath().to_string(), "target.apk"); + ASSERT_EQ(header->GetOverlayPath().to_string(), "overlay.apk"); +} + +TEST(IdmapTests, FailToCreateIdmapHeaderFromBinaryStreamIfPathTooLong) { + std::string raw(reinterpret_cast<const char*>(idmap_raw_data), idmap_raw_data_len); + // overwrite the target path string, including the terminating null, with '.' + for (size_t i = 0x10; i < 0x110; i++) { + raw[i] = '.'; + } + std::istringstream stream(raw); + std::unique_ptr<const IdmapHeader> header = IdmapHeader::FromBinaryStream(stream); + ASSERT_THAT(header, IsNull()); +} + +TEST(IdmapTests, CreateIdmapDataHeaderFromBinaryStream) { + const size_t offset = 0x210; + std::string raw(reinterpret_cast<const char*>(idmap_raw_data + offset), + idmap_raw_data_len - offset); + std::istringstream stream(raw); + + std::unique_ptr<const IdmapData::Header> header = IdmapData::Header::FromBinaryStream(stream); + ASSERT_THAT(header, NotNull()); + ASSERT_EQ(header->GetTargetPackageId(), 0x7fu); + ASSERT_EQ(header->GetTypeCount(), 2u); +} + +TEST(IdmapTests, CreateIdmapDataResourceTypeFromBinaryStream) { + const size_t offset = 0x214; + std::string raw(reinterpret_cast<const char*>(idmap_raw_data + offset), + idmap_raw_data_len - offset); + std::istringstream stream(raw); + + std::unique_ptr<const IdmapData::TypeEntry> data = IdmapData::TypeEntry::FromBinaryStream(stream); + ASSERT_THAT(data, NotNull()); + ASSERT_EQ(data->GetTargetTypeId(), 0x02u); + ASSERT_EQ(data->GetOverlayTypeId(), 0x02u); + ASSERT_EQ(data->GetEntryCount(), 1u); + ASSERT_EQ(data->GetEntryOffset(), 0u); + ASSERT_EQ(data->GetEntry(0), 0u); +} + +TEST(IdmapTests, CreateIdmapDataFromBinaryStream) { + const size_t offset = 0x210; + std::string raw(reinterpret_cast<const char*>(idmap_raw_data + offset), + idmap_raw_data_len - offset); + std::istringstream stream(raw); + + std::unique_ptr<const IdmapData> data = IdmapData::FromBinaryStream(stream); + ASSERT_THAT(data, NotNull()); + ASSERT_EQ(data->GetHeader()->GetTargetPackageId(), 0x7fu); + ASSERT_EQ(data->GetHeader()->GetTypeCount(), 2u); + const std::vector<std::unique_ptr<const IdmapData::TypeEntry>>& types = data->GetTypeEntries(); + ASSERT_EQ(types.size(), 2u); + + ASSERT_EQ(types[0]->GetTargetTypeId(), 0x02u); + ASSERT_EQ(types[0]->GetOverlayTypeId(), 0x02u); + ASSERT_EQ(types[0]->GetEntryCount(), 1u); + ASSERT_EQ(types[0]->GetEntryOffset(), 0u); + ASSERT_EQ(types[0]->GetEntry(0), 0x0000u); + + ASSERT_EQ(types[1]->GetTargetTypeId(), 0x03u); + ASSERT_EQ(types[1]->GetOverlayTypeId(), 0x03u); + ASSERT_EQ(types[1]->GetEntryCount(), 3u); + ASSERT_EQ(types[1]->GetEntryOffset(), 3u); + ASSERT_EQ(types[1]->GetEntry(0), 0x0000u); + ASSERT_EQ(types[1]->GetEntry(1), kNoEntry); + ASSERT_EQ(types[1]->GetEntry(2), 0x0001u); +} + +TEST(IdmapTests, CreateIdmapFromBinaryStream) { + std::string raw(reinterpret_cast<const char*>(idmap_raw_data), idmap_raw_data_len); + std::istringstream stream(raw); + + std::stringstream error; + std::unique_ptr<const Idmap> idmap = Idmap::FromBinaryStream(stream, error); + ASSERT_THAT(idmap, NotNull()); + + ASSERT_THAT(idmap->GetHeader(), NotNull()); + ASSERT_EQ(idmap->GetHeader()->GetMagic(), 0x504d4449u); + ASSERT_EQ(idmap->GetHeader()->GetVersion(), 0x01u); + ASSERT_EQ(idmap->GetHeader()->GetTargetCrc(), 0x1234u); + ASSERT_EQ(idmap->GetHeader()->GetOverlayCrc(), 0x5678u); + ASSERT_EQ(idmap->GetHeader()->GetTargetPath().to_string(), "target.apk"); + ASSERT_EQ(idmap->GetHeader()->GetOverlayPath().to_string(), "overlay.apk"); + + const std::vector<std::unique_ptr<const IdmapData>>& dataBlocks = idmap->GetData(); + ASSERT_EQ(dataBlocks.size(), 1u); + + const std::unique_ptr<const IdmapData>& data = dataBlocks[0]; + ASSERT_EQ(data->GetHeader()->GetTargetPackageId(), 0x7fu); + ASSERT_EQ(data->GetHeader()->GetTypeCount(), 2u); + const std::vector<std::unique_ptr<const IdmapData::TypeEntry>>& types = data->GetTypeEntries(); + ASSERT_EQ(types.size(), 2u); + + ASSERT_EQ(types[0]->GetTargetTypeId(), 0x02u); + ASSERT_EQ(types[0]->GetOverlayTypeId(), 0x02u); + ASSERT_EQ(types[0]->GetEntryCount(), 1u); + ASSERT_EQ(types[0]->GetEntryOffset(), 0u); + ASSERT_EQ(types[0]->GetEntry(0), 0x0000u); + + ASSERT_EQ(types[1]->GetTargetTypeId(), 0x03u); + ASSERT_EQ(types[1]->GetOverlayTypeId(), 0x03u); + ASSERT_EQ(types[1]->GetEntryCount(), 3u); + ASSERT_EQ(types[1]->GetEntryOffset(), 3u); + ASSERT_EQ(types[1]->GetEntry(0), 0x0000u); + ASSERT_EQ(types[1]->GetEntry(1), kNoEntry); + ASSERT_EQ(types[1]->GetEntry(2), 0x0001u); +} + +TEST(IdmapTests, GracefullyFailToCreateIdmapFromCorruptBinaryStream) { + std::string raw(reinterpret_cast<const char*>(idmap_raw_data), + 10); // data too small + std::istringstream stream(raw); + + std::stringstream error; + std::unique_ptr<const Idmap> idmap = Idmap::FromBinaryStream(stream, error); + ASSERT_THAT(idmap, IsNull()); +} + +TEST(IdmapTests, CreateIdmapFromApkAssets) { + const std::string target_apk_path(GetTestDataPath() + "/target/target.apk"); + std::unique_ptr<const ApkAssets> target_apk = ApkAssets::Load(target_apk_path); + ASSERT_THAT(target_apk, NotNull()); + + const std::string overlay_apk_path(GetTestDataPath() + "/overlay/overlay.apk"); + std::unique_ptr<const ApkAssets> overlay_apk = ApkAssets::Load(overlay_apk_path); + ASSERT_THAT(overlay_apk, NotNull()); + + std::stringstream error; + std::unique_ptr<const Idmap> idmap = + Idmap::FromApkAssets(target_apk_path, *target_apk, overlay_apk_path, *overlay_apk, error); + ASSERT_THAT(idmap, NotNull()); + + ASSERT_THAT(idmap->GetHeader(), NotNull()); + ASSERT_EQ(idmap->GetHeader()->GetMagic(), 0x504d4449u); + ASSERT_EQ(idmap->GetHeader()->GetVersion(), 0x01u); + ASSERT_EQ(idmap->GetHeader()->GetTargetCrc(), 0xf5ad1d1d); + ASSERT_EQ(idmap->GetHeader()->GetOverlayCrc(), 0xd470336b); + ASSERT_EQ(idmap->GetHeader()->GetTargetPath().to_string(), target_apk_path); + ASSERT_EQ(idmap->GetHeader()->GetOverlayPath(), overlay_apk_path); + ASSERT_EQ(idmap->GetHeader()->GetOverlayPath(), overlay_apk_path); + + const std::vector<std::unique_ptr<const IdmapData>>& dataBlocks = idmap->GetData(); + ASSERT_EQ(dataBlocks.size(), 1u); + + const std::unique_ptr<const IdmapData>& data = dataBlocks[0]; + + ASSERT_EQ(data->GetHeader()->GetTargetPackageId(), 0x7fu); + ASSERT_EQ(data->GetHeader()->GetTypeCount(), 2u); + + const std::vector<std::unique_ptr<const IdmapData::TypeEntry>>& types = data->GetTypeEntries(); + ASSERT_EQ(types.size(), 2u); + + ASSERT_EQ(types[0]->GetTargetTypeId(), 0x01u); + ASSERT_EQ(types[0]->GetOverlayTypeId(), 0x01u); + ASSERT_EQ(types[0]->GetEntryCount(), 1u); + ASSERT_EQ(types[0]->GetEntryOffset(), 0u); + ASSERT_EQ(types[0]->GetEntry(0), 0x0000u); + + ASSERT_EQ(types[1]->GetTargetTypeId(), 0x02u); + ASSERT_EQ(types[1]->GetOverlayTypeId(), 0x02u); + ASSERT_EQ(types[1]->GetEntryCount(), 4u); + ASSERT_EQ(types[1]->GetEntryOffset(), 3u); + ASSERT_EQ(types[1]->GetEntry(0), 0x0000u); + ASSERT_EQ(types[1]->GetEntry(1), kNoEntry); + ASSERT_EQ(types[1]->GetEntry(2), 0x0001u); + ASSERT_EQ(types[1]->GetEntry(3), 0x0002u); +} + +TEST(IdmapTests, FailToCreateIdmapFromApkAssetsIfPathTooLong) { + std::string target_apk_path(GetTestDataPath()); + for (int i = 0; i < 32; i++) { + target_apk_path += "/target/../"; + } + target_apk_path += "/target/target.apk"; + ASSERT_GT(target_apk_path.size(), kIdmapStringLength); + std::unique_ptr<const ApkAssets> target_apk = ApkAssets::Load(target_apk_path); + ASSERT_THAT(target_apk, NotNull()); + + const std::string overlay_apk_path(GetTestDataPath() + "/overlay/overlay.apk"); + std::unique_ptr<const ApkAssets> overlay_apk = ApkAssets::Load(overlay_apk_path); + ASSERT_THAT(overlay_apk, NotNull()); + + std::stringstream error; + std::unique_ptr<const Idmap> idmap = + Idmap::FromApkAssets(target_apk_path, *target_apk, overlay_apk_path, *overlay_apk, error); + ASSERT_THAT(idmap, IsNull()); +} + +TEST(IdmapTests, IdmapHeaderIsUpToDate) { + fclose(stderr); // silence expected warnings from libandroidfw + + const std::string target_apk_path(GetTestDataPath() + "/target/target.apk"); + std::unique_ptr<const ApkAssets> target_apk = ApkAssets::Load(target_apk_path); + ASSERT_THAT(target_apk, NotNull()); + + const std::string overlay_apk_path(GetTestDataPath() + "/overlay/overlay.apk"); + std::unique_ptr<const ApkAssets> overlay_apk = ApkAssets::Load(overlay_apk_path); + ASSERT_THAT(overlay_apk, NotNull()); + + std::stringstream error; + std::unique_ptr<const Idmap> idmap = + Idmap::FromApkAssets(target_apk_path, *target_apk, overlay_apk_path, *overlay_apk, error); + ASSERT_THAT(idmap, NotNull()); + + std::stringstream stream; + BinaryStreamVisitor visitor(stream); + idmap->accept(&visitor); + + std::unique_ptr<const IdmapHeader> header = IdmapHeader::FromBinaryStream(stream); + ASSERT_THAT(header, NotNull()); + ASSERT_TRUE(header->IsUpToDate(error)) << error.str(); + + // magic: bytes (0x0, 0x03) + std::string bad_magic_string(stream.str()); + bad_magic_string[0x0] = '.'; + bad_magic_string[0x1] = '.'; + bad_magic_string[0x2] = '.'; + bad_magic_string[0x3] = '.'; + std::stringstream bad_magic_stream(bad_magic_string); + std::unique_ptr<const IdmapHeader> bad_magic_header = + IdmapHeader::FromBinaryStream(bad_magic_stream); + ASSERT_THAT(bad_magic_header, NotNull()); + ASSERT_NE(header->GetMagic(), bad_magic_header->GetMagic()); + ASSERT_FALSE(bad_magic_header->IsUpToDate(error)); + + // version: bytes (0x4, 0x07) + std::string bad_version_string(stream.str()); + bad_version_string[0x4] = '.'; + bad_version_string[0x5] = '.'; + bad_version_string[0x6] = '.'; + bad_version_string[0x7] = '.'; + std::stringstream bad_version_stream(bad_version_string); + std::unique_ptr<const IdmapHeader> bad_version_header = + IdmapHeader::FromBinaryStream(bad_version_stream); + ASSERT_THAT(bad_version_header, NotNull()); + ASSERT_NE(header->GetVersion(), bad_version_header->GetVersion()); + ASSERT_FALSE(bad_version_header->IsUpToDate(error)); + + // target crc: bytes (0x8, 0xb) + std::string bad_target_crc_string(stream.str()); + bad_target_crc_string[0x8] = '.'; + bad_target_crc_string[0x9] = '.'; + bad_target_crc_string[0xa] = '.'; + bad_target_crc_string[0xb] = '.'; + std::stringstream bad_target_crc_stream(bad_target_crc_string); + std::unique_ptr<const IdmapHeader> bad_target_crc_header = + IdmapHeader::FromBinaryStream(bad_target_crc_stream); + ASSERT_THAT(bad_target_crc_header, NotNull()); + ASSERT_NE(header->GetTargetCrc(), bad_target_crc_header->GetTargetCrc()); + ASSERT_FALSE(bad_target_crc_header->IsUpToDate(error)); + + // overlay crc: bytes (0xc, 0xf) + std::string bad_overlay_crc_string(stream.str()); + bad_overlay_crc_string[0xc] = '.'; + bad_overlay_crc_string[0xd] = '.'; + bad_overlay_crc_string[0xe] = '.'; + bad_overlay_crc_string[0xf] = '.'; + std::stringstream bad_overlay_crc_stream(bad_overlay_crc_string); + std::unique_ptr<const IdmapHeader> bad_overlay_crc_header = + IdmapHeader::FromBinaryStream(bad_overlay_crc_stream); + ASSERT_THAT(bad_overlay_crc_header, NotNull()); + ASSERT_NE(header->GetOverlayCrc(), bad_overlay_crc_header->GetOverlayCrc()); + ASSERT_FALSE(bad_overlay_crc_header->IsUpToDate(error)); + + // target path: bytes (0x10, 0x10f) + std::string bad_target_path_string(stream.str()); + bad_target_path_string[0x10] = '\0'; + std::stringstream bad_target_path_stream(bad_target_path_string); + std::unique_ptr<const IdmapHeader> bad_target_path_header = + IdmapHeader::FromBinaryStream(bad_target_path_stream); + ASSERT_THAT(bad_target_path_header, NotNull()); + ASSERT_NE(header->GetTargetPath(), bad_target_path_header->GetTargetPath()); + ASSERT_FALSE(bad_target_path_header->IsUpToDate(error)); + + // overlay path: bytes (0x110, 0x20f) + std::string bad_overlay_path_string(stream.str()); + bad_overlay_path_string[0x110] = '\0'; + std::stringstream bad_overlay_path_stream(bad_overlay_path_string); + std::unique_ptr<const IdmapHeader> bad_overlay_path_header = + IdmapHeader::FromBinaryStream(bad_overlay_path_stream); + ASSERT_THAT(bad_overlay_path_header, NotNull()); + ASSERT_NE(header->GetOverlayPath(), bad_overlay_path_header->GetOverlayPath()); + ASSERT_FALSE(bad_overlay_path_header->IsUpToDate(error)); +} + +class TestVisitor : public Visitor { + public: + explicit TestVisitor(std::ostream& stream) : stream_(stream) { + } + + void visit(const Idmap& idmap ATTRIBUTE_UNUSED) { + stream_ << "TestVisitor::visit(Idmap)" << std::endl; + } + + void visit(const IdmapHeader& idmap ATTRIBUTE_UNUSED) { + stream_ << "TestVisitor::visit(IdmapHeader)" << std::endl; + } + + void visit(const IdmapData& idmap ATTRIBUTE_UNUSED) { + stream_ << "TestVisitor::visit(IdmapData)" << std::endl; + } + + void visit(const IdmapData::Header& idmap ATTRIBUTE_UNUSED) { + stream_ << "TestVisitor::visit(IdmapData::Header)" << std::endl; + } + + void visit(const IdmapData::TypeEntry& idmap ATTRIBUTE_UNUSED) { + stream_ << "TestVisitor::visit(IdmapData::TypeEntry)" << std::endl; + } + + private: + std::ostream& stream_; +}; + +TEST(IdmapTests, TestVisitor) { + std::string raw(reinterpret_cast<const char*>(idmap_raw_data), idmap_raw_data_len); + std::istringstream stream(raw); + + std::stringstream error; + std::unique_ptr<const Idmap> idmap = Idmap::FromBinaryStream(stream, error); + ASSERT_THAT(idmap, NotNull()); + + std::stringstream test_stream; + TestVisitor visitor(test_stream); + idmap->accept(&visitor); + + ASSERT_EQ(test_stream.str(), + "TestVisitor::visit(Idmap)\n" + "TestVisitor::visit(IdmapHeader)\n" + "TestVisitor::visit(IdmapData)\n" + "TestVisitor::visit(IdmapData::Header)\n" + "TestVisitor::visit(IdmapData::TypeEntry)\n" + "TestVisitor::visit(IdmapData::TypeEntry)\n"); +} + +} // namespace idmap2 +} // namespace android diff --git a/cmds/idmap2/tests/Main.cpp b/cmds/idmap2/tests/Main.cpp new file mode 100644 index 000000000000..f2469eaf57cc --- /dev/null +++ b/cmds/idmap2/tests/Main.cpp @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <string> + +#include "android-base/file.h" + +#include "gtest/gtest.h" + +#include "TestHelpers.h" + +namespace android { +namespace idmap2 { + +const std::string GetTestDataPath() { + return base::GetExecutableDirectory() + "/tests/data"; +} + +} // namespace idmap2 +} // namespace android + +int main(int argc, char** argv) { + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/cmds/idmap2/tests/PrettyPrintVisitorTests.cpp b/cmds/idmap2/tests/PrettyPrintVisitorTests.cpp new file mode 100644 index 000000000000..da9779211f81 --- /dev/null +++ b/cmds/idmap2/tests/PrettyPrintVisitorTests.cpp @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <memory> +#include <sstream> +#include <string> + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +#include "androidfw/ApkAssets.h" +#include "androidfw/Idmap.h" + +#include "idmap2/Idmap.h" +#include "idmap2/PrettyPrintVisitor.h" + +#include "TestHelpers.h" + +using ::testing::IsNull; +using ::testing::NotNull; + +using android::ApkAssets; + +namespace android { +namespace idmap2 { + +TEST(PrettyPrintVisitorTests, CreatePrettyPrintVisitor) { + const std::string target_apk_path(GetTestDataPath() + "/target/target.apk"); + std::unique_ptr<const ApkAssets> target_apk = ApkAssets::Load(target_apk_path); + ASSERT_THAT(target_apk, NotNull()); + + const std::string overlay_apk_path(GetTestDataPath() + "/overlay/overlay.apk"); + std::unique_ptr<const ApkAssets> overlay_apk = ApkAssets::Load(overlay_apk_path); + ASSERT_THAT(overlay_apk, NotNull()); + + std::stringstream error; + std::unique_ptr<const Idmap> idmap = + Idmap::FromApkAssets(target_apk_path, *target_apk, overlay_apk_path, *overlay_apk, error); + ASSERT_THAT(idmap, NotNull()); + + std::stringstream stream; + PrettyPrintVisitor visitor(stream); + idmap->accept(&visitor); + + ASSERT_NE(stream.str().find("target apk path : "), std::string::npos); + ASSERT_NE(stream.str().find("overlay apk path : "), std::string::npos); + ASSERT_NE(stream.str().find("0x7f010000 -> 0x7f010000 integer/int1\n"), std::string::npos); +} + +TEST(PrettyPrintVisitorTests, CreatePrettyPrintVisitorWithoutAccessToApks) { + fclose(stderr); // silence expected warnings from libandroidfw + + std::string raw(reinterpret_cast<const char*>(idmap_raw_data), idmap_raw_data_len); + std::istringstream raw_stream(raw); + + std::stringstream error; + std::unique_ptr<const Idmap> idmap = Idmap::FromBinaryStream(raw_stream, error); + ASSERT_THAT(idmap, NotNull()); + + std::stringstream stream; + PrettyPrintVisitor visitor(stream); + idmap->accept(&visitor); + + ASSERT_NE(stream.str().find("target apk path : "), std::string::npos); + ASSERT_NE(stream.str().find("overlay apk path : "), std::string::npos); + ASSERT_NE(stream.str().find("0x7f020000 -> 0x7f020000\n"), std::string::npos); +} + +} // namespace idmap2 +} // namespace android diff --git a/cmds/idmap2/tests/RawPrintVisitorTests.cpp b/cmds/idmap2/tests/RawPrintVisitorTests.cpp new file mode 100644 index 000000000000..c28ce2e02ea9 --- /dev/null +++ b/cmds/idmap2/tests/RawPrintVisitorTests.cpp @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <cstdio> // fclose +#include <memory> +#include <sstream> +#include <string> + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +#include "idmap2/Idmap.h" +#include "idmap2/RawPrintVisitor.h" + +#include "TestHelpers.h" + +using ::testing::IsNull; +using ::testing::NotNull; + +namespace android { +namespace idmap2 { + +TEST(RawPrintVisitorTests, CreateRawPrintVisitor) { + const std::string target_apk_path(GetTestDataPath() + "/target/target.apk"); + std::unique_ptr<const ApkAssets> target_apk = ApkAssets::Load(target_apk_path); + ASSERT_THAT(target_apk, NotNull()); + + const std::string overlay_apk_path(GetTestDataPath() + "/overlay/overlay.apk"); + std::unique_ptr<const ApkAssets> overlay_apk = ApkAssets::Load(overlay_apk_path); + ASSERT_THAT(overlay_apk, NotNull()); + + std::stringstream error; + std::unique_ptr<const Idmap> idmap = + Idmap::FromApkAssets(target_apk_path, *target_apk, overlay_apk_path, *overlay_apk, error); + ASSERT_THAT(idmap, NotNull()); + + std::stringstream stream; + RawPrintVisitor visitor(stream); + idmap->accept(&visitor); + + ASSERT_NE(stream.str().find("00000000: 504d4449 magic\n"), std::string::npos); + ASSERT_NE(stream.str().find("00000004: 00000001 version\n"), std::string::npos); + ASSERT_NE(stream.str().find("00000008: f5ad1d1d target crc\n"), std::string::npos); + ASSERT_NE(stream.str().find("0000000c: d470336b overlay crc\n"), std::string::npos); + ASSERT_NE(stream.str().find("0000021c: 00000000 0x7f010000 -> 0x7f010000 integer/int1\n"), + std::string::npos); +} + +TEST(RawPrintVisitorTests, CreateRawPrintVisitorWithoutAccessToApks) { + fclose(stderr); // silence expected warnings from libandroidfw + + std::string raw(reinterpret_cast<const char*>(idmap_raw_data), idmap_raw_data_len); + std::istringstream raw_stream(raw); + + std::stringstream error; + std::unique_ptr<const Idmap> idmap = Idmap::FromBinaryStream(raw_stream, error); + ASSERT_THAT(idmap, NotNull()); + + std::stringstream stream; + RawPrintVisitor visitor(stream); + idmap->accept(&visitor); + + ASSERT_NE(stream.str().find("00000000: 504d4449 magic\n"), std::string::npos); + ASSERT_NE(stream.str().find("00000004: 00000001 version\n"), std::string::npos); + ASSERT_NE(stream.str().find("00000008: 00001234 target crc\n"), std::string::npos); + ASSERT_NE(stream.str().find("0000000c: 00005678 overlay crc\n"), std::string::npos); + ASSERT_NE(stream.str().find("0000021c: 00000000 0x7f020000 -> 0x7f020000\n"), std::string::npos); +} + +} // namespace idmap2 +} // namespace android diff --git a/cmds/idmap2/tests/ResourceUtilsTests.cpp b/cmds/idmap2/tests/ResourceUtilsTests.cpp new file mode 100644 index 000000000000..0547fa00de3d --- /dev/null +++ b/cmds/idmap2/tests/ResourceUtilsTests.cpp @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <memory> +#include <string> +#include <utility> + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +#include "androidfw/ApkAssets.h" +#include "idmap2/ResourceUtils.h" + +#include "TestHelpers.h" + +using ::testing::NotNull; + +namespace android { +namespace idmap2 { + +class ResourceUtilsTests : public Idmap2Tests { + protected: + void SetUp() override { + Idmap2Tests::SetUp(); + + apk_assets_ = ApkAssets::Load(GetTargetApkPath()); + ASSERT_THAT(apk_assets_, NotNull()); + + am_.SetApkAssets({apk_assets_.get()}); + } + + const AssetManager2& GetAssetManager() { + return am_; + } + + private: + AssetManager2 am_; + std::unique_ptr<const ApkAssets> apk_assets_; +}; + +TEST_F(ResourceUtilsTests, ResToTypeEntryName) { + bool lookup_ok; + std::string name; + std::tie(lookup_ok, name) = utils::ResToTypeEntryName(GetAssetManager(), 0x7f010000u); + ASSERT_TRUE(lookup_ok); + ASSERT_EQ(name, "integer/int1"); +} + +TEST_F(ResourceUtilsTests, ResToTypeEntryNameNoSuchResourceId) { + bool lookup_ok; + std::tie(lookup_ok, std::ignore) = utils::ResToTypeEntryName(GetAssetManager(), 0x7f123456u); + ASSERT_FALSE(lookup_ok); +} + +} // namespace idmap2 +} // namespace android diff --git a/cmds/idmap2/tests/TestHelpers.h b/cmds/idmap2/tests/TestHelpers.h new file mode 100644 index 000000000000..18dc541021c1 --- /dev/null +++ b/cmds/idmap2/tests/TestHelpers.h @@ -0,0 +1,168 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef IDMAP2_TESTS_TESTHELPERS_H_ +#define IDMAP2_TESTS_TESTHELPERS_H_ + +#include <string> + +namespace android { +namespace idmap2 { + +const unsigned char idmap_raw_data[] = { + // IDMAP HEADER + // 0x0: magic + 0x49, 0x44, 0x4d, 0x50, + + // 0x4: version + 0x01, 0x00, 0x00, 0x00, + + // 0x8: target crc + 0x34, 0x12, 0x00, 0x00, + + // 0xc: overlay crc + 0x78, 0x56, 0x00, 0x00, + + // 0x10: target path "target.apk" + 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x2e, 0x61, 0x70, 0x6b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + + // 0x110: overlay path "overlay.apk" + 0x6f, 0x76, 0x65, 0x72, 0x6c, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x6b, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + + // DATA HEADER + // 0x210: target package id + 0x7f, 0x00, + + // 0x212: types count + 0x02, 0x00, + + // DATA BLOCK + // 0x214: target type + 0x02, 0x00, + + // 0x216: overlay type + 0x02, 0x00, + + // 0x218: entry count + 0x01, 0x00, + + // 0x21a: entry offset + 0x00, 0x00, + + // 0x21c: entries + 0x00, 0x00, 0x00, 0x00, + + // DATA BLOCK + // 0x220: target type + 0x03, 0x00, + + // 0x222: overlay type + 0x03, 0x00, + + // 0x224: entry count + 0x03, 0x00, + + // 0x226: entry offset + 0x03, 0x00, + + // 0x228, 0x22c, 0x230: entries + 0x00, 0x00, 0x00, 0x00, + + 0xff, 0xff, 0xff, 0xff, + + 0x01, 0x00, 0x00, 0x00}; + +const unsigned int idmap_raw_data_len = 565; + +const std::string GetTestDataPath(); + +class Idmap2Tests : public testing::Test { + protected: + virtual void SetUp() { +#ifdef __ANDROID__ + tmp_dir_path_ = "/data/local/tmp/idmap2-tests-XXXXXX"; +#else + tmp_dir_path_ = "/tmp/idmap2-tests-XXXXXX"; +#endif + EXPECT_NE(mkdtemp(const_cast<char*>(tmp_dir_path_.c_str())), nullptr) + << "Failed to create temporary directory: " << strerror(errno); + target_apk_path_ = GetTestDataPath() + "/target/target.apk"; + overlay_apk_path_ = GetTestDataPath() + "/overlay/overlay.apk"; + idmap_path_ = tmp_dir_path_ + "/a.idmap"; + } + + virtual void TearDown() { + EXPECT_EQ(rmdir(tmp_dir_path_.c_str()), 0) + << "Failed to remove temporary directory " << tmp_dir_path_ << ": " << strerror(errno); + } + + const std::string& GetTempDirPath() { + return tmp_dir_path_; + } + + const std::string& GetTargetApkPath() { + return target_apk_path_; + } + + const std::string& GetOverlayApkPath() { + return overlay_apk_path_; + } + + const std::string& GetIdmapPath() { + return idmap_path_; + } + + private: + std::string tmp_dir_path_; + std::string target_apk_path_; + std::string overlay_apk_path_; + std::string idmap_path_; +}; + +} // namespace idmap2 +} // namespace android + +#endif // IDMAP2_TESTS_TESTHELPERS_H_ diff --git a/cmds/idmap2/tests/XmlTests.cpp b/cmds/idmap2/tests/XmlTests.cpp new file mode 100644 index 000000000000..97ff03e0f9e3 --- /dev/null +++ b/cmds/idmap2/tests/XmlTests.cpp @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <cstdio> // fclose + +#include "idmap2/Xml.h" +#include "idmap2/ZipFile.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +#include "TestHelpers.h" + +using ::testing::IsNull; +using ::testing::NotNull; + +namespace android { +namespace idmap2 { + +TEST(XmlTests, Create) { + auto zip = ZipFile::Open(GetTestDataPath() + "/target/target.apk"); + ASSERT_THAT(zip, NotNull()); + + auto data = zip->Uncompress("AndroidManifest.xml"); + ASSERT_THAT(data, NotNull()); + + auto xml = Xml::Create(data->buf, data->size); + ASSERT_THAT(xml, NotNull()); + + fclose(stderr); // silence expected warnings from libandroidfw + const char* not_xml = "foo"; + auto fail = Xml::Create(reinterpret_cast<const uint8_t*>(not_xml), strlen(not_xml)); + ASSERT_THAT(fail, IsNull()); +} + +TEST(XmlTests, FindTag) { + auto zip = ZipFile::Open(GetTestDataPath() + "/target/target.apk"); + ASSERT_THAT(zip, NotNull()); + + auto data = zip->Uncompress("res/xml/test.xml"); + ASSERT_THAT(data, NotNull()); + + auto xml = Xml::Create(data->buf, data->size); + ASSERT_THAT(xml, NotNull()); + + auto attrs = xml->FindTag("c"); + ASSERT_THAT(attrs, NotNull()); + ASSERT_EQ(attrs->size(), 4u); + ASSERT_EQ(attrs->at("type_string"), "fortytwo"); + ASSERT_EQ(std::stoi(attrs->at("type_int_dec")), 42); + ASSERT_EQ(std::stoi(attrs->at("type_int_hex")), 42); + ASSERT_NE(std::stoul(attrs->at("type_int_boolean")), 0u); + + auto fail = xml->FindTag("does-not-exist"); + ASSERT_THAT(fail, IsNull()); +} + +} // namespace idmap2 +} // namespace android diff --git a/cmds/idmap2/tests/ZipFileTests.cpp b/cmds/idmap2/tests/ZipFileTests.cpp new file mode 100644 index 000000000000..a504d3126c05 --- /dev/null +++ b/cmds/idmap2/tests/ZipFileTests.cpp @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <cstdio> // fclose +#include <string> +#include <utility> + +#include "idmap2/ZipFile.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +#include "TestHelpers.h" + +using ::testing::IsNull; +using ::testing::NotNull; + +namespace android { +namespace idmap2 { + +TEST(ZipFileTests, BasicOpen) { + auto zip = ZipFile::Open(GetTestDataPath() + "/target/target.apk"); + ASSERT_THAT(zip, NotNull()); + + fclose(stderr); // silence expected warnings from libziparchive + auto fail = ZipFile::Open(GetTestDataPath() + "/does-not-exist"); + ASSERT_THAT(fail, IsNull()); +} + +TEST(ZipFileTests, Crc) { + auto zip = ZipFile::Open(GetTestDataPath() + "/target/target.apk"); + ASSERT_THAT(zip, NotNull()); + + bool status; + uint32_t crc; + std::tie(status, crc) = zip->Crc("AndroidManifest.xml"); + ASSERT_TRUE(status); + ASSERT_EQ(crc, 0x762f3d24); + + std::tie(status, std::ignore) = zip->Crc("does-not-exist"); + ASSERT_FALSE(status); +} + +TEST(ZipFileTests, Uncompress) { + auto zip = ZipFile::Open(GetTestDataPath() + "/target/target.apk"); + ASSERT_THAT(zip, NotNull()); + + auto data = zip->Uncompress("assets/lorem-ipsum.txt"); + ASSERT_THAT(data, NotNull()); + const std::string lorem_ipsum("Lorem ipsum dolor sit amet.\n"); + ASSERT_THAT(data->size, lorem_ipsum.size()); + ASSERT_THAT(std::string(reinterpret_cast<const char*>(data->buf), data->size), lorem_ipsum); + + auto fail = zip->Uncompress("does-not-exist"); + ASSERT_THAT(fail, IsNull()); +} + +} // namespace idmap2 +} // namespace android diff --git a/cmds/idmap2/tests/data/overlay/AndroidManifest.xml b/cmds/idmap2/tests/data/overlay/AndroidManifest.xml new file mode 100644 index 000000000000..9f89d3121a82 --- /dev/null +++ b/cmds/idmap2/tests/data/overlay/AndroidManifest.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2018 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<manifest + xmlns:android="http://schemas.android.com/apk/res/android" + package="test.overlay"> + <overlay + android:targetPackage="test.target" /> +</manifest> diff --git a/cmds/idmap2/tests/data/overlay/AndroidManifestStatic1.xml b/cmds/idmap2/tests/data/overlay/AndroidManifestStatic1.xml new file mode 100644 index 000000000000..39336cc7e76b --- /dev/null +++ b/cmds/idmap2/tests/data/overlay/AndroidManifestStatic1.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2018 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<manifest + xmlns:android="http://schemas.android.com/apk/res/android" + package="test.overlay.static1"> + <overlay + android:targetPackage="test.target" + android:isStatic="true" + android:priority="1" /> +</manifest> diff --git a/cmds/idmap2/tests/data/overlay/AndroidManifestStatic2.xml b/cmds/idmap2/tests/data/overlay/AndroidManifestStatic2.xml new file mode 100644 index 000000000000..e1cc1758d8cc --- /dev/null +++ b/cmds/idmap2/tests/data/overlay/AndroidManifestStatic2.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2018 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<manifest + xmlns:android="http://schemas.android.com/apk/res/android" + package="test.overlay.static2"> + <overlay + android:targetPackage="test.target" + android:isStatic="true" + android:priority="2" /> +</manifest> diff --git a/cmds/idmap2/tests/data/overlay/build b/cmds/idmap2/tests/data/overlay/build new file mode 100644 index 000000000000..cba108674005 --- /dev/null +++ b/cmds/idmap2/tests/data/overlay/build @@ -0,0 +1,40 @@ +# Copyright (C) 2018 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +FRAMEWORK_RES_APK="$(gettop)/out/target/common/obj/APPS/framework-res_intermediates/package-export.apk" + +aapt2 compile --dir res -o compiled.flata + +aapt2 link \ + --no-resource-removal \ + -I "$FRAMEWORK_RES_APK" \ + --manifest AndroidManifest.xml \ + -o overlay.apk \ + compiled.flata + +aapt2 link \ + --no-resource-removal \ + -I "$FRAMEWORK_RES_APK" \ + --manifest AndroidManifestStatic1.xml \ + -o overlay-static-1.apk \ + compiled.flata + +aapt2 link \ + --no-resource-removal \ + -I "$FRAMEWORK_RES_APK" \ + --manifest AndroidManifestStatic2.xml \ + -o overlay-static-2.apk \ + compiled.flata + +rm compiled.flata diff --git a/cmds/idmap2/tests/data/overlay/overlay-static-1.apk b/cmds/idmap2/tests/data/overlay/overlay-static-1.apk Binary files differnew file mode 100644 index 000000000000..9a0f487522c8 --- /dev/null +++ b/cmds/idmap2/tests/data/overlay/overlay-static-1.apk diff --git a/cmds/idmap2/tests/data/overlay/overlay-static-2.apk b/cmds/idmap2/tests/data/overlay/overlay-static-2.apk Binary files differnew file mode 100644 index 000000000000..3fc31c7d11b0 --- /dev/null +++ b/cmds/idmap2/tests/data/overlay/overlay-static-2.apk diff --git a/cmds/idmap2/tests/data/overlay/overlay.apk b/cmds/idmap2/tests/data/overlay/overlay.apk Binary files differnew file mode 100644 index 000000000000..b4cd7cfc3248 --- /dev/null +++ b/cmds/idmap2/tests/data/overlay/overlay.apk diff --git a/cmds/idmap2/tests/data/overlay/res/values-sv/values.xml b/cmds/idmap2/tests/data/overlay/res/values-sv/values.xml new file mode 100644 index 000000000000..eed0b3dac1ab --- /dev/null +++ b/cmds/idmap2/tests/data/overlay/res/values-sv/values.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2018 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<resources> + <string name="str1">overlay-1-sv</string> + <string name="str4">overlay-4-sv</string> +</resources> diff --git a/cmds/idmap2/tests/data/overlay/res/values/values.xml b/cmds/idmap2/tests/data/overlay/res/values/values.xml new file mode 100644 index 000000000000..815d1a88fa7b --- /dev/null +++ b/cmds/idmap2/tests/data/overlay/res/values/values.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2018 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<resources> + <string name="str1">overlay-1</string> + <string name="str3">overlay-3</string> + <integer name="int1">-1</integer> + <integer name="not_in_target">-1</integer> +</resources> diff --git a/cmds/idmap2/tests/data/target/AndroidManifest.xml b/cmds/idmap2/tests/data/target/AndroidManifest.xml new file mode 100644 index 000000000000..3a861b4800fa --- /dev/null +++ b/cmds/idmap2/tests/data/target/AndroidManifest.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2018 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<manifest + xmlns:android="http://schemas.android.com/apk/res/android" + package="test.target"> +</manifest> diff --git a/cmds/idmap2/tests/data/target/assets/lorem-ipsum.txt b/cmds/idmap2/tests/data/target/assets/lorem-ipsum.txt new file mode 100644 index 000000000000..d2cf010d36ff --- /dev/null +++ b/cmds/idmap2/tests/data/target/assets/lorem-ipsum.txt @@ -0,0 +1 @@ +Lorem ipsum dolor sit amet. diff --git a/cmds/statsd/tools/dogfood/Android.mk b/cmds/idmap2/tests/data/target/build index baf235bb91be..8569c4ff0a6b 100644 --- a/cmds/statsd/tools/dogfood/Android.mk +++ b/cmds/idmap2/tests/data/target/build @@ -1,4 +1,4 @@ -# Copyright (C) 2017 The Android Open Source Project +# Copyright (C) 2018 The Android Open Source Project # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -11,23 +11,7 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -# -# -LOCAL_PATH:= $(call my-dir) -include $(CLEAR_VARS) - -LOCAL_PACKAGE_NAME := StatsdDogfood -LOCAL_PRIVATE_PLATFORM_APIS := true - -LOCAL_SRC_FILES := $(call all-java-files-under, src) - -LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res -LOCAL_STATIC_JAVA_LIBRARIES := platformprotoslite \ - statsdprotolite - -LOCAL_PRIVILEGED_MODULE := true -LOCAL_DEX_PREOPT := false -LOCAL_CERTIFICATE := platform -LOCAL_PROGUARD_ENABLED := disabled -include $(BUILD_PACKAGE) +aapt2 compile --dir res -o compiled.flata +aapt2 link --manifest AndroidManifest.xml -A assets -o target.apk compiled.flata +rm compiled.flata diff --git a/cmds/idmap2/tests/data/target/res/values/values.xml b/cmds/idmap2/tests/data/target/res/values/values.xml new file mode 100644 index 000000000000..56bf0d60021a --- /dev/null +++ b/cmds/idmap2/tests/data/target/res/values/values.xml @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2018 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<resources> + <string name="a">a</string> + <string name="b">b</string> + <string name="c">c</string> + <string name="str1">target-1</string> + <string name="str2">target-2</string> + <string name="str3">target-3</string> + <string name="str4">target-4</string> + <string name="x">x</string> + <string name="y">y</string> + <string name="z">z</string> + <integer name="int1">1</integer> +</resources> diff --git a/cmds/idmap2/tests/data/target/res/xml/test.xml b/cmds/idmap2/tests/data/target/res/xml/test.xml new file mode 100644 index 000000000000..0fe21c6b6d0a --- /dev/null +++ b/cmds/idmap2/tests/data/target/res/xml/test.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2018 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<a> + <b> + <c + type_string="fortytwo" + type_int_dec="42" + type_int_hex="0x2a" + type_int_boolean="true" + /> + </b> +</a> diff --git a/cmds/idmap2/tests/data/target/target.apk b/cmds/idmap2/tests/data/target/target.apk Binary files differnew file mode 100644 index 000000000000..18ecc276caae --- /dev/null +++ b/cmds/idmap2/tests/data/target/target.apk diff --git a/cmds/incident/Android.bp b/cmds/incident/Android.bp new file mode 100644 index 000000000000..2a5ec5bfacaf --- /dev/null +++ b/cmds/incident/Android.bp @@ -0,0 +1,46 @@ +// Copyright (C) 2016 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +cc_binary { + name: "incident", + + srcs: [ + "main.cpp", + ":incident_sections", + ], + + shared_libs: [ + "libbase", + "libbinder", + "libcutils", + "liblog", + "libutils", + "libincident", + ], + + cflags: [ + "-Wall", + "-Werror", + "-Wno-missing-field-initializers", + "-Wno-unused-variable", + "-Wunused-parameter", + ], +} + +genrule { + name: "incident_sections", + tools: ["incident-section-gen"], + out: ["incident_sections.cpp"], + cmd: "$(location incident-section-gen) incident > $(out)", +} diff --git a/cmds/incident/Android.mk b/cmds/incident/Android.mk deleted file mode 100644 index 8615f9b63e43..000000000000 --- a/cmds/incident/Android.mk +++ /dev/null @@ -1,48 +0,0 @@ -# Copyright (C) 2016 The Android Open Source Project -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -LOCAL_PATH:= $(call my-dir) -include $(CLEAR_VARS) - -LOCAL_SRC_FILES := \ - main.cpp - -LOCAL_MODULE := incident - -LOCAL_SHARED_LIBRARIES := \ - libbase \ - libbinder \ - libcutils \ - liblog \ - libutils \ - libincident - -LOCAL_CFLAGS += \ - -Wall -Werror -Wno-missing-field-initializers -Wno-unused-variable -Wunused-parameter - -LOCAL_MODULE_CLASS := EXECUTABLES -gen_src_dir := $(local-generated-sources-dir) - -gen := $(gen_src_dir)/incident_sections.cpp -$(gen): $(HOST_OUT_EXECUTABLES)/incident-section-gen -$(gen): PRIVATE_CUSTOM_TOOL = \ - $(HOST_OUT_EXECUTABLES)/incident-section-gen incident > $@ -$(gen): $(HOST_OUT_EXECUTABLES)/incident-section-gen - $(transform-generated-source) -LOCAL_GENERATED_SOURCES += $(gen) - -gen_src_dir:= -gen:= - -include $(BUILD_EXECUTABLE) diff --git a/cmds/incidentd/Android.bp b/cmds/incidentd/Android.bp new file mode 100644 index 000000000000..1e970f46b01d --- /dev/null +++ b/cmds/incidentd/Android.bp @@ -0,0 +1,117 @@ +// Copyright (C) 2016 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// ========= +// incidentd +// ========= + +cc_binary { + name: "incidentd", + + srcs: [ + "src/**/*.cpp", + ":incidentd_section_list", + ], + + cflags: [ + "-Wall", + "-Werror", + "-Wno-missing-field-initializers", + "-Wno-unused-variable", + "-Wunused-parameter", + + // Allow implicit fallthrough in IncidentService.cpp:85 until it is fixed. + "-Wno-error=implicit-fallthrough", + + // optimize for size (protobuf glop can get big) + "-Os", + //"-g", + //"-O0", + ], + + local_include_dirs: ["src"], + generated_headers: ["gen-platform-proto-constants"], + + shared_libs: [ + "libbase", + "libbinder", + "libdebuggerd_client", + "libdumputils", + "libincident", + "liblog", + "libprotoutil", + "libservices", + "libutils", + ], + + init_rc: ["incidentd.rc"], +} + +// ============== +// incidentd_test +// ============== + +cc_test { + name: "incidentd_test", + test_suites: ["device-tests"], + + cflags: [ + "-Werror", + "-Wall", + "-Wno-unused-variable", + "-Wunused-parameter", + + // Allow implicit fallthrough in IncidentService.cpp:85 until it is fixed. + "-Wno-error=implicit-fallthrough", + ], + + local_include_dirs: ["src"], + generated_headers: ["gen-platform-proto-constants"], + + srcs: [ + "tests/**/*.cpp", + "src/PrivacyBuffer.cpp", + "src/FdBuffer.cpp", + "src/Privacy.cpp", + "src/Reporter.cpp", + "src/Section.cpp", + "src/Throttler.cpp", + "src/incidentd_util.cpp", + "src/report_directory.cpp", + ], + + data: ["testdata/**/*"], + + static_libs: ["libgmock"], + + shared_libs: [ + "libbase", + "libbinder", + "libdebuggerd_client", + "libdumputils", + "libincident", + "liblog", + "libprotobuf-cpp-lite", + "libprotoutil", + "libservices", + "libutils", + ], +} + +genrule { + name: "incidentd_section_list", + tools: ["incident-section-gen"], + out: ["section_list.cpp"], + cmd: "$(location incident-section-gen) incidentd > $(out)", +} diff --git a/cmds/incidentd/Android.mk b/cmds/incidentd/Android.mk deleted file mode 100644 index eba558653b04..000000000000 --- a/cmds/incidentd/Android.mk +++ /dev/null @@ -1,156 +0,0 @@ -# Copyright (C) 2016 The Android Open Source Project -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -LOCAL_PATH:= $(call my-dir) - -# proto files used in incidentd to generate cppstream proto headers. -PROTO_FILES:= \ - frameworks/base/core/proto/android/os/backtrace.proto \ - frameworks/base/core/proto/android/os/data.proto \ - frameworks/base/core/proto/android/util/log.proto - -# ========= # -# incidentd # -# ========= # - -include $(CLEAR_VARS) - -LOCAL_MODULE := incidentd - -LOCAL_SRC_FILES := $(call all-cpp-files-under, src) \ - -LOCAL_CFLAGS += \ - -Wall -Werror -Wno-missing-field-initializers -Wno-unused-variable -Wunused-parameter - -# Allow implicit fallthrough in IncidentService.cpp:85 until it is fixed. -LOCAL_CFLAGS += -Wno-error=implicit-fallthrough - -ifeq (debug,) - LOCAL_CFLAGS += \ - -g -O0 -else - # optimize for size (protobuf glop can get big) - LOCAL_CFLAGS += \ - -Os -endif -LOCAL_C_INCLUDES += $(LOCAL_PATH)/src - -LOCAL_SHARED_LIBRARIES := \ - libbase \ - libbinder \ - libdebuggerd_client \ - libdumputils \ - libincident \ - liblog \ - libprotoutil \ - libservices \ - libutils - -LOCAL_MODULE_CLASS := EXECUTABLES - -gen_src_dir := $(local-generated-sources-dir) - -# generate section_list.cpp -GEN_LIST := $(gen_src_dir)/src/section_list.cpp -$(GEN_LIST): $(HOST_OUT_EXECUTABLES)/incident-section-gen -$(GEN_LIST): PRIVATE_CUSTOM_TOOL = \ - $(HOST_OUT_EXECUTABLES)/incident-section-gen incidentd > $@ -$(GEN_LIST): $(HOST_OUT_EXECUTABLES)/incident-section-gen - $(transform-generated-source) -LOCAL_GENERATED_SOURCES += $(GEN_LIST) -GEN_LIST:= - -# generate cppstream proto, add proto files to PROTO_FILES -GEN_PROTO := $(gen_src_dir)/proto.timestamp -$(GEN_PROTO): $(HOST_OUT_EXECUTABLES)/aprotoc $(HOST_OUT_EXECUTABLES)/protoc-gen-cppstream $(PROTO_FILES) -$(GEN_PROTO): PRIVATE_GEN_SRC_DIR := $(gen_src_dir) -$(GEN_PROTO): PRIVATE_CUSTOM_TOOL = \ - $(HOST_OUT_EXECUTABLES)/aprotoc --plugin=protoc-gen-cppstream=$(HOST_OUT_EXECUTABLES)/protoc-gen-cppstream \ - --cppstream_out=$(PRIVATE_GEN_SRC_DIR) -Iexternal/protobuf/src -I . \ - $(PROTO_FILES) \ - && touch $@ -$(GEN_PROTO): $(HOST_OUT_EXECUTABLES)/aprotoc - $(transform-generated-source) -LOCAL_GENERATED_SOURCES += $(GEN_PROTO) -GEN_PROTO:= - -gen_src_dir:= - -LOCAL_INIT_RC := incidentd.rc - -include $(BUILD_EXECUTABLE) - -# ============== # -# incidentd_test # -# ============== # - -include $(CLEAR_VARS) - -LOCAL_MODULE := incidentd_test -LOCAL_COMPATIBILITY_SUITE := device-tests -LOCAL_MODULE_TAGS := tests - -LOCAL_CFLAGS := -Werror -Wall -Wno-unused-variable -Wunused-parameter - -# Allow implicit fallthrough in IncidentService.cpp:85 until it is fixed. -LOCAL_CFLAGS += -Wno-error=implicit-fallthrough - -LOCAL_C_INCLUDES += $(LOCAL_PATH)/src - -LOCAL_SRC_FILES := $(call all-cpp-files-under, tests) \ - src/PrivacyBuffer.cpp \ - src/FdBuffer.cpp \ - src/Privacy.cpp \ - src/Reporter.cpp \ - src/Section.cpp \ - src/Throttler.cpp \ - src/incidentd_util.cpp \ - src/report_directory.cpp \ - -LOCAL_STATIC_LIBRARIES := \ - libgmock \ - -LOCAL_SHARED_LIBRARIES := \ - libbase \ - libbinder \ - libdebuggerd_client \ - libdumputils \ - libincident \ - liblog \ - libprotobuf-cpp-lite \ - libprotoutil \ - libservices \ - libutils \ - -LOCAL_TEST_DATA := $(call find-test-data-in-subdirs, $(LOCAL_PATH), *, testdata) - -LOCAL_MODULE_CLASS := NATIVE_TESTS -gen_src_dir := $(local-generated-sources-dir) -# generate cppstream proto for testing -GEN_PROTO := $(gen_src_dir)/test.proto.timestamp -$(GEN_PROTO): $(HOST_OUT_EXECUTABLES)/aprotoc $(HOST_OUT_EXECUTABLES)/protoc-gen-cppstream $(PROTO_FILES) -$(GEN_PROTO): PRIVATE_GEN_SRC_DIR := $(gen_src_dir) -$(GEN_PROTO): PRIVATE_CUSTOM_TOOL = \ - $(HOST_OUT_EXECUTABLES)/aprotoc --plugin=protoc-gen-cppstream=$(HOST_OUT_EXECUTABLES)/protoc-gen-cppstream \ - --cppstream_out=$(PRIVATE_GEN_SRC_DIR) -Iexternal/protobuf/src -I . \ - $(PROTO_FILES) \ - && touch $@ -$(GEN_PROTO): $(HOST_OUT_EXECUTABLES)/aprotoc - $(transform-generated-source) -LOCAL_GENERATED_SOURCES += $(GEN_PROTO) -GEN_PROTO:= - -gen_src_dir:= - -include $(BUILD_NATIVE_TEST) diff --git a/cmds/statsd/Android.bp b/cmds/statsd/Android.bp index 94203f4feccd..a3cd8a3bb32b 100644 --- a/cmds/statsd/Android.bp +++ b/cmds/statsd/Android.bp @@ -1,5 +1,5 @@ // -// Copyright (C) 2015 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. @@ -42,6 +42,279 @@ cc_library_host_shared { } +cc_defaults { + name: "statsd_defaults", + aidl: { + include_dirs: ["frameworks/base/core/java"], + }, + + srcs: [ + ":statsd_aidl", + "src/statsd_config.proto", + "src/FieldValue.cpp", + "src/hash.cpp", + "src/stats_log_util.cpp", + "src/anomaly/AlarmMonitor.cpp", + "src/anomaly/AlarmTracker.cpp", + "src/anomaly/AnomalyTracker.cpp", + "src/anomaly/DurationAnomalyTracker.cpp", + "src/anomaly/subscriber_util.cpp", + "src/condition/CombinationConditionTracker.cpp", + "src/condition/condition_util.cpp", + "src/condition/SimpleConditionTracker.cpp", + "src/condition/ConditionWizard.cpp", + "src/condition/StateTracker.cpp", + "src/config/ConfigKey.cpp", + "src/config/ConfigListener.cpp", + "src/config/ConfigManager.cpp", + "src/external/Perfetto.cpp", + "src/external/Perfprofd.cpp", + "src/external/StatsPuller.cpp", + "src/external/StatsCompanionServicePuller.cpp", + "src/external/SubsystemSleepStatePuller.cpp", + "src/external/ResourceHealthManagerPuller.cpp", + "src/external/ResourceThermalManagerPuller.cpp", + "src/external/StatsPullerManager.cpp", + "src/external/puller_util.cpp", + "src/logd/LogEvent.cpp", + "src/logd/LogListener.cpp", + "src/matchers/CombinationLogMatchingTracker.cpp", + "src/matchers/EventMatcherWizard.cpp", + "src/matchers/matcher_util.cpp", + "src/matchers/SimpleLogMatchingTracker.cpp", + "src/metrics/MetricProducer.cpp", + "src/metrics/EventMetricProducer.cpp", + "src/metrics/CountMetricProducer.cpp", + "src/metrics/DurationMetricProducer.cpp", + "src/metrics/duration_helper/OringDurationTracker.cpp", + "src/metrics/duration_helper/MaxDurationTracker.cpp", + "src/metrics/ValueMetricProducer.cpp", + "src/metrics/GaugeMetricProducer.cpp", + "src/metrics/MetricsManager.cpp", + "src/metrics/metrics_manager_util.cpp", + "src/packages/UidMap.cpp", + "src/storage/StorageManager.cpp", + "src/StatsLogProcessor.cpp", + "src/StatsService.cpp", + "src/statscompanion_util.cpp", + "src/subscriber/IncidentdReporter.cpp", + "src/subscriber/SubscriberReporter.cpp", + "src/HashableDimensionKey.cpp", + "src/guardrail/StatsdStats.cpp", + "src/socket/StatsSocketListener.cpp", + "src/shell/ShellSubscriber.cpp", + "src/shell/shell_config.proto", + + ":perfprofd_aidl", + ], + + local_include_dirs: [ + "src", + ], + + static_libs: [ + "libhealthhalutils", + ], + + shared_libs: [ + "libbase", + "libbinder", + "libincident", + "liblog", + "libutils", + "libservices", + "libprotoutil", + "libstatslog", + "libhardware", + "libhardware_legacy", + "libhidlbase", + "libhidltransport", + "libhwbinder", + "android.frameworks.stats@1.0", + "android.hardware.health@2.0", + "android.hardware.power@1.0", + "android.hardware.power@1.1", + "android.hardware.thermal@1.0", + "libpackagelistparser", + "libsysutils", + "libcutils", + ], +} + +// ========= +// statsd +// ========= + +cc_binary { + name: "statsd", + defaults: ["statsd_defaults"], + + srcs: ["src/main.cpp"], + + cflags: [ + "-Wall", + "-Wextra", + "-Werror", + "-Wno-unused-parameter", + // optimize for size (protobuf glop can get big) + "-Os", + // "-g", + // "-O0", + ], + + product_variables: { + eng: { + // Enable sanitizer ONLY on eng builds + //sanitize: { + // address: true, + //}, + }, + debuggable: { + // Add a flag to enable stats log printing from statsd on debug builds. + cflags: ["-DVERY_VERBOSE_PRINTING"], + }, + }, + + proto: { + type: "lite", + }, + + shared_libs: ["libgtest_prod"], + + vintf_fragments: ["android.frameworks.stats@1.0-service.xml"], + + init_rc: ["statsd.rc"], +} + +// ============== +// statsd_test +// ============== + +cc_test { + name: "statsd_test", + defaults: ["statsd_defaults"], + test_suites: ["device-tests"], + + cflags: [ + "-Wall", + "-Werror", + "-Wno-missing-field-initializers", + "-Wno-unused-variable", + "-Wno-unused-function", + "-Wno-unused-parameter", + ], + + srcs: [ + "src/atom_field_options.proto", + "src/atoms.proto", + "src/stats_log.proto", + "src/shell/shell_data.proto", + "tests/AlarmMonitor_test.cpp", + "tests/anomaly/AlarmTracker_test.cpp", + "tests/anomaly/AnomalyTracker_test.cpp", + "tests/ConfigManager_test.cpp", + "tests/external/puller_util_test.cpp", + "tests/indexed_priority_queue_test.cpp", + "tests/LogEntryMatcher_test.cpp", + "tests/LogEvent_test.cpp", + "tests/MetricsManager_test.cpp", + "tests/StatsLogProcessor_test.cpp", + "tests/StatsService_test.cpp", + "tests/UidMap_test.cpp", + "tests/FieldValue_test.cpp", + "tests/condition/CombinationConditionTracker_test.cpp", + "tests/condition/SimpleConditionTracker_test.cpp", + "tests/condition/StateTracker_test.cpp", + "tests/metrics/OringDurationTracker_test.cpp", + "tests/metrics/MaxDurationTracker_test.cpp", + "tests/metrics/CountMetricProducer_test.cpp", + "tests/metrics/DurationMetricProducer_test.cpp", + "tests/metrics/EventMetricProducer_test.cpp", + "tests/metrics/ValueMetricProducer_test.cpp", + "tests/metrics/GaugeMetricProducer_test.cpp", + "tests/guardrail/StatsdStats_test.cpp", + "tests/metrics/metrics_test_helper.cpp", + "tests/statsd_test_util.cpp", + "tests/e2e/WakelockDuration_e2e_test.cpp", + "tests/e2e/MetricActivation_e2e_test.cpp", + "tests/e2e/MetricConditionLink_e2e_test.cpp", + "tests/e2e/Alarm_e2e_test.cpp", + "tests/e2e/Attribution_e2e_test.cpp", + "tests/e2e/GaugeMetric_e2e_push_test.cpp", + "tests/e2e/GaugeMetric_e2e_pull_test.cpp", + "tests/e2e/ValueMetric_pull_e2e_test.cpp", + "tests/e2e/DimensionInCondition_e2e_combination_AND_cond_test.cpp", + "tests/e2e/DimensionInCondition_e2e_combination_OR_cond_test.cpp", + "tests/e2e/DimensionInCondition_e2e_simple_cond_test.cpp", + "tests/e2e/Anomaly_count_e2e_test.cpp", + "tests/e2e/Anomaly_duration_sum_e2e_test.cpp", + "tests/e2e/ConfigTtl_e2e_test.cpp", + "tests/e2e/PartialBucket_e2e_test.cpp", + "tests/shell/ShellSubscriber_test.cpp", + ], + + static_libs: [ + "libgmock", + "libplatformprotos", + ], + + proto: { + type: "full", + include_dirs: ["external/protobuf/src"], + }, + + shared_libs: ["libprotobuf-cpp-full"], + +} + +//############################# +// statsd micro benchmark +//############################# + +cc_benchmark { + name: "statsd_benchmark", + defaults: ["statsd_defaults"], + + srcs: [ + "src/atom_field_options.proto", + "src/atoms.proto", + "src/stats_log.proto", + "benchmark/main.cpp", + "benchmark/hello_world_benchmark.cpp", + "benchmark/log_event_benchmark.cpp", + "benchmark/stats_write_benchmark.cpp", + "benchmark/filter_value_benchmark.cpp", + "benchmark/get_dimensions_for_condition_benchmark.cpp", + "benchmark/metric_util.cpp", + "benchmark/duration_metric_benchmark.cpp", + ], + + proto: { + type: "full", + include_dirs: ["external/protobuf/src"], + }, + + cflags: [ + "-Wall", + "-Werror", + "-Wno-unused-parameter", + "-Wno-unused-variable", + "-Wno-unused-function", + + // Bug: http://b/29823425 Disable -Wvarargs for Clang update to r271374 + "-Wno-varargs" + ], + + static_libs: [ + "libplatformprotos", + ], + + shared_libs: [ + "libgtest_prod", + "libstatslog", + "libprotobuf-cpp-full", + ], +} // ==== java proto device library (for test only) ============================== java_library { diff --git a/cmds/statsd/Android.mk b/cmds/statsd/Android.mk deleted file mode 100644 index 5818f5d550c3..000000000000 --- a/cmds/statsd/Android.mk +++ /dev/null @@ -1,319 +0,0 @@ -# Copyright (C) 2017 The Android Open Source Project -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -LOCAL_PATH:= $(call my-dir) - -statsd_common_src := \ - ../../core/java/android/os/IStatsCompanionService.aidl \ - ../../core/java/android/os/IStatsManager.aidl \ - src/statsd_config.proto \ - src/FieldValue.cpp \ - src/hash.cpp \ - src/stats_log_util.cpp \ - src/anomaly/AlarmMonitor.cpp \ - src/anomaly/AlarmTracker.cpp \ - src/anomaly/AnomalyTracker.cpp \ - src/anomaly/DurationAnomalyTracker.cpp \ - src/anomaly/subscriber_util.cpp \ - src/condition/CombinationConditionTracker.cpp \ - src/condition/condition_util.cpp \ - src/condition/SimpleConditionTracker.cpp \ - src/condition/ConditionWizard.cpp \ - src/condition/StateTracker.cpp \ - src/config/ConfigKey.cpp \ - src/config/ConfigListener.cpp \ - src/config/ConfigManager.cpp \ - src/external/Perfetto.cpp \ - src/external/Perfprofd.cpp \ - src/external/StatsPuller.cpp \ - src/external/StatsCompanionServicePuller.cpp \ - src/external/SubsystemSleepStatePuller.cpp \ - src/external/ResourceHealthManagerPuller.cpp \ - src/external/ResourceThermalManagerPuller.cpp \ - src/external/StatsPullerManager.cpp \ - src/external/puller_util.cpp \ - src/logd/LogEvent.cpp \ - src/logd/LogListener.cpp \ - src/matchers/CombinationLogMatchingTracker.cpp \ - src/matchers/EventMatcherWizard.cpp \ - src/matchers/matcher_util.cpp \ - src/matchers/SimpleLogMatchingTracker.cpp \ - src/metrics/MetricProducer.cpp \ - src/metrics/EventMetricProducer.cpp \ - src/metrics/CountMetricProducer.cpp \ - src/metrics/DurationMetricProducer.cpp \ - src/metrics/duration_helper/OringDurationTracker.cpp \ - src/metrics/duration_helper/MaxDurationTracker.cpp \ - src/metrics/ValueMetricProducer.cpp \ - src/metrics/GaugeMetricProducer.cpp \ - src/metrics/MetricsManager.cpp \ - src/metrics/metrics_manager_util.cpp \ - src/packages/UidMap.cpp \ - src/storage/StorageManager.cpp \ - src/StatsLogProcessor.cpp \ - src/StatsService.cpp \ - src/statscompanion_util.cpp \ - src/subscriber/IncidentdReporter.cpp \ - src/subscriber/SubscriberReporter.cpp \ - src/HashableDimensionKey.cpp \ - src/guardrail/StatsdStats.cpp \ - src/socket/StatsSocketListener.cpp \ - src/shell/ShellSubscriber.cpp \ - src/shell/shell_config.proto - -# TODO(b/110563449): Once statsd is using a blueprint file, migrate to the proper filegroups. -statsd_common_src += \ - ../../../../system/extras/perfprofd/binder_interface/aidl/android/os/IPerfProfd.aidl - -statsd_common_c_includes := \ - $(LOCAL_PATH)/src \ - $(LOCAL_PATH)/../../libs/services/include - -statsd_common_aidl_includes := \ - $(LOCAL_PATH)/../../core/java - -statsd_common_static_libraries := \ - libhealthhalutils - -statsd_common_shared_libraries := \ - libbase \ - libbinder \ - libincident \ - liblog \ - libutils \ - libservices \ - libprotoutil \ - libstatslog \ - libhardware \ - libhardware_legacy \ - libhidlbase \ - libhidltransport \ - libhwbinder \ - android.frameworks.stats@1.0 \ - android.hardware.health@2.0 \ - android.hardware.power@1.0 \ - android.hardware.power@1.1 \ - android.hardware.thermal@1.0 \ - libpackagelistparser \ - libsysutils \ - libcutils - -# ========= -# statsd -# ========= - -include $(CLEAR_VARS) - -LOCAL_MODULE := statsd - -LOCAL_SRC_FILES := \ - $(statsd_common_src) \ - src/main.cpp - -LOCAL_CFLAGS += \ - -Wall \ - -Wextra \ - -Werror \ - -Wno-unused-parameter - -ifeq (debug,) - LOCAL_CFLAGS += \ - -g -O0 -else - # optimize for size (protobuf glop can get big) - LOCAL_CFLAGS += \ - -Os -endif -LOCAL_PROTOC_OPTIMIZE_TYPE := lite - -LOCAL_AIDL_INCLUDES := $(statsd_common_aidl_includes) -LOCAL_C_INCLUDES += $(statsd_common_c_includes) - -LOCAL_STATIC_LIBRARIES := $(statsd_common_static_libraries) - -LOCAL_SHARED_LIBRARIES := $(statsd_common_shared_libraries) \ - libgtest_prod - -LOCAL_MODULE_CLASS := EXECUTABLES - -# Enable sanitizer ONLY on eng builds. -#ifeq ($(TARGET_BUILD_VARIANT),eng) -# LOCAL_CLANG := true -# LOCAL_SANITIZE := address -#endif - -# Add a flag to enable stats log printing from statsd on debug builds. -ifneq (,$(filter userdebug eng, $(TARGET_BUILD_VARIANT))) - LOCAL_CFLAGS += \ - -DVERY_VERBOSE_PRINTING -endif - -LOCAL_INIT_RC := statsd.rc - -include $(BUILD_EXECUTABLE) - - -# ============== -# statsd_test -# ============== - -include $(CLEAR_VARS) - -LOCAL_MODULE := statsd_test -LOCAL_COMPATIBILITY_SUITE := device-tests -LOCAL_MODULE_TAGS := tests - -LOCAL_AIDL_INCLUDES := $(statsd_common_aidl_includes) -LOCAL_C_INCLUDES += $(statsd_common_c_includes) - -LOCAL_CFLAGS += \ - -Wall \ - -Werror \ - -Wno-missing-field-initializers \ - -Wno-unused-variable \ - -Wno-unused-function \ - -Wno-unused-parameter - -LOCAL_SRC_FILES := \ - $(statsd_common_src) \ - src/atom_field_options.proto \ - src/atoms.proto \ - src/stats_log.proto \ - src/shell/shell_data.proto \ - tests/AlarmMonitor_test.cpp \ - tests/anomaly/AlarmTracker_test.cpp \ - tests/anomaly/AnomalyTracker_test.cpp \ - tests/ConfigManager_test.cpp \ - tests/external/puller_util_test.cpp \ - tests/indexed_priority_queue_test.cpp \ - tests/LogEntryMatcher_test.cpp \ - tests/LogEvent_test.cpp \ - tests/MetricsManager_test.cpp \ - tests/StatsLogProcessor_test.cpp \ - tests/StatsService_test.cpp \ - tests/UidMap_test.cpp \ - tests/FieldValue_test.cpp \ - tests/condition/CombinationConditionTracker_test.cpp \ - tests/condition/SimpleConditionTracker_test.cpp \ - tests/condition/StateTracker_test.cpp \ - tests/metrics/OringDurationTracker_test.cpp \ - tests/metrics/MaxDurationTracker_test.cpp \ - tests/metrics/CountMetricProducer_test.cpp \ - tests/metrics/DurationMetricProducer_test.cpp \ - tests/metrics/EventMetricProducer_test.cpp \ - tests/metrics/ValueMetricProducer_test.cpp \ - tests/metrics/GaugeMetricProducer_test.cpp \ - tests/guardrail/StatsdStats_test.cpp \ - tests/metrics/metrics_test_helper.cpp \ - tests/statsd_test_util.cpp \ - tests/e2e/WakelockDuration_e2e_test.cpp \ - tests/e2e/MetricActivation_e2e_test.cpp \ - tests/e2e/MetricConditionLink_e2e_test.cpp \ - tests/e2e/Alarm_e2e_test.cpp \ - tests/e2e/Attribution_e2e_test.cpp \ - tests/e2e/GaugeMetric_e2e_push_test.cpp \ - tests/e2e/GaugeMetric_e2e_pull_test.cpp \ - tests/e2e/ValueMetric_pull_e2e_test.cpp \ - tests/e2e/DimensionInCondition_e2e_combination_AND_cond_test.cpp \ - tests/e2e/DimensionInCondition_e2e_combination_OR_cond_test.cpp \ - tests/e2e/DimensionInCondition_e2e_simple_cond_test.cpp \ - tests/e2e/Anomaly_count_e2e_test.cpp \ - tests/e2e/Anomaly_duration_sum_e2e_test.cpp \ - tests/e2e/ConfigTtl_e2e_test.cpp \ - tests/e2e/PartialBucket_e2e_test.cpp \ - tests/shell/ShellSubscriber_test.cpp - -LOCAL_STATIC_LIBRARIES := \ - $(statsd_common_static_libraries) \ - libgmock \ - libplatformprotos - -LOCAL_PROTOC_OPTIMIZE_TYPE := full - -LOCAL_PROTOC_FLAGS := \ - -Iexternal/protobuf/src - -LOCAL_SHARED_LIBRARIES := $(statsd_common_shared_libraries) \ - libprotobuf-cpp-full - -include $(BUILD_NATIVE_TEST) - -############################## -# statsd micro benchmark -############################## - -include $(CLEAR_VARS) -LOCAL_MODULE := statsd_benchmark - -LOCAL_SRC_FILES := $(statsd_common_src) \ - src/atom_field_options.proto \ - src/atoms.proto \ - src/stats_log.proto \ - benchmark/main.cpp \ - benchmark/hello_world_benchmark.cpp \ - benchmark/log_event_benchmark.cpp \ - benchmark/stats_write_benchmark.cpp \ - benchmark/filter_value_benchmark.cpp \ - benchmark/get_dimensions_for_condition_benchmark.cpp \ - benchmark/metric_util.cpp \ - benchmark/duration_metric_benchmark.cpp - -LOCAL_STATIC_LIBRARIES := \ - $(statsd_common_static_libraries) - -LOCAL_PROTOC_OPTIMIZE_TYPE := full - -LOCAL_PROTOC_FLAGS := \ - -Iexternal/protobuf/src - -LOCAL_SHARED_LIBRARIES := $(statsd_common_shared_libraries) \ - libprotobuf-cpp-full - -LOCAL_STATIC_JAVA_LIBRARIES := \ - platformprotoslite - -LOCAL_C_INCLUDES := $(statsd_common_c_includes) - -LOCAL_CFLAGS := -Wall \ - -Werror \ - -Wno-unused-parameter \ - -Wno-unused-variable \ - -Wno-unused-function \ - -# Bug: http://b/29823425 Disable -Wvarargs for Clang update to r271374 -LOCAL_CFLAGS += -Wno-varargs - -LOCAL_AIDL_INCLUDES := $(statsd_common_aidl_includes) - -LOCAL_STATIC_LIBRARIES := \ - $(statsd_common_static_libraries) \ - libplatformprotos - -LOCAL_SHARED_LIBRARIES := $(statsd_common_shared_libraries) \ - libgtest_prod \ - libstatslog - -LOCAL_MODULE_TAGS := eng tests - -include $(BUILD_NATIVE_BENCHMARK) - - -statsd_common_src:= -statsd_common_aidl_includes:= -statsd_common_c_includes:= -statsd_common_static_libraries:= -statsd_common_shared_libraries:= - - -include $(call all-makefiles-under,$(LOCAL_PATH)) diff --git a/cmds/statsd/android.frameworks.stats@1.0-service.xml b/cmds/statsd/android.frameworks.stats@1.0-service.xml new file mode 100644 index 000000000000..bb02f66a28b1 --- /dev/null +++ b/cmds/statsd/android.frameworks.stats@1.0-service.xml @@ -0,0 +1,11 @@ +<manifest version="1.0" type="framework"> + <hal> + <name>android.frameworks.stats</name> + <transport>hwbinder</transport> + <version>1.0</version> + <interface> + <name>IStats</name> + <instance>default</instance> + </interface> + </hal> +</manifest> diff --git a/cmds/statsd/src/StatsLogProcessor.cpp b/cmds/statsd/src/StatsLogProcessor.cpp index f4c70bee2806..12e2560a43b5 100644 --- a/cmds/statsd/src/StatsLogProcessor.cpp +++ b/cmds/statsd/src/StatsLogProcessor.cpp @@ -442,7 +442,7 @@ void StatsLogProcessor::flushIfNecessaryLocked( if (totalBytes > StatsdStats::kMaxMetricsBytesPerConfig) { // Too late. We need to start clearing data. metricsManager.dropData(timestampNs); - StatsdStats::getInstance().noteDataDropped(key); + StatsdStats::getInstance().noteDataDropped(key, totalBytes); VLOG("StatsD had to toss out metrics for %s", key.ToString().c_str()); } else if ((totalBytes > StatsdStats::kBytesPerConfigTriggerGetData) || (mOnDiskDataConfigs.find(key) != mOnDiskDataConfigs.end())) { diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto index bce182076bd5..ca049b03c4c7 100644 --- a/cmds/statsd/src/atoms.proto +++ b/cmds/statsd/src/atoms.proto @@ -22,7 +22,6 @@ option java_outer_classname = "AtomsProto"; import "frameworks/base/cmds/statsd/src/atom_field_options.proto"; import "frameworks/base/core/proto/android/app/enums.proto"; -import "frameworks/base/core/proto/android/app/launcher/launcher.proto"; import "frameworks/base/core/proto/android/app/settings_enums.proto"; import "frameworks/base/core/proto/android/app/job/enums.proto"; import "frameworks/base/core/proto/android/bluetooth/enums.proto"; @@ -30,6 +29,7 @@ import "frameworks/base/core/proto/android/os/enums.proto"; import "frameworks/base/core/proto/android/server/enums.proto"; import "frameworks/base/core/proto/android/service/procstats_enum.proto"; import "frameworks/base/core/proto/android/stats/enums.proto"; +import "frameworks/base/core/proto/android/stats/launcher/launcher.proto"; import "frameworks/base/core/proto/android/telecomm/enums.proto"; import "frameworks/base/core/proto/android/telephony/enums.proto"; import "frameworks/base/core/proto/android/view/enums.proto"; @@ -150,7 +150,7 @@ message Atom { } // Pulled events will start at field 10000. - // Next: 10037 + // Next: 10038 oneof pulled { WifiBytesTransfer wifi_bytes_transfer = 10000; WifiBytesTransferByFgBg wifi_bytes_transfer_by_fg_bg = 10001; @@ -189,6 +189,7 @@ message Atom { ProcStats proc_stats_pkg_proc = 10034; ProcessCpuTime process_cpu_time = 10035; NativeProcessMemoryState native_process_memory_state = 10036; + CpuTimePerThreadFreq cpu_time_per_thread_freq = 10037; } // DO NOT USE field numbers above 100,000 in AOSP. @@ -1441,10 +1442,10 @@ message PhoneStateChanged { } message LauncherUIChanged { - optional android.app.launcher.LauncherAction action = 1; - optional android.app.launcher.LauncherState src_state = 2; - optional android.app.launcher.LauncherState dst_state = 3; - optional android.app.launcher.LauncherExtension extension = 4 [(log_mode) = MODE_BYTES]; + optional android.stats.launcher.LauncherAction action = 1; + optional android.stats.launcher.LauncherState src_state = 2; + optional android.stats.launcher.LauncherState dst_state = 3; + optional android.stats.launcher.LauncherExtension extension = 4 [(log_mode) = MODE_BYTES]; optional bool is_swipe_up_enabled = 5; } @@ -2539,10 +2540,17 @@ message Temperature { * Binder stats will be reset every time the data is pulled. It means it can only be pulled by one * config on the device. * - * Next tag: 14 + * Next tag: 15 */ message BinderCalls { + // UID of the process responsible for the binder transaction. It will be set if the process + // executing the binder transaction attribute the transaction to another uid using + // Binder.setThreadWorkSource(). + // + // If not set, the value will be -1. optional int32 uid = 1 [(is_uid) = true]; + // UID of the process executing the binder transaction. + optional int32 direct_caller_uid = 14; // Fully qualified class name of the API call. // // This is a system server class name. @@ -3121,3 +3129,29 @@ message ProcessCpuTime { // Process cpu time in system space, cumulative from boot/process start optional int64 system_time_millis = 4; } + +/** + * Pulls the CPU usage for each thread. + * + * Read from /proc/$PID/task/$TID/time_in_state files. + * + * TODO(mishaw): This is an experimental atom. Issues with big/little CPU frequencies, and + * time_in_state files not being present on some phones, have not been addressed. These should be + * considered before a public release. + */ +message CpuTimePerThreadFreq { + // UID that owns the process. + optional int32 uid = 1 [(is_uid) = true]; + // ID of the process. + optional uint32 process_id = 2; + // ID of the thread. + optional uint32 thread_id = 3; + // Name of the process taken from `/proc/$PID/cmdline`. + optional string process_name = 4; + // Name of the thread taken from `/proc/$PID/task/$TID/comm` + optional string thread_name = 5; + // What frequency the CPU was running at, in KHz + optional uint32 frequency_khz = 6; + // Time spent in frequency in milliseconds, since thread start. + optional uint32 time_millis = 7; +} diff --git a/cmds/statsd/src/external/StatsPullerManager.cpp b/cmds/statsd/src/external/StatsPullerManager.cpp index 73a88c1b1796..c9b361d8cc82 100644 --- a/cmds/statsd/src/external/StatsPullerManager.cpp +++ b/cmds/statsd/src/external/StatsPullerManager.cpp @@ -234,6 +234,11 @@ const std::map<int, PullAtomInfo> StatsPullerManager::kAllPullAtomInfo = { {{} /* additive fields */, {} /* non additive fields */, 5 * NS_PER_SEC /* min cool-down in seconds*/, new StatsCompanionServicePuller(android::util::PROCESS_CPU_TIME)}}, + {android::util::CPU_TIME_PER_THREAD_FREQ, + {{7}, + {2, 3, 4, 5, 6}, + 1 * NS_PER_SEC, + new StatsCompanionServicePuller(android::util::CPU_TIME_PER_THREAD_FREQ)}}, }; StatsPullerManager::StatsPullerManager() : mNextPullTimeNs(NO_ALARM_UPDATE) { diff --git a/cmds/statsd/src/guardrail/StatsdStats.cpp b/cmds/statsd/src/guardrail/StatsdStats.cpp index bf0bfec9c08d..023d835a8429 100644 --- a/cmds/statsd/src/guardrail/StatsdStats.cpp +++ b/cmds/statsd/src/guardrail/StatsdStats.cpp @@ -73,7 +73,8 @@ const int FIELD_ID_CONFIG_STATS_MATCHER_COUNT = 7; const int FIELD_ID_CONFIG_STATS_ALERT_COUNT = 8; const int FIELD_ID_CONFIG_STATS_VALID = 9; const int FIELD_ID_CONFIG_STATS_BROADCAST = 10; -const int FIELD_ID_CONFIG_STATS_DATA_DROP = 11; +const int FIELD_ID_CONFIG_STATS_DATA_DROP_TIME = 11; +const int FIELD_ID_CONFIG_STATS_DATA_DROP_BYTES = 21; const int FIELD_ID_CONFIG_STATS_DUMP_REPORT_TIME = 12; const int FIELD_ID_CONFIG_STATS_DUMP_REPORT_BYTES = 20; const int FIELD_ID_CONFIG_STATS_MATCHER_STATS = 13; @@ -205,11 +206,11 @@ void StatsdStats::noteBroadcastSent(const ConfigKey& key, int32_t timeSec) { it->second->broadcast_sent_time_sec.push_back(timeSec); } -void StatsdStats::noteDataDropped(const ConfigKey& key) { - noteDataDropped(key, getWallClockSec()); +void StatsdStats::noteDataDropped(const ConfigKey& key, const size_t totalBytes) { + noteDataDropped(key, totalBytes, getWallClockSec()); } -void StatsdStats::noteDataDropped(const ConfigKey& key, int32_t timeSec) { +void StatsdStats::noteDataDropped(const ConfigKey& key, const size_t totalBytes, int32_t timeSec) { lock_guard<std::mutex> lock(mLock); auto it = mConfigStats.find(key); if (it == mConfigStats.end()) { @@ -218,8 +219,10 @@ void StatsdStats::noteDataDropped(const ConfigKey& key, int32_t timeSec) { } if (it->second->data_drop_time_sec.size() == kMaxTimestampCount) { it->second->data_drop_time_sec.pop_front(); + it->second->data_drop_bytes.pop_front(); } it->second->data_drop_time_sec.push_back(timeSec); + it->second->data_drop_bytes.push_back(totalBytes); } void StatsdStats::noteMetricsReportSent(const ConfigKey& key, const size_t num_bytes) { @@ -382,6 +385,7 @@ void StatsdStats::resetInternalLocked() { for (auto& config : mConfigStats) { config.second->broadcast_sent_time_sec.clear(); config.second->data_drop_time_sec.clear(); + config.second->data_drop_bytes.clear(); config.second->dump_report_stats.clear(); config.second->annotations.clear(); config.second->matcher_stats.clear(); @@ -421,8 +425,12 @@ void StatsdStats::dumpStats(int out) const { dprintf(out, "\tbroadcast time: %d\n", broadcastTime); } - for (const auto& dataDropTime : configStats->data_drop_time_sec) { - dprintf(out, "\tdata drop time: %d\n", dataDropTime); + auto dropTimePtr = configStats->data_drop_time_sec.begin(); + auto dropBytesPtr = configStats->data_drop_bytes.begin(); + for (int i = 0; i < (int)configStats->data_drop_time_sec.size(); + i++, dropTimePtr++, dropBytesPtr++) { + dprintf(out, "\tdata drop time: %d with size %lld", *dropTimePtr, + (long long)*dropBytesPtr); } } dprintf(out, "%lu Active Configs\n", (unsigned long)mConfigStats.size()); @@ -445,9 +453,13 @@ void StatsdStats::dumpStats(int out) const { (long long)broadcastTime); } - for (const auto& dataDropTime : configStats->data_drop_time_sec) { - dprintf(out, "\tdata drop time: %s(%lld)\n", buildTimeString(dataDropTime).c_str(), - (long long)dataDropTime); + auto dropTimePtr = configStats->data_drop_time_sec.begin(); + auto dropBytesPtr = configStats->data_drop_bytes.begin(); + for (int i = 0; i < (int)configStats->data_drop_time_sec.size(); + i++, dropTimePtr++, dropBytesPtr++) { + dprintf(out, "\tdata drop time: %s(%lld) with %lld bytes\n", + buildTimeString(*dropTimePtr).c_str(), (long long)*dropTimePtr, + (long long)*dropBytesPtr); } for (const auto& dump : configStats->dump_report_stats) { @@ -540,9 +552,15 @@ void addConfigStatsToProto(const ConfigStats& configStats, ProtoOutputStream* pr broadcast); } - for (const auto& drop : configStats.data_drop_time_sec) { - proto->write(FIELD_TYPE_INT32 | FIELD_ID_CONFIG_STATS_DATA_DROP | FIELD_COUNT_REPEATED, - drop); + for (const auto& drop_time : configStats.data_drop_time_sec) { + proto->write(FIELD_TYPE_INT32 | FIELD_ID_CONFIG_STATS_DATA_DROP_TIME | FIELD_COUNT_REPEATED, + drop_time); + } + + for (const auto& drop_bytes : configStats.data_drop_bytes) { + proto->write( + FIELD_TYPE_INT64 | FIELD_ID_CONFIG_STATS_DATA_DROP_BYTES | FIELD_COUNT_REPEATED, + (long long)drop_bytes); } for (const auto& dump : configStats.dump_report_stats) { diff --git a/cmds/statsd/src/guardrail/StatsdStats.h b/cmds/statsd/src/guardrail/StatsdStats.h index a8188c89928a..7d95b54cc574 100644 --- a/cmds/statsd/src/guardrail/StatsdStats.h +++ b/cmds/statsd/src/guardrail/StatsdStats.h @@ -43,6 +43,8 @@ struct ConfigStats { std::list<int32_t> broadcast_sent_time_sec; std::list<int32_t> data_drop_time_sec; + // Number of bytes dropped at corresponding time. + std::list<int64_t> data_drop_bytes; std::list<std::pair<int32_t, int64_t>> dump_report_stats; // Stores how many times a matcher have been matched. The map size is capped by kMaxConfigCount. @@ -169,7 +171,7 @@ public: /** * Report a config's metrics data has been dropped. */ - void noteDataDropped(const ConfigKey& key); + void noteDataDropped(const ConfigKey& key, const size_t totalBytes); /** * Report metrics data report has been sent. @@ -350,7 +352,7 @@ private: void resetInternalLocked(); - void noteDataDropped(const ConfigKey& key, int32_t timeSec); + void noteDataDropped(const ConfigKey& key, const size_t totalBytes, int32_t timeSec); void noteMetricsReportSent(const ConfigKey& key, const size_t num_bytes, int32_t timeSec); diff --git a/cmds/statsd/src/stats_log.proto b/cmds/statsd/src/stats_log.proto index 10ed7f3ebaa1..e8f74d3008e4 100644 --- a/cmds/statsd/src/stats_log.proto +++ b/cmds/statsd/src/stats_log.proto @@ -328,6 +328,7 @@ message StatsdStatsReport { optional bool is_valid = 9; repeated int32 broadcast_sent_time_sec = 10; repeated int32 data_drop_time_sec = 11; + repeated int64 data_drop_bytes = 21; repeated int32 dump_report_time_sec = 12; repeated int32 dump_report_data_size = 20; repeated MatcherStats matcher_stats = 13; diff --git a/cmds/statsd/tests/LogEvent_test.cpp b/cmds/statsd/tests/LogEvent_test.cpp index f59ac1a737e2..6384757b7fc8 100644 --- a/cmds/statsd/tests/LogEvent_test.cpp +++ b/cmds/statsd/tests/LogEvent_test.cpp @@ -12,9 +12,11 @@ // See the License for the specific language governing permissions and // limitations under the License. +#include "src/logd/LogEvent.h" #include <gtest/gtest.h> #include <log/log_event_list.h> -#include "src/logd/LogEvent.h" +#include "frameworks/base/cmds/statsd/src/atoms.pb.h" +#include "frameworks/base/core/proto/android/stats/launcher/launcher.pb.h" #ifdef __ANDROID__ @@ -22,6 +24,9 @@ namespace android { namespace os { namespace statsd { +using std::string; +using util::ProtoOutputStream; + TEST(LogEventTest, TestLogParsing) { LogEvent event1(1, 2000); @@ -390,6 +395,57 @@ TEST(LogEventTest, TestKeyValuePairsEvent) { } +TEST(LogEventTest, TestBinaryFieldAtom) { + Atom launcherAtom; + auto launcher_event = launcherAtom.mutable_launcher_event(); + launcher_event->set_action(stats::launcher::LauncherAction::LONGPRESS); + launcher_event->set_src_state(stats::launcher::LauncherState::OVERVIEW); + launcher_event->set_dst_state(stats::launcher::LauncherState::ALLAPPS); + + auto extension = launcher_event->mutable_extension(); + + auto src_target = extension->add_src_target(); + src_target->set_type(stats::launcher::LauncherTarget_Type_ITEM_TYPE); + src_target->set_item(stats::launcher::LauncherTarget_Item_FOLDER_ICON); + + auto dst_target = extension->add_dst_target(); + dst_target->set_type(stats::launcher::LauncherTarget_Type_ITEM_TYPE); + dst_target->set_item(stats::launcher::LauncherTarget_Item_WIDGET); + + string extension_str; + extension->SerializeToString(&extension_str); + + LogEvent event1(Atom::kLauncherEventFieldNumber, 1000); + + event1.write((int32_t)stats::launcher::LauncherAction::LONGPRESS); + event1.write((int32_t)stats::launcher::LauncherState::OVERVIEW); + event1.write((int64_t)stats::launcher::LauncherState::ALLAPPS); + event1.write(extension_str); + event1.init(); + + ProtoOutputStream proto; + event1.ToProto(proto); + + std::vector<uint8_t> outData; + outData.resize(proto.size()); + size_t pos = 0; + auto iter = proto.data(); + while (iter.readBuffer() != NULL) { + size_t toRead = iter.currentToRead(); + std::memcpy(&(outData[pos]), iter.readBuffer(), toRead); + pos += toRead; + iter.rp()->move(toRead); + } + + std::string result_str(outData.begin(), outData.end()); + std::string orig_str; + launcherAtom.SerializeToString(&orig_str); + + EXPECT_EQ(orig_str, result_str); +} + + + } // namespace statsd } // namespace os } // namespace android diff --git a/cmds/statsd/tests/guardrail/StatsdStats_test.cpp b/cmds/statsd/tests/guardrail/StatsdStats_test.cpp index 967ef3c5af63..f37f908532ca 100644 --- a/cmds/statsd/tests/guardrail/StatsdStats_test.cpp +++ b/cmds/statsd/tests/guardrail/StatsdStats_test.cpp @@ -126,7 +126,7 @@ TEST(StatsdStatsTest, TestSubStats) { stats.noteBroadcastSent(key); // data drop -> 1 - stats.noteDataDropped(key); + stats.noteDataDropped(key, 123); // dump report -> 3 stats.noteMetricsReportSent(key, 0); @@ -142,6 +142,8 @@ TEST(StatsdStatsTest, TestSubStats) { const auto& configReport = report.config_stats(0); EXPECT_EQ(2, configReport.broadcast_sent_time_sec_size()); EXPECT_EQ(1, configReport.data_drop_time_sec_size()); + EXPECT_EQ(1, configReport.data_drop_bytes_size()); + EXPECT_EQ(123, configReport.data_drop_bytes(0)); EXPECT_EQ(3, configReport.dump_report_time_sec_size()); EXPECT_EQ(3, configReport.dump_report_data_size_size()); EXPECT_EQ(1, configReport.annotation_size()); @@ -275,7 +277,7 @@ TEST(StatsdStatsTest, TestTimestampThreshold) { int32_t newTimestamp = 10000; // now it should trigger removing oldest timestamp - stats.noteDataDropped(key, 10000); + stats.noteDataDropped(key, 123, 10000); stats.noteBroadcastSent(key, 10000); stats.noteMetricsReportSent(key, 0, 10000); @@ -295,6 +297,7 @@ TEST(StatsdStatsTest, TestTimestampThreshold) { // the last timestamp is the newest timestamp. EXPECT_EQ(newTimestamp, configStats->broadcast_sent_time_sec.back()); EXPECT_EQ(newTimestamp, configStats->data_drop_time_sec.back()); + EXPECT_EQ(123, configStats->data_drop_bytes.back()); EXPECT_EQ(newTimestamp, configStats->dump_report_stats.back().first); } diff --git a/cmds/statsd/tools/dogfood/Android.bp b/cmds/statsd/tools/dogfood/Android.bp new file mode 100644 index 000000000000..bb494a6025af --- /dev/null +++ b/cmds/statsd/tools/dogfood/Android.bp @@ -0,0 +1,37 @@ +// 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. +// +// + +android_app { + name: "StatsdDogfood", + platform_apis: true, + + srcs: ["src/**/*.java"], + + resource_dirs: ["res"], + static_libs: [ + "platformprotoslite", + "statsdprotolite", + ], + + privileged: true, + dex_preopt: { + enabled: false, + }, + certificate: "platform", + optimize: { + enabled: false, + }, +} diff --git a/cmds/statsd/tools/loadtest/Android.bp b/cmds/statsd/tools/loadtest/Android.bp new file mode 100644 index 000000000000..bf87fc51dce1 --- /dev/null +++ b/cmds/statsd/tools/loadtest/Android.bp @@ -0,0 +1,37 @@ +// 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. +// +// + +android_app { + name: "StatsdLoadtest", + platform_apis: true, + + srcs: ["src/**/*.java"], + + resource_dirs: ["res"], + static_libs: [ + "platformprotoslite", + "statsdprotolite", + ], + + certificate: "platform", + privileged: true, + dex_preopt: { + enabled: false, + }, + optimize: { + enabled: false, + }, +} diff --git a/cmds/statsd/tools/loadtest/Android.mk b/cmds/statsd/tools/loadtest/Android.mk deleted file mode 100644 index 219cd9525bbd..000000000000 --- a/cmds/statsd/tools/loadtest/Android.mk +++ /dev/null @@ -1,33 +0,0 @@ -# Copyright (C) 2017 The Android Open Source Project -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -# -LOCAL_PATH:= $(call my-dir) -include $(CLEAR_VARS) - -LOCAL_PACKAGE_NAME := StatsdLoadtest -LOCAL_PRIVATE_PLATFORM_APIS := true - -LOCAL_SRC_FILES := $(call all-java-files-under, src) - -LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res -LOCAL_STATIC_JAVA_LIBRARIES := platformprotoslite \ - statsdprotolite - -LOCAL_CERTIFICATE := platform -LOCAL_PRIVILEGED_MODULE := true -LOCAL_DEX_PREOPT := false -LOCAL_PROGUARD_ENABLED := disabled - -include $(BUILD_PACKAGE) diff --git a/config/hiddenapi-light-greylist.txt b/config/hiddenapi-light-greylist.txt index d6f2712e2f4a..22c11e4625f7 100644 --- a/config/hiddenapi-light-greylist.txt +++ b/config/hiddenapi-light-greylist.txt @@ -2282,347 +2282,6 @@ Lcom/google/android/mms/util/PduCache;->purge(Landroid/net/Uri;)Lcom/google/andr Lcom/google/android/mms/util/PduCache;->purgeAll()V Lcom/google/android/mms/util/PduCacheEntry;->getPdu()Lcom/google/android/mms/pdu/GenericPdu; Lcom/google/android/mms/util/SqliteWrapper;->insert(Landroid/content/Context;Landroid/content/ContentResolver;Landroid/net/Uri;Landroid/content/ContentValues;)Landroid/net/Uri; -Ljava/io/Console;->encoding()Ljava/lang/String; -Ljava/io/File;->filePath:Ljava/nio/file/Path; -Ljava/io/File;->fs:Ljava/io/FileSystem; -Ljava/io/File;->path:Ljava/lang/String; -Ljava/io/File;->prefixLength:I -Ljava/io/File;->status:Ljava/io/File$PathStatus; -Ljava/io/FileDescriptor;->descriptor:I -Ljava/io/FileDescriptor;->getInt$()I -Ljava/io/FileDescriptor;->isSocket$()Z -Ljava/io/FileDescriptor;->setInt$(I)V -Ljava/io/FileInputStream;->fd:Ljava/io/FileDescriptor; -Ljava/io/FileOutputStream;->channel:Ljava/nio/channels/FileChannel; -Ljava/io/FileOutputStream;->fd:Ljava/io/FileDescriptor; -Ljava/io/FileSystem;->canonicalize(Ljava/lang/String;)Ljava/lang/String; -Ljava/io/FileSystem;->checkAccess(Ljava/io/File;I)Z -Ljava/io/FileSystem;->compare(Ljava/io/File;Ljava/io/File;)I -Ljava/io/FileSystem;->createDirectory(Ljava/io/File;)Z -Ljava/io/FileSystem;->createFileExclusively(Ljava/lang/String;)Z -Ljava/io/FileSystem;->delete(Ljava/io/File;)Z -Ljava/io/FileSystem;->fromURIPath(Ljava/lang/String;)Ljava/lang/String; -Ljava/io/FileSystem;->getBooleanAttributes(Ljava/io/File;)I -Ljava/io/FileSystem;->getDefaultParent()Ljava/lang/String; -Ljava/io/FileSystem;->getLastModifiedTime(Ljava/io/File;)J -Ljava/io/FileSystem;->getLength(Ljava/io/File;)J -Ljava/io/FileSystem;->getPathSeparator()C -Ljava/io/FileSystem;->getSeparator()C -Ljava/io/FileSystem;->getSpace(Ljava/io/File;I)J -Ljava/io/FileSystem;->hashCode(Ljava/io/File;)I -Ljava/io/FileSystem;->isAbsolute(Ljava/io/File;)Z -Ljava/io/FileSystem;->list(Ljava/io/File;)[Ljava/lang/String; -Ljava/io/FileSystem;->listRoots()[Ljava/io/File; -Ljava/io/FileSystem;->normalize(Ljava/lang/String;)Ljava/lang/String; -Ljava/io/FileSystem;->prefixLength(Ljava/lang/String;)I -Ljava/io/FileSystem;->rename(Ljava/io/File;Ljava/io/File;)Z -Ljava/io/FileSystem;->resolve(Ljava/io/File;)Ljava/lang/String; -Ljava/io/FileSystem;->resolve(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String; -Ljava/io/FileSystem;->setLastModifiedTime(Ljava/io/File;J)Z -Ljava/io/FileSystem;->setPermission(Ljava/io/File;IZZ)Z -Ljava/io/FileSystem;->setReadOnly(Ljava/io/File;)Z -Ljava/io/ObjectInputStream;->bin:Ljava/io/ObjectInputStream$BlockDataInputStream; -Ljava/io/ObjectInputStream;->bytesToDoubles([BI[DII)V -Ljava/io/ObjectInputStream;->bytesToFloats([BI[FII)V -Ljava/io/ObjectOutputStream;->protocol:I -Ljava/io/ObjectStreamClass;->computeDefaultSUID(Ljava/lang/Class;)J -Ljava/io/ObjectStreamClass;->computeFieldOffsets()V -Ljava/io/ObjectStreamClass;->fields:[Ljava/io/ObjectStreamField; -Ljava/io/ObjectStreamClass;->getConstructorId(Ljava/lang/Class;)J -Ljava/io/ObjectStreamClass;->getLocalDesc()Ljava/io/ObjectStreamClass; -Ljava/io/ObjectStreamClass;->getNumObjFields()I -Ljava/io/ObjectStreamClass;->getPrimDataSize()I -Ljava/io/ObjectStreamClass;->hasReadObjectMethod()Z -Ljava/io/ObjectStreamClass;->hasReadObjectNoDataMethod()Z -Ljava/io/ObjectStreamClass;->hasWriteObjectData()Z -Ljava/io/ObjectStreamClass;->newInstance()Ljava/lang/Object; -Ljava/io/ObjectStreamClass;->newInstance(Ljava/lang/Class;J)Ljava/lang/Object; -Ljava/io/ObjectStreamField;->getField()Ljava/lang/reflect/Field; -Ljava/io/RandomAccessFile;->fd:Ljava/io/FileDescriptor; -Ljava/lang/AbstractStringBuilder;->value:[C -Ljava/lang/Boolean;->value:Z -Ljava/lang/Byte;->toHexString(BZ)Ljava/lang/String; -Ljava/lang/Byte;->value:B -Ljava/lang/Character;->value:C -Ljava/lang/Class;-><init>()V -Ljava/lang/Class;->accessFlags:I -Ljava/lang/Class;->classLoader:Ljava/lang/ClassLoader; -Ljava/lang/Class;->clinitThreadId:I -Ljava/lang/Class;->dexCache:Ljava/lang/Object; -Ljava/lang/Class;->dexClassDefIndex:I -Ljava/lang/Class;->getDeclaredMethodsUnchecked(Z)[Ljava/lang/reflect/Method; -Ljava/lang/Class;->getMethod(Ljava/lang/String;[Ljava/lang/Class;Z)Ljava/lang/reflect/Method; -Ljava/lang/Class;->ifTable:[Ljava/lang/Object; -Ljava/lang/Class;->name:Ljava/lang/String; -Ljava/lang/Class;->objectSize:I -Ljava/lang/Class;->status:I -Ljava/lang/ClassLoader;->parent:Ljava/lang/ClassLoader; -Ljava/lang/Double;->value:D -Ljava/lang/Enum;->getSharedConstants(Ljava/lang/Class;)[Ljava/lang/Enum; -Ljava/lang/Enum;->name:Ljava/lang/String; -Ljava/lang/Enum;->ordinal:I -Ljava/lang/Float;->value:F -Ljava/lang/Integer;->value:I -Ljava/lang/invoke/MethodHandles$Lookup;-><init>(Ljava/lang/Class;I)V -Ljava/lang/Long;->value:J -Ljava/lang/Object;->identityHashCode(Ljava/lang/Object;)I -Ljava/lang/ref/Reference;->getReferent()Ljava/lang/Object; -Ljava/lang/ref/Reference;->referent:Ljava/lang/Object; -Ljava/lang/ref/ReferenceQueue;->add(Ljava/lang/ref/Reference;)V -Ljava/lang/reflect/AccessibleObject;->override:Z -Ljava/lang/reflect/Constructor;->serializationCopy(Ljava/lang/Class;Ljava/lang/Class;)Ljava/lang/reflect/Constructor; -Ljava/lang/reflect/Executable;->artMethod:J -Ljava/lang/reflect/Field;->accessFlags:I -Ljava/lang/reflect/Field;->getOffset()I -Ljava/lang/reflect/Parameter;-><init>(Ljava/lang/String;ILjava/lang/reflect/Executable;I)V -Ljava/lang/reflect/Proxy;->invoke(Ljava/lang/reflect/Proxy;Ljava/lang/reflect/Method;[Ljava/lang/Object;)Ljava/lang/Object; -Ljava/lang/Runtime;-><init>()V -Ljava/lang/Runtime;->load(Ljava/lang/String;Ljava/lang/ClassLoader;)V -Ljava/lang/Runtime;->loadLibrary(Ljava/lang/String;Ljava/lang/ClassLoader;)V -Ljava/lang/Runtime;->loadLibrary0(Ljava/lang/ClassLoader;Ljava/lang/String;)V -Ljava/lang/Runtime;->mLibPaths:[Ljava/lang/String; -Ljava/lang/Runtime;->nativeLoad(Ljava/lang/String;Ljava/lang/ClassLoader;)Ljava/lang/String; -Ljava/lang/Short;->value:S -Ljava/lang/StackTraceElement;->declaringClass:Ljava/lang/String; -Ljava/lang/StackTraceElement;->fileName:Ljava/lang/String; -Ljava/lang/StackTraceElement;->lineNumber:I -Ljava/lang/StackTraceElement;->methodName:Ljava/lang/String; -Ljava/lang/String;-><init>(II[C)V -Ljava/lang/String;->count:I -Ljava/lang/String;->getCharsNoCheck(II[CI)V -Ljava/lang/String;->hash:I -Ljava/lang/String;->indexOf([CII[CIII)I -Ljava/lang/String;->lastIndexOf([CII[CIII)I -Ljava/lang/System;-><init>()V -Ljava/lang/System;->arraycopy([CI[CII)V -Ljava/lang/System;->arraycopy([FI[FII)V -Ljava/lang/System;->arraycopy([II[III)V -Ljava/lang/System;->arraycopy([JI[JII)V -Ljava/lang/System;->arraycopy([SI[SII)V -Ljava/lang/System;->arraycopy([ZI[ZII)V -Ljava/lang/System;->log(CLjava/lang/String;Ljava/lang/Throwable;)V -Ljava/lang/System;->logE(Ljava/lang/String;)V -Ljava/lang/System;->logE(Ljava/lang/String;Ljava/lang/Throwable;)V -Ljava/lang/System;->logW(Ljava/lang/String;Ljava/lang/Throwable;)V -Ljava/lang/Thread;-><init>(Ljava/lang/ThreadGroup;Ljava/lang/String;IZ)V -Ljava/lang/Thread;->contextClassLoader:Ljava/lang/ClassLoader; -Ljava/lang/Thread;->daemon:Z -Ljava/lang/Thread;->dispatchUncaughtException(Ljava/lang/Throwable;)V -Ljava/lang/Thread;->getUncaughtExceptionPreHandler()Ljava/lang/Thread$UncaughtExceptionHandler; -Ljava/lang/Thread;->group:Ljava/lang/ThreadGroup; -Ljava/lang/Thread;->inheritableThreadLocals:Ljava/lang/ThreadLocal$ThreadLocalMap; -Ljava/lang/Thread;->inheritedAccessControlContext:Ljava/security/AccessControlContext; -Ljava/lang/Thread;->lock:Ljava/lang/Object; -Ljava/lang/Thread;->name:Ljava/lang/String; -Ljava/lang/Thread;->nativePeer:J -Ljava/lang/Thread;->parkBlocker:Ljava/lang/Object; -Ljava/lang/Thread;->priority:I -Ljava/lang/Thread;->target:Ljava/lang/Runnable; -Ljava/lang/Thread;->threadLocals:Ljava/lang/ThreadLocal$ThreadLocalMap; -Ljava/lang/Thread;->threadSeqNumber:J -Ljava/lang/ThreadGroup;->add(Ljava/lang/Thread;)V -Ljava/lang/ThreadGroup;->groups:[Ljava/lang/ThreadGroup; -Ljava/lang/ThreadGroup;->mainThreadGroup:Ljava/lang/ThreadGroup; -Ljava/lang/ThreadGroup;->name:Ljava/lang/String; -Ljava/lang/ThreadGroup;->ngroups:I -Ljava/lang/ThreadGroup;->parent:Ljava/lang/ThreadGroup; -Ljava/lang/ThreadGroup;->systemThreadGroup:Ljava/lang/ThreadGroup; -Ljava/lang/ThreadGroup;->threadTerminated(Ljava/lang/Thread;)V -Ljava/lang/ThreadLocal;->getMap(Ljava/lang/Thread;)Ljava/lang/ThreadLocal$ThreadLocalMap; -Ljava/lang/Throwable;->backtrace:Ljava/lang/Object; -Ljava/lang/Throwable;->cause:Ljava/lang/Throwable; -Ljava/lang/Throwable;->detailMessage:Ljava/lang/String; -Ljava/lang/Throwable;->getOurStackTrace()[Ljava/lang/StackTraceElement; -Ljava/lang/Throwable;->nativeFillInStackTrace()Ljava/lang/Object; -Ljava/lang/Throwable;->printStackTrace(Ljava/lang/Throwable$PrintStreamOrWriter;)V -Ljava/lang/Throwable;->stackTrace:[Ljava/lang/StackTraceElement; -Ljava/lang/Throwable;->suppressedExceptions:Ljava/util/List; -Ljava/lang/Void;-><init>()V -Ljava/net/Authenticator;->theAuthenticator:Ljava/net/Authenticator; -Ljava/net/DatagramSocket;->getFileDescriptor$()Ljava/io/FileDescriptor; -Ljava/net/DatagramSocket;->impl:Ljava/net/DatagramSocketImpl; -Ljava/net/HttpCookie;->assignors:Ljava/util/Map; -Ljava/net/HttpCookie;->comment:Ljava/lang/String; -Ljava/net/HttpCookie;->commentURL:Ljava/lang/String; -Ljava/net/HttpCookie;->domain:Ljava/lang/String; -Ljava/net/HttpCookie;->header:Ljava/lang/String; -Ljava/net/HttpCookie;->httpOnly:Z -Ljava/net/HttpCookie;->maxAge:J -Ljava/net/HttpCookie;->name:Ljava/lang/String; -Ljava/net/HttpCookie;->path:Ljava/lang/String; -Ljava/net/HttpCookie;->portlist:Ljava/lang/String; -Ljava/net/HttpCookie;->secure:Z -Ljava/net/HttpCookie;->toDiscard:Z -Ljava/net/HttpCookie;->tspecials:Ljava/lang/String; -Ljava/net/HttpCookie;->value:Ljava/lang/String; -Ljava/net/HttpCookie;->version:I -Ljava/net/HttpCookie;->whenCreated:J -Ljava/net/Inet4Address;-><init>()V -Ljava/net/Inet6Address$Inet6AddressHolder;->ipaddress:[B -Ljava/net/Inet6Address$Inet6AddressHolder;->scope_id:I -Ljava/net/Inet6Address$Inet6AddressHolder;->scope_id_set:Z -Ljava/net/Inet6Address$Inet6AddressHolder;->scope_ifname:Ljava/net/NetworkInterface; -Ljava/net/Inet6Address;-><init>()V -Ljava/net/Inet6Address;->holder6:Ljava/net/Inet6Address$Inet6AddressHolder; -Ljava/net/InetAddress$InetAddressHolder;->address:I -Ljava/net/InetAddress$InetAddressHolder;->family:I -Ljava/net/InetAddress$InetAddressHolder;->hostName:Ljava/lang/String; -Ljava/net/InetAddress$InetAddressHolder;->originalHostName:Ljava/lang/String; -Ljava/net/InetAddress;->clearDnsCache()V -Ljava/net/InetAddress;->getAllByNameOnNet(Ljava/lang/String;I)[Ljava/net/InetAddress; -Ljava/net/InetAddress;->holder()Ljava/net/InetAddress$InetAddressHolder; -Ljava/net/InetAddress;->holder:Ljava/net/InetAddress$InetAddressHolder; -Ljava/net/InetAddress;->isNumeric(Ljava/lang/String;)Z -Ljava/net/InetAddress;->parseNumericAddress(Ljava/lang/String;)Ljava/net/InetAddress; -Ljava/net/InetSocketAddress;->holder:Ljava/net/InetSocketAddress$InetSocketAddressHolder; -Ljava/net/InterfaceAddress;-><init>()V -Ljava/net/Proxy;-><init>()V -Ljava/net/ServerSocket;->factory:Ljava/net/SocketImplFactory; -Ljava/net/Socket;->factory:Ljava/net/SocketImplFactory; -Ljava/net/Socket;->getFileDescriptor$()Ljava/io/FileDescriptor; -Ljava/net/Socket;->impl:Ljava/net/SocketImpl; -Ljava/net/SocketException;-><init>(Ljava/lang/String;Ljava/lang/Throwable;)V -Ljava/net/SocketImpl;->serverSocket:Ljava/net/ServerSocket; -Ljava/net/SocketImpl;->socket:Ljava/net/Socket; -Ljava/net/SocksSocketImpl;-><init>()V -Ljava/net/URI;->fragment:Ljava/lang/String; -Ljava/net/URI;->host:Ljava/lang/String; -Ljava/net/URI;->port:I -Ljava/net/URI;->query:Ljava/lang/String; -Ljava/net/URI;->string:Ljava/lang/String; -Ljava/net/URL;->factory:Ljava/net/URLStreamHandlerFactory; -Ljava/net/URL;->handler:Ljava/net/URLStreamHandler; -Ljava/net/URL;->handlers:Ljava/util/Hashtable; -Ljava/net/URL;->protocol:Ljava/lang/String; -Ljava/net/URLClassLoader;->acc:Ljava/security/AccessControlContext; -Ljava/net/URLClassLoader;->ucp:Lsun/misc/URLClassPath; -Ljava/nio/Buffer;->address:J -Ljava/nio/Buffer;->capacity:I -Ljava/nio/Buffer;->limit:I -Ljava/nio/Buffer;->position:I -Ljava/nio/Buffer;->_elementSizeShift:I -Ljava/nio/ByteBuffer;->hb:[B -Ljava/nio/ByteBuffer;->isReadOnly:Z -Ljava/nio/ByteBuffer;->offset:I -Ljava/nio/CharBuffer;->toString(II)Ljava/lang/String; -Ljava/nio/charset/Charset;->defaultCharset:Ljava/nio/charset/Charset; -Ljava/nio/charset/CharsetEncoder;->canEncode(Ljava/nio/CharBuffer;)Z -Ljava/nio/DirectByteBuffer;-><init>(JI)V -Ljava/security/KeyPairGenerator;->getInstance(Lsun/security/jca/GetInstance$Instance;Ljava/lang/String;)Ljava/security/KeyPairGenerator; -Ljava/security/KeyStore;->keyStoreSpi:Ljava/security/KeyStoreSpi; -Ljava/security/Signature;->getInstance(Lsun/security/jca/GetInstance$Instance;Ljava/lang/String;)Ljava/security/Signature; -Ljava/security/spec/ECParameterSpec;->getCurveName()Ljava/lang/String; -Ljava/security/spec/ECParameterSpec;->setCurveName(Ljava/lang/String;)V -Ljava/text/Collator;->icuColl:Landroid/icu/text/Collator; -Ljava/text/DateFormat;->is24Hour:Ljava/lang/Boolean; -Ljava/text/DecimalFormatSymbols;->getPercentString()Ljava/lang/String; -Ljava/text/NumberFormat;->getInstance(Ljava/util/Locale;I)Ljava/text/NumberFormat; -Ljava/time/Duration;->toSeconds()Ljava/math/BigDecimal; -Ljava/time/OffsetDateTime;-><init>(Ljava/time/LocalDateTime;Ljava/time/ZoneOffset;)V -Ljava/time/ZoneId;->of(Ljava/lang/String;Z)Ljava/time/ZoneId; -Ljava/util/ArrayDeque;->elements:[Ljava/lang/Object; -Ljava/util/ArrayDeque;->head:I -Ljava/util/ArrayDeque;->tail:I -Ljava/util/ArrayList$SubList;->offset:I -Ljava/util/ArrayList$SubList;->parent:Ljava/util/AbstractList; -Ljava/util/ArrayList$SubList;->parentOffset:I -Ljava/util/ArrayList$SubList;->size:I -Ljava/util/ArrayList;->elementData:[Ljava/lang/Object; -Ljava/util/ArrayList;->size:I -Ljava/util/Arrays$ArrayList;->a:[Ljava/lang/Object; -Ljava/util/Arrays;->deepToString([Ljava/lang/Object;Ljava/lang/StringBuilder;Ljava/util/Set;)V -Ljava/util/Calendar;->zone:Ljava/util/TimeZone; -Ljava/util/Collections$EmptyList;-><init>()V -Ljava/util/Collections$EmptyMap;-><init>()V -Ljava/util/Collections$SynchronizedCollection;->c:Ljava/util/Collection; -Ljava/util/Collections$SynchronizedList;->list:Ljava/util/List; -Ljava/util/Collections$SynchronizedMap;->m:Ljava/util/Map; -Ljava/util/Collections$UnmodifiableCollection;->c:Ljava/util/Collection; -Ljava/util/Collections$UnmodifiableMap;->m:Ljava/util/Map; -Ljava/util/concurrent/atomic/AtomicInteger;->value:I -Ljava/util/concurrent/ConcurrentHashMap$BaseIterator;->hasMoreElements()Z -Ljava/util/concurrent/CopyOnWriteArrayList;->elements:[Ljava/lang/Object; -Ljava/util/concurrent/CopyOnWriteArraySet;->al:Ljava/util/concurrent/CopyOnWriteArrayList; -Ljava/util/concurrent/Executors$RunnableAdapter;->task:Ljava/lang/Runnable; -Ljava/util/concurrent/FutureTask;->callable:Ljava/util/concurrent/Callable; -Ljava/util/concurrent/FutureTask;->EXCEPTIONAL:I -Ljava/util/concurrent/FutureTask;->outcome:Ljava/lang/Object; -Ljava/util/concurrent/FutureTask;->state:I -Ljava/util/concurrent/LinkedBlockingDeque;->first:Ljava/util/concurrent/LinkedBlockingDeque$Node; -Ljava/util/concurrent/LinkedBlockingDeque;->lock:Ljava/util/concurrent/locks/ReentrantLock; -Ljava/util/concurrent/LinkedBlockingQueue;->capacity:I -Ljava/util/concurrent/LinkedBlockingQueue;->head:Ljava/util/concurrent/LinkedBlockingQueue$Node; -Ljava/util/concurrent/LinkedBlockingQueue;->putLock:Ljava/util/concurrent/locks/ReentrantLock; -Ljava/util/concurrent/LinkedBlockingQueue;->takeLock:Ljava/util/concurrent/locks/ReentrantLock; -Ljava/util/concurrent/locks/ReentrantLock;->sync:Ljava/util/concurrent/locks/ReentrantLock$Sync; -Ljava/util/concurrent/PriorityBlockingQueue;->dequeue()Ljava/lang/Object; -Ljava/util/concurrent/PriorityBlockingQueue;->lock:Ljava/util/concurrent/locks/ReentrantLock; -Ljava/util/concurrent/PriorityBlockingQueue;->notEmpty:Ljava/util/concurrent/locks/Condition; -Ljava/util/concurrent/ThreadPoolExecutor;->allowCoreThreadTimeOut:Z -Ljava/util/concurrent/ThreadPoolExecutor;->ctl:Ljava/util/concurrent/atomic/AtomicInteger; -Ljava/util/concurrent/ThreadPoolExecutor;->defaultHandler:Ljava/util/concurrent/RejectedExecutionHandler; -Ljava/util/concurrent/ThreadPoolExecutor;->mainLock:Ljava/util/concurrent/locks/ReentrantLock; -Ljava/util/EnumMap;->keyType:Ljava/lang/Class; -Ljava/util/EnumSet;->elementType:Ljava/lang/Class; -Ljava/util/HashMap$HashIterator;->hasNext()Z -Ljava/util/HashMap$HashIterator;->remove()V -Ljava/util/HashMap$Node;->key:Ljava/lang/Object; -Ljava/util/HashMap$Node;->next:Ljava/util/HashMap$Node; -Ljava/util/HashMap$Node;->value:Ljava/lang/Object; -Ljava/util/HashMap;->modCount:I -Ljava/util/HashMap;->table:[Ljava/util/HashMap$Node; -Ljava/util/HashSet;->map:Ljava/util/HashMap; -Ljava/util/jar/JarFile;->manifest:Ljava/util/jar/Manifest; -Ljava/util/LinkedHashMap$LinkedHashIterator;->hasNext()Z -Ljava/util/LinkedHashMap;->accessOrder:Z -Ljava/util/LinkedHashMap;->eldest()Ljava/util/Map$Entry; -Ljava/util/LinkedList$Node;->item:Ljava/lang/Object; -Ljava/util/LinkedList$Node;->next:Ljava/util/LinkedList$Node; -Ljava/util/LinkedList;->first:Ljava/util/LinkedList$Node; -Ljava/util/LinkedList;->size:I -Ljava/util/Locale;->createConstant(Ljava/lang/String;Ljava/lang/String;)Ljava/util/Locale; -Ljava/util/Locale;->getInstance(Lsun/util/locale/BaseLocale;Lsun/util/locale/LocaleExtensions;)Ljava/util/Locale; -Ljava/util/logging/Handler;->sealed:Z -Ljava/util/logging/Logger;->treeLock:Ljava/lang/Object; -Ljava/util/logging/LogManager;->getFormatterProperty(Ljava/lang/String;Ljava/util/logging/Formatter;)Ljava/util/logging/Formatter; -Ljava/util/PriorityQueue;->modCount:I -Ljava/util/PriorityQueue;->queue:[Ljava/lang/Object; -Ljava/util/PriorityQueue;->size:I -Ljava/util/Properties;->saveConvert(Ljava/lang/String;ZZ)Ljava/lang/String; -Ljava/util/Random;->seedUniquifier()J -Ljava/util/regex/Matcher;->appendPos:I -Ljava/util/TimerTask;->period:J -Ljava/util/UUID;->leastSigBits:J -Ljava/util/UUID;->mostSigBits:J -Ljava/util/Vector;->elementData(I)Ljava/lang/Object; -Ljava/util/zip/Adler32;->update(II)I -Ljava/util/zip/CRC32;->update(II)I -Ljava/util/zip/Deflater;->buf:[B -Ljava/util/zip/Deflater;->finish:Z -Ljava/util/zip/Deflater;->finished:Z -Ljava/util/zip/Deflater;->len:I -Ljava/util/zip/Deflater;->level:I -Ljava/util/zip/Deflater;->off:I -Ljava/util/zip/Deflater;->setParams:Z -Ljava/util/zip/Deflater;->strategy:I -Ljava/util/zip/Inflater;->buf:[B -Ljava/util/zip/Inflater;->finished:Z -Ljava/util/zip/Inflater;->len:I -Ljava/util/zip/Inflater;->needDict:Z -Ljava/util/zip/Inflater;->off:I -Ljava/util/zip/ZipEntry;-><init>(Ljava/lang/String;Ljava/lang/String;JJJII[BJ)V -Ljava/util/zip/ZipEntry;->method:I -Ljava/util/zip/ZipFile;->close(J)V -Ljava/util/zip/ZipFile;->getEntry(J[BZ)J -Ljava/util/zip/ZipFile;->jzfile:J -Ljava/util/zip/ZipInputStream;->flag:I -Ljava/util/zip/ZipInputStream;->tmpbuf:[B -Ljava/util/zip/ZipOutputStream;->method:I -Ljava/util/zip/ZipOutputStream;->names:Ljava/util/HashSet; -Ljava/util/zip/ZipOutputStream;->written:J -Ljavax/net/ssl/SSLServerSocketFactory;->defaultServerSocketFactory:Ljavax/net/ssl/SSLServerSocketFactory; -Ljavax/net/ssl/SSLSocketFactory;->createSocket(Ljava/net/Socket;Ljava/io/InputStream;Z)Ljava/net/Socket; -Ljavax/net/ssl/SSLSocketFactory;->defaultSocketFactory:Ljavax/net/ssl/SSLSocketFactory; Lorg/ccil/cowan/tagsoup/AttributesImpl;->data:[Ljava/lang/String; Lorg/ccil/cowan/tagsoup/AttributesImpl;->length:I Lorg/ccil/cowan/tagsoup/ElementType;->theAtts:Lorg/ccil/cowan/tagsoup/AttributesImpl; diff --git a/config/hiddenapi-vendor-list.txt b/config/hiddenapi-vendor-list.txt index 4275bd47f1ad..575ba340f52a 100644 --- a/config/hiddenapi-vendor-list.txt +++ b/config/hiddenapi-vendor-list.txt @@ -245,7 +245,3 @@ Lcom/android/internal/location/ILocationProvider;->setRequest(Lcom/android/inter Lcom/android/internal/R$styleable;->NumberPicker:[I Lcom/android/internal/R$styleable;->TwoLineListItem:[I Lcom/android/internal/telephony/ITelephony;->getDataEnabled(I)Z -Ljava/lang/System;->arraycopy([BI[BII)V -Ljava/net/Inet4Address;->ALL:Ljava/net/InetAddress; -Ljava/net/Inet4Address;->ANY:Ljava/net/InetAddress; -Ljava/net/Inet6Address;->ANY:Ljava/net/InetAddress; diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java index a30ae799bd3d..a4f1db336ba5 100644 --- a/core/java/android/app/AppOpsManager.java +++ b/core/java/android/app/AppOpsManager.java @@ -18,6 +18,7 @@ package android.app; import android.Manifest; import android.annotation.NonNull; +import android.annotation.Nullable; import android.annotation.RequiresPermission; import android.annotation.SystemApi; import android.annotation.SystemService; @@ -33,6 +34,7 @@ import android.os.Parcelable; import android.os.Process; import android.os.RemoteException; import android.os.UserManager; +import android.provider.Settings; import android.util.ArrayMap; import com.android.internal.app.IAppOpsActiveCallback; @@ -1537,6 +1539,19 @@ public class AppOpsManager { } /** + * Retrieve the permission associated with an operation, or null if there is not one. + * + * @param op The operation name. + * + * @hide + */ + @Nullable + @SystemApi + public static String opToPermission(@NonNull String op) { + return opToPermission(strOpToOp(op)); + } + + /** * Retrieve the user restriction associated with an operation, or null if there is not one. * @hide */ @@ -1570,6 +1585,26 @@ public class AppOpsManager { * @hide */ public static int opToDefaultMode(int op) { + // STOPSHIP b/118520006: Hardcode the default values once the feature is stable. + switch (op) { + // SMS permissions + case AppOpsManager.OP_SEND_SMS: + case AppOpsManager.OP_RECEIVE_SMS: + case AppOpsManager.OP_READ_SMS: + case AppOpsManager.OP_RECEIVE_WAP_PUSH: + case AppOpsManager.OP_RECEIVE_MMS: + case AppOpsManager.OP_READ_CELL_BROADCASTS: + // CallLog permissions + case AppOpsManager.OP_READ_CALL_LOG: + case AppOpsManager.OP_WRITE_CALL_LOG: + case AppOpsManager.OP_PROCESS_OUTGOING_CALLS: { + // ActivityThread.currentApplication() is never null + if (Settings.Global.getInt(ActivityThread.currentApplication().getContentResolver(), + Settings.Global.SMS_ACCESS_RESTRICTION_ENABLED, 0) == 1) { + return AppOpsManager.MODE_DEFAULT; + } + } + } return sOpDefaultMode[op]; } @@ -1982,6 +2017,26 @@ public class AppOpsManager { } } + /** + * Resets given app op in its default mode for app ops in the UID. + * This applies to all apps currently in the UID or installed in this UID in the future. + * + * @param appOp The app op. + * @param uid The UID for which to set the app. + * + * @hide + */ + @RequiresPermission(android.Manifest.permission.MANAGE_APP_OPS_MODES) + @SystemApi + public void resetUidMode(String appOp, int uid, boolean force) { + int code = strOpToOp(appOp); + if (!(opAllowsReset(code) || force)) { + return; + } + int mode = opToDefaultMode(code); + setUidMode(code, uid, mode); + } + /** @hide */ public void setUserRestriction(int code, boolean restricted, IBinder token) { setUserRestriction(code, restricted, token, /*exceptionPackages*/null); diff --git a/core/java/android/app/InstantAppResolverService.java b/core/java/android/app/InstantAppResolverService.java index 8f83ee3e2779..b1541c6b8780 100644 --- a/core/java/android/app/InstantAppResolverService.java +++ b/core/java/android/app/InstantAppResolverService.java @@ -240,7 +240,7 @@ public abstract class InstantAppResolverService extends Service { args.arg4 = token; args.arg5 = sanitizedIntent; mHandler.obtainMessage(ServiceHandler.MSG_GET_INSTANT_APP_INTENT_FILTER, - callback).sendToTarget(); + args).sendToTarget(); } }; } diff --git a/core/java/android/app/role/IRoleManager.aidl b/core/java/android/app/role/IRoleManager.aidl new file mode 100644 index 000000000000..64f69c182f78 --- /dev/null +++ b/core/java/android/app/role/IRoleManager.aidl @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.app.role; + +import android.app.role.IRoleManagerCallback; + +/** + * @hide + */ +interface IRoleManager { + + boolean isRoleAvailable(in String roleName); + + boolean isRoleHeld(in String roleName, in String packageName); + + List<String> getRoleHoldersAsUser(in String roleName, int userId); + + void addRoleHolderAsUser(in String roleName, in String packageName, int userId, + in IRoleManagerCallback callback); + + void removeRoleHolderAsUser(in String roleName, in String packageName, int userId, + in IRoleManagerCallback callback); + + void clearRoleHoldersAsUser(in String roleName, int userId, in IRoleManagerCallback callback); +} diff --git a/core/java/android/app/role/IRoleManagerCallback.aidl b/core/java/android/app/role/IRoleManagerCallback.aidl new file mode 100644 index 000000000000..c0f8eea51a25 --- /dev/null +++ b/core/java/android/app/role/IRoleManagerCallback.aidl @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.app.role; + +/** + * @hide + */ +oneway interface IRoleManagerCallback { + + void onSuccess(); + + void onFailure(); +} diff --git a/core/java/android/app/role/RoleManager.java b/core/java/android/app/role/RoleManager.java new file mode 100644 index 000000000000..f7c6dea21e85 --- /dev/null +++ b/core/java/android/app/role/RoleManager.java @@ -0,0 +1,348 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.app.role; + +import android.Manifest; +import android.annotation.CallbackExecutor; +import android.annotation.NonNull; +import android.annotation.RequiresPermission; +import android.annotation.SystemApi; +import android.annotation.SystemService; +import android.app.Activity; +import android.content.Context; +import android.content.Intent; +import android.os.Binder; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.os.UserHandle; +import android.util.ArraySet; + +import com.android.internal.util.Preconditions; + +import java.util.List; +import java.util.Set; +import java.util.concurrent.Executor; + +/** + * This class provides information about and manages roles. + * <p> + * A role is a unique name within the system associated with certain privileges. The list of + * available roles might change with a system app update, so apps should not make assumption about + * the availability of roles. Instead, they should always query if the role is available using + * {@link #isRoleAvailable(String)} before trying to do anything with it. Some predefined role names + * are available as constants in this class, and a list of possibly available roles can be found in + * the AndroidX Libraries. + * <p> + * There can be multiple applications qualifying for a role, but only a subset of them can become + * role holders. To qualify for a role, an application must meet certain requirements, including + * defining certain components in its manifest. These requirements can be found in the AndroidX + * Libraries. Then the application will need user consent to become a role holder, which can be + * requested using {@link Activity#startActivityForResult(Intent, int)} with the {@code Intent} + * obtained from {@link #createRequestRoleIntent(String)}. + * <p> + * Upon becoming a role holder, the application may be granted certain privileges that are role + * specific. When the application loses its role, these privileges will also be revoked. + */ +@SystemService(Context.ROLE_SERVICE) +public final class RoleManager { + + private static final String LOG_TAG = RoleManager.class.getSimpleName(); + + /** + * The name of the dialer role. + */ + public static final String ROLE_DIALER = "android.app.role.DIALER"; + + /** + * The name of the SMS role. + */ + public static final String ROLE_SMS = "android.app.role.SMS"; + + /** + * The action used to request user approval of a role for an application. + * + * @hide + */ + public static final String ACTION_REQUEST_ROLE = "android.app.role.action.REQUEST_ROLE"; + + /** + * The name of the requested role. + * <p> + * <strong>Type:</strong> String + * + * @hide + */ + @SystemApi + public static final String EXTRA_REQUEST_ROLE_NAME = "android.app.role.extra.REQUEST_ROLE_NAME"; + + @NonNull + private final Context mContext; + + @NonNull + private final IRoleManager mService; + + /** + * @hide + */ + public RoleManager(@NonNull Context context) throws ServiceManager.ServiceNotFoundException { + mContext = context; + mService = IRoleManager.Stub.asInterface(ServiceManager.getServiceOrThrow( + Context.ROLE_SERVICE)); + } + + /** + * Returns an {@code Intent} suitable for passing to {@link Activity#startActivityForResult( + * Intent, int)} which prompts the user to grant a role to this application. + * <p> + * If the role is granted, the {@code resultCode} will be {@link Activity#RESULT_OK}, otherwise + * it will be {@link Activity#RESULT_CANCELED}. + * + * @param roleName the name of requested role + * + * @return the {@code Intent} to prompt user to grant the role + * + * @throws IllegalArgumentException if {@code role} is {@code null} or empty + */ + @NonNull + public Intent createRequestRoleIntent(@NonNull String roleName) { + Preconditions.checkStringNotEmpty(roleName, "roleName cannot be null or empty"); + Intent intent = new Intent(ACTION_REQUEST_ROLE); + intent.setPackage(mContext.getPackageManager().getPermissionControllerPackageName()); + intent.putExtra(EXTRA_REQUEST_ROLE_NAME, roleName); + return intent; + } + + /** + * Check whether a role is available in the system. + * + * @param roleName the name of role to checking for + * + * @return whether the role is available in the system + * + * @throws IllegalArgumentException if the role name is {@code null} or empty + */ + public boolean isRoleAvailable(@NonNull String roleName) { + Preconditions.checkStringNotEmpty(roleName, "roleName cannot be null or empty"); + try { + return mService.isRoleAvailable(roleName); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Check whether the calling application is holding a particular role. + * + * @param roleName the name of the role to check for + * + * @return whether the calling application is holding the role + * + * @throws IllegalArgumentException if the role name is {@code null} or empty. + */ + public boolean isRoleHeld(@NonNull String roleName) { + Preconditions.checkStringNotEmpty(roleName, "roleName cannot be null or empty"); + try { + return mService.isRoleHeld(roleName, mContext.getPackageName()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Get package names of the applications holding the role. + * <p> + * <strong>Note: </strong>Using this API requires holding + * {@code android.permission.MANAGE_ROLE_HOLDERS} and if the user id is not the current user + * {@code android.permission.INTERACT_ACROSS_USERS_FULL}. + * + * @param roleName the name of the role to get the role holder for + * @param user the user to get the role holder for + * + * @return the package name of the role holder, or {@code null} if none. + * + * @throws IllegalArgumentException if the role name is {@code null} or empty. + * + * @see #addRoleHolderAsUser(String, String, UserHandle, Executor, RoleManagerCallback) + * @see #removeRoleHolderAsUser(String, String, UserHandle, Executor, RoleManagerCallback) + * @see #clearRoleHoldersAsUser(String, UserHandle, Executor, RoleManagerCallback) + * + * @hide + */ + @NonNull + @RequiresPermission(Manifest.permission.MANAGE_ROLE_HOLDERS) + @SystemApi + public Set<String> getRoleHoldersAsUser(@NonNull String roleName, @NonNull UserHandle user) { + Preconditions.checkStringNotEmpty(roleName, "roleName cannot be null or empty"); + Preconditions.checkNotNull(user, "user cannot be null"); + List<String> roleHolders; + try { + roleHolders = mService.getRoleHoldersAsUser(roleName, user.getIdentifier()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + return new ArraySet<>(roleHolders); + } + + /** + * Add a specific application to the holders of a role. If the role is exclusive, the previous + * holder will be replaced. + * <p> + * <strong>Note: </strong>Using this API requires holding + * {@code android.permission.MANAGE_ROLE_HOLDERS} and if the user id is not the current user + * {@code android.permission.INTERACT_ACROSS_USERS_FULL}. + * + * @param roleName the name of the role to add the role holder for + * @param packageName the package name of the application to add to the role holders + * @param user the user to add the role holder for + * @param executor the {@code Executor} to run the callback on. + * @param callback the callback for whether this call is successful + * + * @throws IllegalArgumentException if the role name or package name is {@code null} or empty. + * + * @see #getRoleHoldersAsUser(String, UserHandle) + * @see #removeRoleHolderAsUser(String, String, UserHandle, Executor, RoleManagerCallback) + * @see #clearRoleHoldersAsUser(String, UserHandle, Executor, RoleManagerCallback) + * + * @hide + */ + @RequiresPermission(Manifest.permission.MANAGE_ROLE_HOLDERS) + @SystemApi + public void addRoleHolderAsUser(@NonNull String roleName, @NonNull String packageName, + @NonNull UserHandle user, @CallbackExecutor @NonNull Executor executor, + @NonNull RoleManagerCallback callback) { + Preconditions.checkStringNotEmpty(roleName, "roleName cannot be null or empty"); + Preconditions.checkStringNotEmpty(packageName, "packageName cannot be null or empty"); + Preconditions.checkNotNull(user, "user cannot be null"); + Preconditions.checkNotNull(executor, "executor cannot be null"); + Preconditions.checkNotNull(callback, "callback cannot be null"); + try { + mService.addRoleHolderAsUser(roleName, packageName, user.getIdentifier(), + new RoleManagerCallbackDelegate(executor, callback)); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Remove a specific application from the holders of a role. + * <p> + * <strong>Note: </strong>Using this API requires holding + * {@code android.permission.MANAGE_ROLE_HOLDERS} and if the user id is not the current user + * {@code android.permission.INTERACT_ACROSS_USERS_FULL}. + * + * @param roleName the name of the role to remove the role holder for + * @param packageName the package name of the application to remove from the role holders + * @param user the user to remove the role holder for + * @param executor the {@code Executor} to run the callback on. + * @param callback the callback for whether this call is successful + * + * @throws IllegalArgumentException if the role name or package name is {@code null} or empty. + * + * @see #getRoleHoldersAsUser(String, UserHandle) + * @see #addRoleHolderAsUser(String, String, UserHandle, Executor, RoleManagerCallback) + * @see #clearRoleHoldersAsUser(String, UserHandle, Executor, RoleManagerCallback) + * + * @hide + */ + @RequiresPermission(Manifest.permission.MANAGE_ROLE_HOLDERS) + @SystemApi + public void removeRoleHolderAsUser(@NonNull String roleName, @NonNull String packageName, + @NonNull UserHandle user, @CallbackExecutor @NonNull Executor executor, + @NonNull RoleManagerCallback callback) { + Preconditions.checkStringNotEmpty(roleName, "roleName cannot be null or empty"); + Preconditions.checkStringNotEmpty(packageName, "packageName cannot be null or empty"); + Preconditions.checkNotNull(user, "user cannot be null"); + Preconditions.checkNotNull(executor, "executor cannot be null"); + Preconditions.checkNotNull(callback, "callback cannot be null"); + try { + mService.removeRoleHolderAsUser(roleName, packageName, user.getIdentifier(), + new RoleManagerCallbackDelegate(executor, callback)); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Remove all holders of a role. + * <p> + * <strong>Note: </strong>Using this API requires holding + * {@code android.permission.MANAGE_ROLE_HOLDERS} and if the user id is not the current user + * {@code android.permission.INTERACT_ACROSS_USERS_FULL}. + * + * @param roleName the name of the role to remove role holders for + * @param user the user to remove role holders for + * @param executor the {@code Executor} to run the callback on. + * @param callback the callback for whether this call is successful + * + * @throws IllegalArgumentException if the role name is {@code null} or empty. + * + * @see #getRoleHoldersAsUser(String, UserHandle) + * @see #addRoleHolderAsUser(String, String, UserHandle, Executor, RoleManagerCallback) + * @see #removeRoleHolderAsUser(String, String, UserHandle, Executor, RoleManagerCallback) + * + * @hide + */ + @RequiresPermission(Manifest.permission.MANAGE_ROLE_HOLDERS) + @SystemApi + public void clearRoleHoldersAsUser(@NonNull String roleName, @NonNull UserHandle user, + @CallbackExecutor @NonNull Executor executor, @NonNull RoleManagerCallback callback) { + Preconditions.checkStringNotEmpty(roleName, "roleName cannot be null or empty"); + Preconditions.checkNotNull(user, "user cannot be null"); + Preconditions.checkNotNull(executor, "executor cannot be null"); + Preconditions.checkNotNull(callback, "callback cannot be null"); + try { + mService.clearRoleHoldersAsUser(roleName, user.getIdentifier(), + new RoleManagerCallbackDelegate(executor, callback)); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + private static class RoleManagerCallbackDelegate extends IRoleManagerCallback.Stub { + + @NonNull + private final Executor mExecutor; + @NonNull + private final RoleManagerCallback mCallback; + + RoleManagerCallbackDelegate(@NonNull Executor executor, + @NonNull RoleManagerCallback callback) { + mExecutor = executor; + mCallback = callback; + } + + @Override + public void onSuccess() { + long token = Binder.clearCallingIdentity(); + try { + mExecutor.execute(mCallback::onSuccess); + } finally { + Binder.restoreCallingIdentity(token); + } + } + + @Override + public void onFailure() { + long token = Binder.clearCallingIdentity(); + try { + mExecutor.execute(mCallback::onFailure); + } finally { + Binder.restoreCallingIdentity(token); + } + } + } +} diff --git a/core/java/android/app/role/RoleManagerCallback.java b/core/java/android/app/role/RoleManagerCallback.java new file mode 100644 index 000000000000..ca68ebc98303 --- /dev/null +++ b/core/java/android/app/role/RoleManagerCallback.java @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.app.role; + +import android.annotation.SystemApi; + +/** + * Callback for a {@link RoleManager} request. + * + * @hide + */ +@SystemApi +public interface RoleManagerCallback { + + /** + * Signals a success. + */ + void onSuccess(); + + /** + * Signals a failure. + */ + void onFailure(); +} diff --git a/core/java/android/app/usage/IUsageStatsManager.aidl b/core/java/android/app/usage/IUsageStatsManager.aidl index 971352783dcb..4d52263c1d78 100644 --- a/core/java/android/app/usage/IUsageStatsManager.aidl +++ b/core/java/android/app/usage/IUsageStatsManager.aidl @@ -51,4 +51,8 @@ interface IUsageStatsManager { void registerAppUsageObserver(int observerId, in String[] packages, long timeLimitMs, in PendingIntent callback, String callingPackage); void unregisterAppUsageObserver(int observerId, String callingPackage); + void registerUsageSessionObserver(int sessionObserverId, in String[] observed, long timeLimitMs, + long sessionThresholdTimeMs, in PendingIntent limitReachedCallbackIntent, + in PendingIntent sessionEndCallbackIntent, String callingPackage); + void unregisterUsageSessionObserver(int sessionObserverId, String callingPackage); } diff --git a/core/java/android/app/usage/UsageStatsManager.java b/core/java/android/app/usage/UsageStatsManager.java index dbb00eb5c288..6d7400e0ef73 100644 --- a/core/java/android/app/usage/UsageStatsManager.java +++ b/core/java/android/app/usage/UsageStatsManager.java @@ -18,6 +18,7 @@ package android.app.usage; import android.annotation.IntDef; import android.annotation.NonNull; +import android.annotation.Nullable; import android.annotation.RequiresPermission; import android.annotation.SystemApi; import android.annotation.SystemService; @@ -595,7 +596,7 @@ public final class UsageStatsManager { * exceeded by the group of apps. The delivered Intent will also contain * the extras {@link #EXTRA_OBSERVER_ID}, {@link #EXTRA_TIME_LIMIT} and * {@link #EXTRA_TIME_USED}. Cannot be null. - * @throws SecurityException if the caller doesn't have the OBSERVE_APP_USAGE permission or + * @throws SecurityException if the caller doesn't have the OBSERVE_APP_USAGE permission and * is not the profile owner of this user. */ @SystemApi @@ -616,7 +617,7 @@ public final class UsageStatsManager { * to any observer registered by this application. Unregistering an observer that was already * unregistered or never registered will have no effect. * @param observerId The id of the observer that was previously registered. - * @throws SecurityException if the caller doesn't have the OBSERVE_APP_USAGE permission or is + * @throws SecurityException if the caller doesn't have the OBSERVE_APP_USAGE permission and is * not the profile owner of this user. */ @SystemApi @@ -629,6 +630,81 @@ public final class UsageStatsManager { } } + /** + * Register a usage session observer that receives a callback on the provided {@code + * limitReachedCallbackIntent} when the sum of usages of apps in the packages array exceeds + * the {@code timeLimit} specified within a usage session. After the {@code timeLimit} has + * been reached, the usage session observer will receive a callback on the provided {@code + * sessionEndCallbackIntent} when the usage session ends. Registering another session + * observer against a {@code sessionObserverId} that has already been registered will + * override the previous session observer. + * + * @param sessionObserverId A unique id associated with the group of apps to be + * monitored. There can be multiple groups with common + * packages and different time limits. + * @param packages The list of packages to observe for foreground activity time. Cannot be null + * and must include at least one package. + * @param timeLimit The total time the set of apps can be used continuously before the {@code + * limitReachedCallbackIntent} is delivered. Must be at least one minute. + * @param timeUnit The unit for time specified in {@code timeLimit}. Cannot be null. + * @param sessionThresholdTime The time that can take place between usage sessions before the + * next session is considered a new session. Must be non-negative. + * @param sessionThresholdTimeUnit The unit for time specified in {@code sessionThreshold}. + * Cannot be null. + * @param limitReachedCallbackIntent The {@link PendingIntent} that will be dispatched when the + * time limit is exceeded by the group of apps. The delivered + * Intent will also contain the extras {@link + * #EXTRA_OBSERVER_ID}, {@link #EXTRA_TIME_LIMIT} and {@link + * #EXTRA_TIME_USED}. Cannot be null. + * @param sessionEndCallbackIntent The {@link PendingIntent} that will be dispatched when the + * session has ended after the time limit has been exceeded. The + * session is considered at its end after the {@code observed} + * usage has stopped and an additional {@code + * sessionThresholdTime} has passed. The delivered Intent will + * also contain the extras {@link #EXTRA_OBSERVER_ID} and {@link + * #EXTRA_TIME_USED}. Can be null. + * @throws SecurityException if the caller doesn't have the OBSERVE_APP_USAGE permission and + * is not the profile owner of this user. + * @hide + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.OBSERVE_APP_USAGE) + public void registerUsageSessionObserver(int sessionObserverId, @NonNull String[] packages, + long timeLimit, @NonNull TimeUnit timeUnit, long sessionThresholdTime, + @NonNull TimeUnit sessionThresholdTimeUnit, + @NonNull PendingIntent limitReachedCallbackIntent, + @Nullable PendingIntent sessionEndCallbackIntent) { + try { + mService.registerUsageSessionObserver(sessionObserverId, packages, + timeUnit.toMillis(timeLimit), + sessionThresholdTimeUnit.toMillis(sessionThresholdTime), + limitReachedCallbackIntent, sessionEndCallbackIntent, + mContext.getOpPackageName()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Unregister the usage session observer specified by the {@code sessionObserverId}. This will + * only apply to any app session observer registered by this application. Unregistering an + * observer that was already unregistered or never registered will have no effect. + * + * @param sessionObserverId The id of the observer that was previously registered. + * @throws SecurityException if the caller doesn't have the OBSERVE_APP_USAGE permission and + * is not the profile owner of this user. + * @hide + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.OBSERVE_APP_USAGE) + public void unregisterUsageSessionObserver(int sessionObserverId) { + try { + mService.unregisterUsageSessionObserver(sessionObserverId, mContext.getOpPackageName()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + /** @hide */ public static String reasonToString(int standbyReason) { StringBuilder sb = new StringBuilder(); diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index 31973524ceaf..713cc8ba0bd2 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -3061,6 +3061,7 @@ public abstract class Context { USER_SERVICE, RESTRICTIONS_SERVICE, APP_OPS_SERVICE, + ROLE_SERVICE, CAMERA_SERVICE, PRINT_SERVICE, CONSUMER_IR_SERVICE, @@ -4044,6 +4045,15 @@ public abstract class Context { public static final String APP_OPS_SERVICE = "appops"; /** + * Use with {@link #getSystemService(String)} to retrieve a {@link android.app.role.RoleManager} + * for managing roles. + * + * @see #getSystemService(String) + * @see android.app.role.RoleManager + */ + public static final String ROLE_SERVICE = "role"; + + /** * Use with {@link #getSystemService(String)} to retrieve a * {@link android.hardware.camera2.CameraManager} for interacting with * camera devices. diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java index 5ab643cb666c..6c0fa4c4d35c 100644 --- a/core/java/android/content/Intent.java +++ b/core/java/android/content/Intent.java @@ -23,6 +23,7 @@ import android.annotation.BroadcastBehavior; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.RequiresPermission; import android.annotation.SdkConstant; import android.annotation.SdkConstant.SdkConstantType; import android.annotation.SystemApi; @@ -1924,6 +1925,29 @@ public class Intent implements Parcelable, Cloneable { public static final String ACTION_REVIEW_PERMISSION_USAGE = "android.intent.action.REVIEW_PERMISSION_USAGE"; + /** + * Activity action: Launch UI to review uses of permissions for a single app. + * <p> + * Input: {@link #EXTRA_PACKAGE_NAME} specifies the package whose + * permissions will be reviewed (mandatory). + * </p> + * <p> + * Output: Nothing. + * </p> + * <p class="note"> + * This requires {@link android.Manifest.permission#GRANT_RUNTIME_PERMISSIONS} permission. + * </p> + * + * @see #EXTRA_PACKAGE_NAME + * + * @hide + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.GRANT_RUNTIME_PERMISSIONS) + @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) + public static final String ACTION_REVIEW_APP_PERMISSION_USAGE = + "android.intent.action.REVIEW_APP_PERMISSION_USAGE"; + // --------------------------------------------------------------------- // --------------------------------------------------------------------- // Standard intent broadcast actions (see action variable). diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index dfb8128e37ee..1f700f758dc8 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -5439,6 +5439,28 @@ public abstract class PackageManager { ComponentName[] set, ComponentName activity); /** + * Replaces an existing preferred activity mapping to the system, and if that were not present + * adds a new preferred activity. This will be used to automatically select the given activity + * component when {@link Context#startActivity(Intent) Context.startActivity()} finds multiple + * matching activities and also matches the given filter. + * + * @param filter The set of intents under which this activity will be made preferred. + * @param match The IntentFilter match category that this preference applies to. Should be a + * combination of {@link IntentFilter#MATCH_CATEGORY_MASK} and + * {@link IntentFilter#MATCH_ADJUSTMENT_MASK}). + * @param set The set of activities that the user was picking from when this preference was + * made. + * @param activity The component name of the activity that is to be preferred. + * + * @hide + */ + @SystemApi + public void replacePreferredActivity(@NonNull IntentFilter filter, int match, + @NonNull List<ComponentName> set, @NonNull ComponentName activity) { + replacePreferredActivity(filter, match, set.toArray(new ComponentName[0]), activity); + } + + /** * @hide */ @Deprecated diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java index d3b37ff08d0b..87c3075ee2e9 100644 --- a/core/java/android/content/pm/PackageParser.java +++ b/core/java/android/content/pm/PackageParser.java @@ -228,6 +228,24 @@ public class PackageParser { CHILD_PACKAGE_TAGS.add(TAG_EAT_COMMENT); } + // STOPSHIP(b/112545973): remove once feature enabled by default + private static final Set<String> FORCE_AUDIO_PACKAGES; + private static final Set<String> FORCE_VIDEO_PACKAGES; + private static final Set<String> FORCE_IMAGES_PACKAGES; + static { + FORCE_AUDIO_PACKAGES = parsePackageList( + SystemProperties.get(StorageManager.PROP_FORCE_AUDIO)); + FORCE_VIDEO_PACKAGES = parsePackageList( + SystemProperties.get(StorageManager.PROP_FORCE_VIDEO)); + FORCE_IMAGES_PACKAGES = parsePackageList( + SystemProperties.get(StorageManager.PROP_FORCE_IMAGES)); + } + + private static Set<String> parsePackageList(String pkgs) { + if (TextUtils.isEmpty(pkgs)) return Collections.emptySet(); + return new ArraySet<String>(Arrays.asList(pkgs.split(","))); + } + private static final boolean LOG_UNSAFE_BROADCASTS = false; /** @@ -2534,6 +2552,34 @@ public class PackageParser { } } } + } else { + if (FORCE_AUDIO_PACKAGES.contains(pkg.packageName)) { + pkg.requestedPermissions.add(android.Manifest.permission.READ_MEDIA_AUDIO); + pkg.requestedPermissions.add(android.Manifest.permission.WRITE_MEDIA_AUDIO); + } + if (FORCE_VIDEO_PACKAGES.contains(pkg.packageName)) { + pkg.requestedPermissions.add(android.Manifest.permission.READ_MEDIA_VIDEO); + pkg.requestedPermissions.add(android.Manifest.permission.WRITE_MEDIA_VIDEO); + } + if (FORCE_IMAGES_PACKAGES.contains(pkg.packageName)) { + pkg.requestedPermissions.add(android.Manifest.permission.READ_MEDIA_IMAGES); + pkg.requestedPermissions.add(android.Manifest.permission.WRITE_MEDIA_IMAGES); + } + + if (SystemProperties.getBoolean(StorageManager.PROP_FORCE_LEGACY, false)) { + if (pkg.requestedPermissions + .contains(android.Manifest.permission.READ_EXTERNAL_STORAGE)) { + pkg.requestedPermissions.add(android.Manifest.permission.READ_MEDIA_AUDIO); + pkg.requestedPermissions.add(android.Manifest.permission.READ_MEDIA_VIDEO); + pkg.requestedPermissions.add(android.Manifest.permission.READ_MEDIA_IMAGES); + } + if (pkg.requestedPermissions + .contains(android.Manifest.permission.WRITE_EXTERNAL_STORAGE)) { + pkg.requestedPermissions.add(android.Manifest.permission.WRITE_MEDIA_AUDIO); + pkg.requestedPermissions.add(android.Manifest.permission.WRITE_MEDIA_VIDEO); + pkg.requestedPermissions.add(android.Manifest.permission.WRITE_MEDIA_IMAGES); + } + } } return pkg; @@ -3883,10 +3929,13 @@ public class PackageParser { } } - // Add a hidden app detail activity which forwards user to App Details page. - Activity a = generateAppDetailsHiddenActivity(owner, flags, outError, - owner.baseHardwareAccelerated); - owner.activities.add(a); + if (TextUtils.isEmpty(owner.staticSharedLibName)) { + // Add a hidden app detail activity to normal apps which forwards user to App Details + // page. + Activity a = generateAppDetailsHiddenActivity(owner, flags, outError, + owner.baseHardwareAccelerated); + owner.activities.add(a); + } if (hasActivityOrder) { Collections.sort(owner.activities, (a1, a2) -> Integer.compare(a2.order, a1.order)); diff --git a/core/java/android/hardware/location/IContextHubClient.aidl b/core/java/android/hardware/location/IContextHubClient.aidl index 7559cd597736..b53941491c24 100644 --- a/core/java/android/hardware/location/IContextHubClient.aidl +++ b/core/java/android/hardware/location/IContextHubClient.aidl @@ -31,8 +31,8 @@ interface IContextHubClient { void close(); // Registers a PendingIntent with the client - boolean registerIntent(in PendingIntent intent, long nanoAppId); + boolean registerIntent(in PendingIntent pendingIntent, long nanoAppId); // Unregisters a PendingIntent from the client - boolean unregisterIntent(in PendingIntent intent); + boolean unregisterIntent(in PendingIntent pendingIntent); } diff --git a/core/java/android/net/InterfaceConfiguration.java b/core/java/android/net/InterfaceConfiguration.java index b27415566368..62cf7d7ceb25 100644 --- a/core/java/android/net/InterfaceConfiguration.java +++ b/core/java/android/net/InterfaceConfiguration.java @@ -19,9 +19,11 @@ package android.net; import android.annotation.UnsupportedAppUsage; import android.os.Parcel; import android.os.Parcelable; +import android.text.TextUtils; import com.google.android.collect.Sets; +import java.net.InetAddress; import java.util.HashSet; /** @@ -34,8 +36,10 @@ public class InterfaceConfiguration implements Parcelable { private LinkAddress mAddr; private HashSet<String> mFlags = Sets.newHashSet(); - private static final String FLAG_UP = "up"; - private static final String FLAG_DOWN = "down"; + private static final String FLAG_UP = INetd.IF_STATE_UP; + private static final String FLAG_DOWN = INetd.IF_STATE_DOWN; + + private static final String[] EMPTY_STRING_ARRAY = new String[0]; @Override public String toString() { @@ -112,6 +116,40 @@ public class InterfaceConfiguration implements Parcelable { } /** + * Construct InterfaceConfiguration from InterfaceConfigurationParcel. + */ + public static InterfaceConfiguration fromParcel(InterfaceConfigurationParcel p) { + InterfaceConfiguration cfg = new InterfaceConfiguration(); + cfg.setHardwareAddress(p.hwAddr); + + final InetAddress addr = NetworkUtils.numericToInetAddress(p.ipv4Addr); + cfg.setLinkAddress(new LinkAddress(addr, p.prefixLength)); + for (String flag : p.flags) { + cfg.setFlag(flag); + } + + return cfg; + } + + /** + * Convert InterfaceConfiguration to InterfaceConfigurationParcel with given ifname. + */ + public InterfaceConfigurationParcel toParcel(String iface) { + InterfaceConfigurationParcel cfgParcel = new InterfaceConfigurationParcel(); + cfgParcel.ifName = iface; + if (!TextUtils.isEmpty(mHwAddr)) { + cfgParcel.hwAddr = mHwAddr; + } else { + cfgParcel.hwAddr = ""; + } + cfgParcel.ipv4Addr = mAddr.getAddress().getHostAddress(); + cfgParcel.prefixLength = mAddr.getPrefixLength(); + cfgParcel.flags = mFlags.toArray(EMPTY_STRING_ARRAY); + + return cfgParcel; + } + + /** * This function determines if the interface is up and has a valid IP * configuration (IP address has a non zero octet). * diff --git a/core/java/android/net/NetworkStats.java b/core/java/android/net/NetworkStats.java index e270fc2e3923..5447f595cad6 100644 --- a/core/java/android/net/NetworkStats.java +++ b/core/java/android/net/NetworkStats.java @@ -811,14 +811,19 @@ public class NetworkStats implements Parcelable { * packet needs to be subtracted from the root UID on the base interface both for tx * and rx traffic (http://b/12249687, http:/b/33681750). * + * As for eBPF, the per uid stats is collected by different hook, the rx packets on base + * interface will not be counted. Thus, the adjustment on root uid is only needed in tx + * direction. + * * <p>This method will behave fine if {@code stackedIfaces} is an non-synchronized but add-only * {@code ConcurrentHashMap} * @param baseTraffic Traffic on the base interfaces. Will be mutated. * @param stackedTraffic Stats with traffic stacked on top of our ifaces. Will also be mutated. * @param stackedIfaces Mapping ipv6if -> ipv4if interface where traffic is counted on both. + * @param useBpfStats True if eBPF is in use. */ public static void apply464xlatAdjustments(NetworkStats baseTraffic, - NetworkStats stackedTraffic, Map<String, String> stackedIfaces) { + NetworkStats stackedTraffic, Map<String, String> stackedIfaces, boolean useBpfStats) { // Total 464xlat traffic to subtract from uid 0 on all base interfaces. // stackedIfaces may grow afterwards, but NetworkStats will just be resized automatically. final NetworkStats adjustments = new NetworkStats(0, stackedIfaces.size()); @@ -837,15 +842,20 @@ public class NetworkStats implements Parcelable { continue; } // Subtract any 464lat traffic seen for the root UID on the current base interface. + // However, for eBPF, the per uid stats is collected by different hook, the rx packets + // on base interface will not be counted. Thus, the adjustment on root uid is only + // needed in tx direction. adjust.iface = baseIface; - adjust.rxBytes = -(entry.rxBytes + entry.rxPackets * IPV4V6_HEADER_DELTA); + if (!useBpfStats) { + adjust.rxBytes = -(entry.rxBytes + entry.rxPackets * IPV4V6_HEADER_DELTA); + adjust.rxPackets = -entry.rxPackets; + } adjust.txBytes = -(entry.txBytes + entry.txPackets * IPV4V6_HEADER_DELTA); - adjust.rxPackets = -entry.rxPackets; adjust.txPackets = -entry.txPackets; adjustments.combineValues(adjust); - // For 464xlat traffic, xt_qtaguid only counts the bytes of the native IPv4 packet sent - // on the stacked interface with prefix "v4-" and drops the IPv6 header size after + // For 464xlat traffic, per uid stats only counts the bytes of the native IPv4 packet + // sent on the stacked interface with prefix "v4-" and drops the IPv6 header size after // unwrapping. To account correctly for on-the-wire traffic, add the 20 additional bytes // difference for all packets (http://b/12249687, http:/b/33681750). entry.rxBytes += entry.rxPackets * IPV4V6_HEADER_DELTA; @@ -864,8 +874,8 @@ public class NetworkStats implements Parcelable { * base and stacked traffic. * @param stackedIfaces Mapping ipv6if -> ipv4if interface where traffic is counted on both. */ - public void apply464xlatAdjustments(Map<String, String> stackedIfaces) { - apply464xlatAdjustments(this, this, stackedIfaces); + public void apply464xlatAdjustments(Map<String, String> stackedIfaces, boolean useBpfStats) { + apply464xlatAdjustments(this, this, stackedIfaces, useBpfStats); } /** diff --git a/core/java/android/os/Binder.java b/core/java/android/os/Binder.java index 8b6194c29707..da4b82308313 100644 --- a/core/java/android/os/Binder.java +++ b/core/java/android/os/Binder.java @@ -249,6 +249,7 @@ public class Binder implements IBinder { * If the current thread is not currently executing an incoming transaction, * then its own pid is returned. */ + @CriticalNative public static final native int getCallingPid(); /** @@ -258,6 +259,7 @@ public class Binder implements IBinder { * permissions. If the current thread is not currently executing an * incoming transaction, then its own uid is returned. */ + @CriticalNative public static final native int getCallingUid(); /** @@ -288,6 +290,7 @@ public class Binder implements IBinder { * @see #getCallingUid() * @see #restoreCallingIdentity(long) */ + @CriticalNative public static final native long clearCallingIdentity(); /** @@ -364,6 +367,7 @@ public class Binder implements IBinder { * @see StrictMode * @hide */ + @CriticalNative public static final native void setThreadStrictModePolicy(int policyMask); /** @@ -372,6 +376,7 @@ public class Binder implements IBinder { * @see #setThreadStrictModePolicy * @hide */ + @CriticalNative public static final native int getThreadStrictModePolicy(); /** @@ -555,6 +560,79 @@ public class Binder implements IBinder { } /** + * Listener to be notified about each proxy-side binder call. + * + * See {@link setProxyTransactListener}. + * @hide + */ + public interface ProxyTransactListener { + /** + * Called before onTransact. + * + * @return an object that will be passed back to #onTransactEnded (or null). + */ + Object onTransactStarted(IBinder binder, int transactionCode); + + /** + * Called after onTranact (even when an exception is thrown). + * + * @param session The object return by #onTransactStarted. + */ + void onTransactEnded(@Nullable Object session); + } + + /** + * Propagates the work source to binder calls executed by the system server. + * + * <li>By default, this listener will propagate the worksource if the outgoing call happens on + * the same thread as the incoming binder call. + * <li>Custom attribution can be done by calling {@link ThreadLocalWorkSourceUid#set(int)}. + * @hide + */ + public static class PropagateWorkSourceTransactListener implements ProxyTransactListener { + @Override + public Object onTransactStarted(IBinder binder, int transactionCode) { + // Note that {@link Binder#getCallingUid()} is already set to the UID of the current + // process when this method is called. + // + // We use ThreadLocalWorkSourceUid instead. It also allows feature owners to set + // {@link ThreadLocalWorkSourceUid#set(int) manually to attribute resources to a UID. + int uid = ThreadLocalWorkSourceUid.get(); + if (uid >= 0) { + int originalUid = Binder.setThreadWorkSource(uid); + return Integer.valueOf(originalUid); + } + return null; + } + + @Override + public void onTransactEnded(Object session) { + if (session != null) { + int uid = (int) session; + Binder.setThreadWorkSource(uid); + } + } + } + + /** + * Sets a listener for the transact method on the proxy-side. + * + * <li>The listener is global. Only fast operations should be done to avoid thread + * contentions. + * <li>The listener implementation needs to handle synchronization if needed. The methods on the + * listener can be called concurrently. + * <li>Listener set will be used for new transactions. On-going transaction will still use the + * previous listener (if already set). + * <li>The listener is called on the critical path of the binder transaction so be careful about + * performance. + * <li>Never execute another binder transaction inside the listener. + * @hide + */ + public static void setProxyTransactListener(@Nullable ProxyTransactListener listener) { + BinderProxy.setTransactListener(listener); + } + + /** * Default implementation is a stub that returns false. You will want * to override this to do the appropriate unmarshalling of transactions. * diff --git a/core/java/android/os/BinderProxy.java b/core/java/android/os/BinderProxy.java index 591370ff728b..720c16723c63 100644 --- a/core/java/android/os/BinderProxy.java +++ b/core/java/android/os/BinderProxy.java @@ -17,6 +17,7 @@ package android.os; import android.annotation.NonNull; +import android.annotation.Nullable; import android.util.Log; import android.util.SparseIntArray; @@ -45,6 +46,15 @@ public final class BinderProxy implements IBinder { // Assume the process-wide default value when created volatile boolean mWarnOnBlocking = Binder.sWarnOnBlocking; + private static volatile Binder.ProxyTransactListener sTransactListener = null; + + /** + * @see {@link Binder#setProxyTransactListener(listener)}. + */ + public static void setTransactListener(@Nullable Binder.ProxyTransactListener listener) { + sTransactListener = listener; + } + /* * Map from longs to BinderProxy, retaining only a WeakReference to the BinderProxies. * We roll our own only because we need to lazily remove WeakReferences during accesses @@ -469,9 +479,21 @@ public final class BinderProxy implements IBinder { Trace.traceBegin(Trace.TRACE_TAG_ALWAYS, stackTraceElement.getClassName() + "." + stackTraceElement.getMethodName()); } + + // Make sure the listener won't change while processing a transaction. + final Binder.ProxyTransactListener transactListener = sTransactListener; + Object session = null; + if (transactListener != null) { + session = transactListener.onTransactStarted(this, code); + } + try { return transactNative(code, data, reply, flags); } finally { + if (transactListener != null) { + transactListener.onTransactEnded(session); + } + if (tracingEnabled) { Trace.traceEnd(Trace.TRACE_TAG_ALWAYS); } diff --git a/core/java/android/os/ParcelFileDescriptor.java b/core/java/android/os/ParcelFileDescriptor.java index a54c589ab26f..70688fd9d2e3 100644 --- a/core/java/android/os/ParcelFileDescriptor.java +++ b/core/java/android/os/ParcelFileDescriptor.java @@ -291,22 +291,7 @@ public class ParcelFileDescriptor implements Parcelable, Closeable { } private static FileDescriptor openInternal(File file, int mode) throws FileNotFoundException { - if ((mode & MODE_READ_WRITE) == 0) { - throw new IllegalArgumentException( - "Must specify MODE_READ_ONLY, MODE_WRITE_ONLY, or MODE_READ_WRITE"); - } - - int flags = 0; - switch (mode & MODE_READ_WRITE) { - case 0: - case MODE_READ_ONLY: flags = O_RDONLY; break; - case MODE_WRITE_ONLY: flags = O_WRONLY; break; - case MODE_READ_WRITE: flags = O_RDWR; break; - } - - if ((mode & MODE_CREATE) != 0) flags |= O_CREAT; - if ((mode & MODE_TRUNCATE) != 0) flags |= O_TRUNC; - if ((mode & MODE_APPEND) != 0) flags |= O_APPEND; + final int flags = FileUtils.translateModePfdToPosix(mode); int realMode = S_IRWXU | S_IRWXG; if ((mode & MODE_WORLD_READABLE) != 0) realMode |= S_IROTH; diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java index 379d28cf10a0..651caece01f9 100644 --- a/core/java/android/os/Process.java +++ b/core/java/android/os/Process.java @@ -990,6 +990,8 @@ public class Process { /** @hide */ public static final int PROC_TAB_TERM = (int)'\t'; /** @hide */ + public static final int PROC_NEWLINE_TERM = (int) '\n'; + /** @hide */ public static final int PROC_COMBINE = 0x100; /** @hide */ public static final int PROC_PARENS = 0x200; @@ -1009,7 +1011,8 @@ public class Process { * * <p>The format is a list of integers, where every integer describes a variable in the file. It * specifies how the variable is syntactically terminated (e.g. {@link Process#PROC_SPACE_TERM}, - * {@link Process#PROC_TAB_TERM}, {@link Process#PROC_ZERO_TERM}). + * {@link Process#PROC_TAB_TERM}, {@link Process#PROC_ZERO_TERM}, {@link + * Process#PROC_NEWLINE_TERM}). * * <p>If the variable should be parsed and returned to the caller, the termination type should * be binary OR'd with the type of output (e.g. {@link Process#PROC_OUT_STRING}, {@link diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java index df771df5de0d..c91cda689703 100644 --- a/core/java/android/os/storage/StorageManager.java +++ b/core/java/android/os/storage/StorageManager.java @@ -132,6 +132,15 @@ public class StorageManager { public static final String PROP_ISOLATED_STORAGE = "persist.sys.isolated_storage"; /** {@hide} */ + public static final String PROP_FORCE_AUDIO = "persist.fw.force_audio"; + /** {@hide} */ + public static final String PROP_FORCE_VIDEO = "persist.fw.force_video"; + /** {@hide} */ + public static final String PROP_FORCE_IMAGES = "persist.fw.force_images"; + /** {@hide} */ + public static final String PROP_FORCE_LEGACY = "persist.fw.force_legacy"; + + /** {@hide} */ public static final String UUID_PRIVATE_INTERNAL = null; /** {@hide} */ public static final String UUID_PRIMARY_PHYSICAL = "primary_physical"; diff --git a/core/java/android/provider/CallLog.java b/core/java/android/provider/CallLog.java index bc72c4e38411..c0fa1de6feeb 100644 --- a/core/java/android/provider/CallLog.java +++ b/core/java/android/provider/CallLog.java @@ -684,6 +684,9 @@ public class CallLog { values.put(PHONE_ACCOUNT_ID, accountId); values.put(PHONE_ACCOUNT_ADDRESS, accountAddress); values.put(NEW, Integer.valueOf(1)); + if ((ci != null) && (ci.name != null)) { + values.put(CACHED_NAME, ci.name); + } values.put(ADD_FOR_ALL_USERS, addForAllUsers ? 1 : 0); if (callType == MISSED_TYPE) { diff --git a/core/java/android/provider/DocumentsProvider.java b/core/java/android/provider/DocumentsProvider.java index 0e782d752815..68f8acd8a586 100644 --- a/core/java/android/provider/DocumentsProvider.java +++ b/core/java/android/provider/DocumentsProvider.java @@ -461,6 +461,41 @@ public abstract class DocumentsProvider extends ContentProvider { } /** + * Return recently modified documents under the requested root. This will + * only be called for roots that advertise + * {@link Root#FLAG_SUPPORTS_RECENTS}. The returned documents should be + * sorted by {@link Document#COLUMN_LAST_MODIFIED} in descending order of + * the most recently modified documents. + * <p> + * If this method is overriden by the concrete DocumentsProvider and + * QUERY_ARGS_LIMIT is specified with a nonnegative int under queryArgs, the + * result will be limited by that number and QUERY_ARG_LIMIT will be + * specified under EXTRA_HONORED_ARGS. Otherwise, a default 64 limit will + * be used and no QUERY_ARG* will be specified under EXTRA_HONORED_ARGS. + * <p> + * Recent documents do not support change notifications. + * + * @param projection list of {@link Document} columns to put into the + * cursor. If {@code null} all supported columns should be + * included. + * @param queryArgs the extra query arguments. + * @param signal used by the caller to signal if the request should be + * cancelled. May be null. + * @see DocumentsContract#EXTRA_LOADING + */ + @SuppressWarnings("unused") + public Cursor queryRecentDocuments( + String rootId, String[] projection, @Nullable Bundle queryArgs, + @Nullable CancellationSignal signal) + throws FileNotFoundException { + Cursor c = queryRecentDocuments(rootId, projection); + Bundle extras = new Bundle(); + c.setExtras(extras); + extras.putStringArray(ContentResolver.EXTRA_HONORED_ARGS, new String[0]); + return c; + } + + /** * Return metadata for the single requested document. You should avoid * making network requests to keep this request fast. * @@ -774,7 +809,7 @@ public abstract class DocumentsProvider extends ContentProvider { * Implementation is provided by the parent class. Cannot be overridden. * * @see #queryRoots(String[]) - * @see #queryRecentDocuments(String, String[]) + * @see #queryRecentDocuments(String, String[], Bundle, CancellationSignal) * @see #queryDocument(String, String[]) * @see #queryChildDocuments(String, String[], String) * @see #querySearchDocuments(String, String, String[]) @@ -787,7 +822,8 @@ public abstract class DocumentsProvider extends ContentProvider { case MATCH_ROOTS: return queryRoots(projection); case MATCH_RECENT: - return queryRecentDocuments(getRootId(uri), projection); + return queryRecentDocuments( + getRootId(uri), projection, queryArgs, cancellationSignal); case MATCH_SEARCH: return querySearchDocuments( getRootId(uri), getSearchDocumentsQuery(uri), projection); diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 80e8f78ec92b..0ef56b0c5d2d 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -1815,27 +1815,39 @@ public final class Settings { /** - * User has not started setup personalization. + * Indicates that the user has not started setup personalization. + * One of the possible states for {@link Secure#USER_SETUP_PERSONALIZATION_STATE}. + * * @hide */ + @SystemApi public static final int USER_SETUP_PERSONALIZATION_NOT_STARTED = 0; /** - * User has not yet completed setup personalization. + * Indicates that the user has not yet completed setup personalization. + * One of the possible states for {@link Secure#USER_SETUP_PERSONALIZATION_STATE}. + * * @hide */ + @SystemApi public static final int USER_SETUP_PERSONALIZATION_STARTED = 1; /** - * User has snoozed personalization and will complete it later. + * Indicates that the user has snoozed personalization and will complete it later. + * One of the possible states for {@link Secure#USER_SETUP_PERSONALIZATION_STATE}. + * * @hide */ + @SystemApi public static final int USER_SETUP_PERSONALIZATION_PAUSED = 2; /** - * User has completed setup personalization. + * Indicates that the user has completed setup personalization. + * One of the possible states for {@link Secure#USER_SETUP_PERSONALIZATION_STATE}. + * * @hide */ + @SystemApi public static final int USER_SETUP_PERSONALIZATION_COMPLETE = 10; /** @hide */ @@ -5614,18 +5626,23 @@ public final class Settings { public static final String DEVICE_PROVISIONED = Global.DEVICE_PROVISIONED; /** - * Whether the current user has been set up via setup wizard (0 = false, 1 = true) + * Indicates whether the current user has completed setup via the setup wizard. + * <p> + * Type: int (0 for false, 1 for true) + * * @hide */ + @SystemApi @TestApi public static final String USER_SETUP_COMPLETE = "user_setup_complete"; /** - * The current state of device personalization. + * Defines the user's current state of device personalization. + * The possible states are defined in {@link UserSetupPersonalization}. * * @hide - * @see UserSetupPersonalization */ + @SystemApi public static final String USER_SETUP_PERSONALIZATION_STATE = "user_setup_personalization_state"; @@ -5639,10 +5656,14 @@ public final class Settings { public static final String TV_USER_SETUP_COMPLETE = "tv_user_setup_complete"; /** - * Prefix for category name that marks whether a suggested action from that category was - * completed. + * The prefix for a category name that indicates whether a suggested action from that + * category was marked as completed. + * <p> + * Type: int (0 for false, 1 for true) + * * @hide */ + @SystemApi public static final String COMPLETED_CATEGORY_PREFIX = "suggested.completed_category."; /** @@ -5914,11 +5935,14 @@ public final class Settings { "lock_screen_owner_info_enabled"; /** - * When set by a user, allows notifications to be shown atop a securely locked screen - * in their full "private" form (same as when the device is unlocked). + * Indicates whether the user has allowed notifications to be shown atop a securely locked + * screen in their full "private" form (same as when the device is unlocked). + * <p> + * Type: int (0 for false, 1 for true) + * * @hide */ - @UnsupportedAppUsage + @SystemApi public static final String LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS = "lock_screen_allow_private_notifications"; @@ -6038,6 +6062,7 @@ public final class Settings { * shortcut. Must be its flattened {@link ComponentName}. * @hide */ + @TestApi public static final String ACCESSIBILITY_SHORTCUT_TARGET_SERVICE = "accessibility_shortcut_target_service"; @@ -6943,10 +6968,12 @@ public final class Settings { public static final String BACKUP_TRANSPORT = "backup_transport"; /** - * Version for which the setup wizard was last shown. Bumped for - * each release when there is new setup information to show. + * Indicates the version for which the setup wizard was last shown. The version gets + * bumped for each release when there is new setup information to show. + * * @hide */ + @SystemApi public static final String LAST_SETUP_SHOWN = "last_setup_shown"; /** @@ -7259,9 +7286,13 @@ public final class Settings { private static final Validator DOZE_ENABLED_VALIDATOR = BOOLEAN_VALIDATOR; /** - * Whether doze should be always on. + * Indicates whether doze should be always on. + * <p> + * Type: int (0 for false, 1 for true) + * * @hide */ + @SystemApi public static final String DOZE_ALWAYS_ON = "doze_always_on"; private static final Validator DOZE_ALWAYS_ON_VALIDATOR = BOOLEAN_VALIDATOR; @@ -7387,8 +7418,16 @@ public final class Settings { public static final String DIALER_DEFAULT_APPLICATION = "dialer_default_application"; /** - * Specifies the package name currently configured to be the default application to perform - * the user-defined call redirection service with Telecom. + * Specifies the component name currently configured to be the default call screening + * application + * @hide + */ + public static final String CALL_SCREENING_DEFAULT_COMPONENT = + "call_screening_default_component"; + + /** + * Specifies the component name currently configured to be the default application to + * perform the user-defined call redirection service with Telecom. * @hide */ @UnsupportedAppUsage @@ -7571,10 +7610,13 @@ public final class Settings { public static final String UNSAFE_VOLUME_MUSIC_ACTIVE_MS = "unsafe_volume_music_active_ms"; /** - * This preference enables notification display on the lockscreen. + * Indicates whether notification display on the lock screen is enabled. + * <p> + * Type: int (0 for false, 1 for true) + * * @hide */ - @UnsupportedAppUsage + @SystemApi public static final String LOCK_SCREEN_SHOW_NOTIFICATIONS = "lock_screen_show_notifications"; @@ -7768,10 +7810,13 @@ public final class Settings { BOOLEAN_VALIDATOR; /** - * Whether Assist Gesture Deferred Setup has been completed + * Indicates whether the Assist Gesture Deferred Setup has been completed. + * <p> + * Type: int (0 for false, 1 for true) * * @hide */ + @SystemApi public static final String ASSIST_GESTURE_SETUP_COMPLETE = "assist_gesture_setup_complete"; /** @@ -8414,6 +8459,8 @@ public final class Settings { VALIDATORS.put(CHARGING_VIBRATION_ENABLED, BOOLEAN_VALIDATOR); VALIDATORS.put(ACCESSIBILITY_MINIMUM_UI_TIMEOUT_ENABLED, BOOLEAN_VALIDATOR); VALIDATORS.put(ACCESSIBILITY_MINIMUM_UI_TIMEOUT_MS, NON_NEGATIVE_INTEGER_VALIDATOR); + VALIDATORS.put(USER_SETUP_COMPLETE, BOOLEAN_VALIDATOR); + VALIDATORS.put(ASSIST_GESTURE_SETUP_COMPLETE, BOOLEAN_VALIDATOR); } /** @@ -9119,16 +9166,19 @@ public final class Settings { */ public static final String DEVICE_PROVISIONED = "device_provisioned"; - /** - * Whether mobile data should be allowed while the device is being provisioned. - * This allows the provisioning process to turn off mobile data before the user - * has an opportunity to set things up, preventing other processes from burning - * precious bytes before wifi is setup. - * (0 = false, 1 = true) - * @hide - */ - public static final String DEVICE_PROVISIONING_MOBILE_DATA_ENABLED = - "device_provisioning_mobile_data"; + /** + * Indicates whether mobile data should be allowed while the device is being provisioned. + * This allows the provisioning process to turn off mobile data before the user + * has an opportunity to set things up, preventing other processes from burning + * precious bytes before wifi is setup. + * <p> + * Type: int (0 for false, 1 for true) + * + * @hide + */ + @SystemApi + public static final String DEVICE_PROVISIONING_MOBILE_DATA_ENABLED = + "device_provisioning_mobile_data"; /** * The saved value for WindowManagerService.setForcedDisplaySize(). @@ -12103,13 +12153,14 @@ public final class Settings { public static final String NETWORK_SCORING_PROVISIONED = "network_scoring_provisioned"; /** - * Whether the user wants to be prompted for password to decrypt the device on boot. - * This only matters if the storage is encrypted. + * Indicates whether the user wants to be prompted for password to decrypt the device on + * boot. This only matters if the storage is encrypted. * <p> * Type: int (0 for false, 1 for true) + * * @hide */ - @UnsupportedAppUsage + @SystemApi public static final String REQUIRE_PASSWORD_TO_DECRYPT = "require_password_to_decrypt"; /** @@ -12293,12 +12344,14 @@ public final class Settings { public static final String SAFE_BOOT_DISALLOWED = "safe_boot_disallowed"; /** - * Whether this device is currently in retail demo mode. If true, device + * Indicates whether this device is currently in retail demo mode. If true, the device * usage is severely limited. * <p> * Type: int (0 for false, 1 for true) + * * @hide */ + @SystemApi public static final String DEVICE_DEMO_MODE = "device_demo_mode"; /** @@ -12620,6 +12673,9 @@ public final class Settings { VALIDATORS.put(APP_AUTO_RESTRICTION_ENABLED, APP_AUTO_RESTRICTION_ENABLED_VALIDATOR); VALIDATORS.put(ZEN_DURATION, ZEN_DURATION_VALIDATOR); VALIDATORS.put(CHARGING_VIBRATION_ENABLED, CHARGING_VIBRATION_ENABLED_VALIDATOR); + VALIDATORS.put(DEVICE_PROVISIONING_MOBILE_DATA_ENABLED, BOOLEAN_VALIDATOR); + VALIDATORS.put(REQUIRE_PASSWORD_TO_DECRYPT, BOOLEAN_VALIDATOR); + VALIDATORS.put(DEVICE_DEMO_MODE, BOOLEAN_VALIDATOR); } /** diff --git a/core/java/android/rolecontrollerservice/IRoleControllerService.aidl b/core/java/android/rolecontrollerservice/IRoleControllerService.aidl new file mode 100644 index 000000000000..0000b9f8c76c --- /dev/null +++ b/core/java/android/rolecontrollerservice/IRoleControllerService.aidl @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.rolecontrollerservice; + +import android.app.role.IRoleManagerCallback; + +/** + * @hide + */ +oneway interface IRoleControllerService { + + void onAddRoleHolder(in String roleName, in String packageName, + in IRoleManagerCallback callback); + + void onRemoveRoleHolder(in String roleName, in String packageName, + in IRoleManagerCallback callback); + + void onClearRoleHolders(in String roleName, in IRoleManagerCallback callback); +} diff --git a/core/java/android/rolecontrollerservice/RoleControllerService.java b/core/java/android/rolecontrollerservice/RoleControllerService.java new file mode 100644 index 000000000000..da11bca220d4 --- /dev/null +++ b/core/java/android/rolecontrollerservice/RoleControllerService.java @@ -0,0 +1,162 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.rolecontrollerservice; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SystemApi; +import android.app.Service; +import android.app.role.IRoleManagerCallback; +import android.app.role.RoleManager; +import android.app.role.RoleManagerCallback; +import android.content.Intent; +import android.os.IBinder; +import android.os.RemoteException; +import android.os.UserHandle; +import android.util.Log; + +import com.android.internal.util.Preconditions; + +import java.util.concurrent.Executor; + +/** + * Abstract base class for the role controller service. + * <p> + * Subclass should implement the business logic for role management, including enforcing role + * requirements and granting or revoking relevant privileges of roles. This class can only be + * implemented by the permission controller app which is registered in {@code PackageManager}. + * + * @hide + */ +@SystemApi +public abstract class RoleControllerService extends Service { + + private static final String LOG_TAG = RoleControllerService.class.getSimpleName(); + + /** + * The {@link Intent} that must be declared as handled by the service. The service should also + * require the {@link android.Manifest.permission#BIND_ROLE_CONTROLLER_SERVICE} permission so + * that other applications can not abuse it. + */ + public static final String SERVICE_INTERFACE = + "android.rolecontrollerservice.RoleControllerService"; + + @Nullable + @Override + public final IBinder onBind(@Nullable Intent intent) { + return new IRoleControllerService.Stub() { + + @Override + public void onAddRoleHolder(String roleName, String packageName, + IRoleManagerCallback callback) { + Preconditions.checkStringNotEmpty(roleName, "roleName cannot be null or empty"); + Preconditions.checkStringNotEmpty(packageName, + "packageName cannot be null or empty"); + Preconditions.checkNotNull(callback, "callback cannot be null"); + RoleControllerService.this.onAddRoleHolder(roleName, packageName, + new RoleManagerCallbackDelegate(callback)); + } + + @Override + public void onRemoveRoleHolder(String roleName, String packageName, + IRoleManagerCallback callback) { + Preconditions.checkStringNotEmpty(roleName, "roleName cannot be null or empty"); + Preconditions.checkStringNotEmpty(packageName, + "packageName cannot be null or empty"); + Preconditions.checkNotNull(callback, "callback cannot be null"); + RoleControllerService.this.onRemoveRoleHolder(roleName, packageName, + new RoleManagerCallbackDelegate(callback)); + } + + @Override + public void onClearRoleHolders(String roleName, IRoleManagerCallback callback) { + Preconditions.checkStringNotEmpty(roleName, "roleName cannot be null or empty"); + Preconditions.checkNotNull(callback, "callback cannot be null"); + RoleControllerService.this.onClearRoleHolders(roleName, + new RoleManagerCallbackDelegate(callback)); + } + }; + } + + /** + * Add a specific application to the holders of a role. If the role is exclusive, the previous + * holder will be replaced. + * <p> + * Implementation should enforce the role requirements and grant or revoke the relevant + * privileges of roles. + * + * @param roleName the name of the role to add the role holder for + * @param packageName the package name of the application to add to the role holders + * @param callback the callback for whether this call is successful + * + * @see RoleManager#addRoleHolderAsUser(String, String, UserHandle, Executor, + * RoleManagerCallback) + */ + public abstract void onAddRoleHolder(@NonNull String roleName, @NonNull String packageName, + @NonNull RoleManagerCallback callback); + + /** + * Remove a specific application from the holders of a role. + * + * @param roleName the name of the role to remove the role holder for + * @param packageName the package name of the application to remove from the role holders + * @param callback the callback for whether this call is successful + * + * @see RoleManager#removeRoleHolderAsUser(String, String, UserHandle, Executor, + * RoleManagerCallback) + */ + public abstract void onRemoveRoleHolder(@NonNull String roleName, @NonNull String packageName, + @NonNull RoleManagerCallback callback); + + /** + * Remove all holders of a role. + * + * @param roleName the name of the role to remove role holders for + * @param callback the callback for whether this call is successful + * + * @see RoleManager#clearRoleHoldersAsUser(String, UserHandle, Executor, RoleManagerCallback) + */ + public abstract void onClearRoleHolders(@NonNull String roleName, + @NonNull RoleManagerCallback callback); + + private static class RoleManagerCallbackDelegate implements RoleManagerCallback { + + private IRoleManagerCallback mCallback; + + RoleManagerCallbackDelegate(IRoleManagerCallback callback) { + mCallback = callback; + } + + @Override + public void onSuccess() { + try { + mCallback.onSuccess(); + } catch (RemoteException e) { + Log.e(LOG_TAG, "Error calling onSuccess() callback"); + } + } + + @Override + public void onFailure() { + try { + mCallback.onFailure(); + } catch (RemoteException e) { + Log.e(LOG_TAG, "Error calling onFailure() callback"); + } + } + } +} diff --git a/core/java/android/service/textclassifier/TextClassifierService.java b/core/java/android/service/textclassifier/TextClassifierService.java index 7af9db8ebf32..7f1082d4dcc6 100644 --- a/core/java/android/service/textclassifier/TextClassifierService.java +++ b/core/java/android/service/textclassifier/TextClassifierService.java @@ -17,7 +17,6 @@ package android.service.textclassifier; import android.Manifest; -import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemApi; @@ -98,8 +97,7 @@ public abstract class TextClassifierService extends Service { Preconditions.checkNotNull(request); Preconditions.checkNotNull(callback); TextClassifierService.this.onSuggestSelection( - request.getText(), request.getStartIndex(), request.getEndIndex(), - TextSelection.Options.from(sessionId, request), mCancellationSignal, + sessionId, request, mCancellationSignal, new Callback<TextSelection>() { @Override public void onSuccess(TextSelection result) { @@ -132,8 +130,7 @@ public abstract class TextClassifierService extends Service { Preconditions.checkNotNull(request); Preconditions.checkNotNull(callback); TextClassifierService.this.onClassifyText( - request.getText(), request.getStartIndex(), request.getEndIndex(), - TextClassification.Options.from(sessionId, request), mCancellationSignal, + sessionId, request, mCancellationSignal, new Callback<TextClassification>() { @Override public void onSuccess(TextClassification result) { @@ -164,7 +161,7 @@ public abstract class TextClassifierService extends Service { Preconditions.checkNotNull(request); Preconditions.checkNotNull(callback); TextClassifierService.this.onGenerateLinks( - request.getText(), TextLinks.Options.from(sessionId, request), + sessionId, request, mCancellationSignal, new Callback<TextLinks>() { @Override @@ -238,25 +235,6 @@ public abstract class TextClassifierService extends Service { @NonNull CancellationSignal cancellationSignal, @NonNull Callback<TextSelection> callback); - // TODO: Remove once apps can build against the latest sdk. - /** @hide */ - public void onSuggestSelection( - @NonNull CharSequence text, - @IntRange(from = 0) int selectionStartIndex, - @IntRange(from = 0) int selectionEndIndex, - @Nullable TextSelection.Options options, - @NonNull CancellationSignal cancellationSignal, - @NonNull Callback<TextSelection> callback) { - final TextClassificationSessionId sessionId = options.getSessionId(); - final TextSelection.Request request = options.getRequest() != null - ? options.getRequest() - : new TextSelection.Request.Builder( - text, selectionStartIndex, selectionEndIndex) - .setDefaultLocales(options.getDefaultLocales()) - .build(); - onSuggestSelection(sessionId, request, cancellationSignal, callback); - } - /** * Classifies the specified text and returns a {@link TextClassification} object that can be * used to generate a widget for handling the classified text. @@ -272,26 +250,6 @@ public abstract class TextClassifierService extends Service { @NonNull CancellationSignal cancellationSignal, @NonNull Callback<TextClassification> callback); - // TODO: Remove once apps can build against the latest sdk. - /** @hide */ - public void onClassifyText( - @NonNull CharSequence text, - @IntRange(from = 0) int startIndex, - @IntRange(from = 0) int endIndex, - @Nullable TextClassification.Options options, - @NonNull CancellationSignal cancellationSignal, - @NonNull Callback<TextClassification> callback) { - final TextClassificationSessionId sessionId = options.getSessionId(); - final TextClassification.Request request = options.getRequest() != null - ? options.getRequest() - : new TextClassification.Request.Builder( - text, startIndex, endIndex) - .setDefaultLocales(options.getDefaultLocales()) - .setReferenceTime(options.getReferenceTime()) - .build(); - onClassifyText(sessionId, request, cancellationSignal, callback); - } - /** * Generates and returns a {@link TextLinks} that may be applied to the text to annotate it with * links information. @@ -307,23 +265,6 @@ public abstract class TextClassifierService extends Service { @NonNull CancellationSignal cancellationSignal, @NonNull Callback<TextLinks> callback); - // TODO: Remove once apps can build against the latest sdk. - /** @hide */ - public void onGenerateLinks( - @NonNull CharSequence text, - @Nullable TextLinks.Options options, - @NonNull CancellationSignal cancellationSignal, - @NonNull Callback<TextLinks> callback) { - final TextClassificationSessionId sessionId = options.getSessionId(); - final TextLinks.Request request = options.getRequest() != null - ? options.getRequest() - : new TextLinks.Request.Builder(text) - .setDefaultLocales(options.getDefaultLocales()) - .setEntityConfig(options.getEntityConfig()) - .build(); - onGenerateLinks(sessionId, request, cancellationSignal, callback); - } - /** * Writes the selection event. * This is called when a selection event occurs. e.g. user changed selection; or smart selection diff --git a/core/java/android/text/Layout.java b/core/java/android/text/Layout.java index 3ab8a0a8885f..e5fd2921227d 100644 --- a/core/java/android/text/Layout.java +++ b/core/java/android/text/Layout.java @@ -1269,29 +1269,47 @@ public abstract class Layout { * scrolling on the specified line. */ public float getLineLeft(int line) { - int dir = getParagraphDirection(line); - Alignment align = getParagraphAlignment(line); + final int dir = getParagraphDirection(line); + final Alignment align = getParagraphAlignment(line); - if (align == Alignment.ALIGN_LEFT) { - return 0; - } else if (align == Alignment.ALIGN_NORMAL) { - if (dir == DIR_RIGHT_TO_LEFT) - return getParagraphRight(line) - getLineMax(line); - else - return 0; - } else if (align == Alignment.ALIGN_RIGHT) { - return mWidth - getLineMax(line); - } else if (align == Alignment.ALIGN_OPPOSITE) { - if (dir == DIR_RIGHT_TO_LEFT) - return 0; - else + // First convert combinations of alignment and direction settings to + // three basic cases: ALIGN_LEFT, ALIGN_RIGHT and ALIGN_CENTER. + // For unexpected cases, it will fallback to ALIGN_LEFT. + final Alignment resultAlign; + switch(align) { + case ALIGN_NORMAL: + resultAlign = + dir == DIR_RIGHT_TO_LEFT ? Alignment.ALIGN_RIGHT : Alignment.ALIGN_LEFT; + break; + case ALIGN_OPPOSITE: + resultAlign = + dir == DIR_RIGHT_TO_LEFT ? Alignment.ALIGN_LEFT : Alignment.ALIGN_RIGHT; + break; + case ALIGN_CENTER: + resultAlign = Alignment.ALIGN_CENTER; + break; + case ALIGN_RIGHT: + resultAlign = Alignment.ALIGN_RIGHT; + break; + default: /* align == Alignment.ALIGN_LEFT */ + resultAlign = Alignment.ALIGN_LEFT; + } + + // Here we must use getLineMax() to do the computation, because it maybe overridden by + // derived class. And also note that line max equals the width of the text in that line + // plus the leading margin. + switch (resultAlign) { + case ALIGN_CENTER: + final int left = getParagraphLeft(line); + final float max = getLineMax(line); + // This computation only works when mWidth equals leadingMargin plus + // the width of text in this line. If this condition doesn't meet anymore, + // please change here too. + return (float) Math.floor(left + (mWidth - max) / 2); + case ALIGN_RIGHT: return mWidth - getLineMax(line); - } else { /* align == Alignment.ALIGN_CENTER */ - int left = getParagraphLeft(line); - int right = getParagraphRight(line); - int max = ((int) getLineMax(line)) & ~1; - - return left + ((right - left) - max) / 2; + default: /* resultAlign == Alignment.ALIGN_LEFT */ + return 0; } } @@ -1300,29 +1318,40 @@ public abstract class Layout { * scrolling on the specified line. */ public float getLineRight(int line) { - int dir = getParagraphDirection(line); - Alignment align = getParagraphAlignment(line); + final int dir = getParagraphDirection(line); + final Alignment align = getParagraphAlignment(line); - if (align == Alignment.ALIGN_LEFT) { - return getParagraphLeft(line) + getLineMax(line); - } else if (align == Alignment.ALIGN_NORMAL) { - if (dir == DIR_RIGHT_TO_LEFT) + final Alignment resultAlign; + switch(align) { + case ALIGN_NORMAL: + resultAlign = + dir == DIR_RIGHT_TO_LEFT ? Alignment.ALIGN_RIGHT : Alignment.ALIGN_LEFT; + break; + case ALIGN_OPPOSITE: + resultAlign = + dir == DIR_RIGHT_TO_LEFT ? Alignment.ALIGN_LEFT : Alignment.ALIGN_RIGHT; + break; + case ALIGN_CENTER: + resultAlign = Alignment.ALIGN_CENTER; + break; + case ALIGN_RIGHT: + resultAlign = Alignment.ALIGN_RIGHT; + break; + default: /* align == Alignment.ALIGN_LEFT */ + resultAlign = Alignment.ALIGN_LEFT; + } + + switch (resultAlign) { + case ALIGN_CENTER: + final int right = getParagraphRight(line); + final float max = getLineMax(line); + // This computation only works when mWidth equals leadingMargin plus width of the + // text in this line. If this condition doesn't meet anymore, please change here. + return (float) Math.ceil(right - (mWidth - max) / 2); + case ALIGN_RIGHT: return mWidth; - else - return getParagraphLeft(line) + getLineMax(line); - } else if (align == Alignment.ALIGN_RIGHT) { - return mWidth; - } else if (align == Alignment.ALIGN_OPPOSITE) { - if (dir == DIR_RIGHT_TO_LEFT) + default: /* resultAlign == Alignment.ALIGN_LEFT */ return getLineMax(line); - else - return mWidth; - } else { /* align == Alignment.ALIGN_CENTER */ - int left = getParagraphLeft(line); - int right = getParagraphRight(line); - int max = ((int) getLineMax(line)) & ~1; - - return right - ((right - left) - max) / 2; } } @@ -1671,7 +1700,7 @@ public abstract class Layout { * Return the vertical position of the baseline of the specified line. */ public final int getLineBaseline(int line) { - // getLineTop(line+1) == getLineTop(line) + // getLineTop(line+1) == getLineBottom(line) return getLineTop(line+1) - getLineDescent(line); } diff --git a/core/java/android/transition/Scene.java b/core/java/android/transition/Scene.java index 1bdcff98ff17..7e499f29122a 100644 --- a/core/java/android/transition/Scene.java +++ b/core/java/android/transition/Scene.java @@ -16,6 +16,8 @@ package android.transition; +import android.annotation.NonNull; +import android.annotation.Nullable; import android.annotation.UnsupportedAppUsage; import android.content.Context; import android.util.SparseArray; @@ -196,22 +198,24 @@ public final class Scene { * information is used by Scene to determine whether there is a previous * scene which should be exited before the new scene is entered. * - * @param view The view on which the current scene is being set + * @param sceneRoot The view on which the current scene is being set */ @UnsupportedAppUsage - static void setCurrentScene(View view, Scene scene) { - view.setTagInternal(com.android.internal.R.id.current_scene, scene); + static void setCurrentScene(@NonNull View sceneRoot, @Nullable Scene scene) { + sceneRoot.setTagInternal(com.android.internal.R.id.current_scene, scene); } /** * Gets the current {@link Scene} set on the given view. A scene is set on a view * only if that view is the scene root. * + * @param sceneRoot The view on which the current scene will be returned * @return The current Scene set on this view. A value of null indicates that * no Scene is currently set. */ - static Scene getCurrentScene(View view) { - return (Scene) view.getTag(com.android.internal.R.id.current_scene); + @Nullable + public static Scene getCurrentScene(@NonNull View sceneRoot) { + return (Scene) sceneRoot.getTag(com.android.internal.R.id.current_scene); } /** diff --git a/core/java/android/transition/TransitionSet.java b/core/java/android/transition/TransitionSet.java index 589ad574dd9b..b9b2a70d32b1 100644 --- a/core/java/android/transition/TransitionSet.java +++ b/core/java/android/transition/TransitionSet.java @@ -163,8 +163,7 @@ public class TransitionSet extends Transition { */ public TransitionSet addTransition(Transition transition) { if (transition != null) { - mTransitions.add(transition); - transition.mParent = this; + addTransitionInternal(transition); if (mDuration >= 0) { transition.setDuration(mDuration); } @@ -184,6 +183,11 @@ public class TransitionSet extends Transition { return this; } + private void addTransitionInternal(Transition transition) { + mTransitions.add(transition); + transition.mParent = this; + } + /** * Returns the number of child transitions in the TransitionSet. * @@ -355,8 +359,10 @@ public class TransitionSet extends Transition { public void setPathMotion(PathMotion pathMotion) { super.setPathMotion(pathMotion); mChangeFlags |= FLAG_CHANGE_PATH_MOTION; - for (int i = 0; i < mTransitions.size(); i++) { - mTransitions.get(i).setPathMotion(pathMotion); + if (mTransitions != null) { + for (int i = 0; i < mTransitions.size(); i++) { + mTransitions.get(i).setPathMotion(pathMotion); + } } } @@ -604,7 +610,7 @@ public class TransitionSet extends Transition { clone.mTransitions = new ArrayList<Transition>(); int numTransitions = mTransitions.size(); for (int i = 0; i < numTransitions; ++i) { - clone.addTransition((Transition) mTransitions.get(i).clone()); + clone.addTransitionInternal((Transition) mTransitions.get(i).clone()); } return clone; } diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java index db2c19043361..fb44eda5bc73 100644 --- a/core/java/android/util/FeatureFlagUtils.java +++ b/core/java/android/util/FeatureFlagUtils.java @@ -48,7 +48,8 @@ public class FeatureFlagUtils { DEFAULT_FLAGS.put("settings_data_usage_v2", "false"); DEFAULT_FLAGS.put("settings_seamless_transfer", "false"); DEFAULT_FLAGS.put(HEARING_AID_SETTINGS, "false"); - DEFAULT_FLAGS.put(EMERGENCY_DIAL_SHORTCUTS, "false"); + DEFAULT_FLAGS.put(EMERGENCY_DIAL_SHORTCUTS, "true"); + DEFAULT_FLAGS.put("settings_network_and_internet_v2", "false"); } /** diff --git a/core/java/android/util/MathUtils.java b/core/java/android/util/MathUtils.java index 37bb5971d000..56558d04e50d 100644 --- a/core/java/android/util/MathUtils.java +++ b/core/java/android/util/MathUtils.java @@ -17,6 +17,7 @@ package android.util; import android.annotation.UnsupportedAppUsage; +import android.graphics.Rect; /** * A class that contains utility methods related to numbers. @@ -227,4 +228,18 @@ public final class MathUtils { } throw new IllegalArgumentException("Addition overflow: " + a + " + " + b); } + + /** + * Resize a {@link Rect} so one size would be {@param largestSide}. + * + * @param outToResize Rectangle that will be resized. + * @param largestSide Size of the largest side. + */ + public static void fitRect(Rect outToResize, int largestSide) { + if (outToResize.isEmpty()) { + return; + } + float maxSize = Math.max(outToResize.width(), outToResize.height()); + outToResize.scale(largestSide / maxSize); + } } diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index fef2d43a5baf..48761486d9dc 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -243,9 +243,10 @@ public final class ViewRootImpl implements ViewParent, final Context mContext; /** * TODO(b/116349163): Check if we can merge this into {@link #mContext}. + * @hide */ @NonNull - private Context mDisplayContext; + public Context mDisplayContext; @UnsupportedAppUsage final IWindowSession mWindowSession; diff --git a/core/java/android/view/accessibility/AccessibilityManager.java b/core/java/android/view/accessibility/AccessibilityManager.java index e88682ea6287..a7d0cfbde1d5 100644 --- a/core/java/android/view/accessibility/AccessibilityManager.java +++ b/core/java/android/view/accessibility/AccessibilityManager.java @@ -23,8 +23,11 @@ import android.accessibilityservice.AccessibilityServiceInfo; import android.accessibilityservice.AccessibilityServiceInfo.FeedbackType; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.RequiresPermission; import android.annotation.SdkConstant; +import android.annotation.SystemApi; import android.annotation.SystemService; +import android.annotation.TestApi; import android.annotation.UnsupportedAppUsage; import android.content.ComponentName; import android.content.Context; @@ -204,6 +207,7 @@ public final class AccessibilityManager { * * @hide */ + @TestApi public interface AccessibilityServicesStateChangeListener { /** @@ -778,6 +782,7 @@ public final class AccessibilityManager { * for a callback on the process's main handler. * @hide */ + @TestApi public void addAccessibilityServicesStateChangeListener( @NonNull AccessibilityServicesStateChangeListener listener, @Nullable Handler handler) { synchronized (mLock) { @@ -793,6 +798,7 @@ public final class AccessibilityManager { * * @hide */ + @TestApi public void removeAccessibilityServicesStateChangeListener( @NonNull AccessibilityServicesStateChangeListener listener) { synchronized (mLock) { @@ -1056,6 +1062,9 @@ public final class AccessibilityManager { * * @hide */ + @SystemApi + @TestApi + @RequiresPermission(Manifest.permission.MANAGE_ACCESSIBILITY) public void performAccessibilityShortcut() { final IAccessibilityManager service; synchronized (mLock) { @@ -1139,6 +1148,30 @@ public final class AccessibilityManager { } } + /** + * Get the component name of the service currently assigned to the accessibility shortcut. + * + * @return The flattened component name + * @hide + */ + @TestApi + @RequiresPermission(Manifest.permission.MANAGE_ACCESSIBILITY) + @Nullable + public String getAccessibilityShortcutService() { + final IAccessibilityManager service; + synchronized (mLock) { + service = getServiceLocked(); + } + if (service != null) { + try { + return service.getAccessibilityShortcutService(); + } catch (RemoteException re) { + re.rethrowFromSystemServer(); + } + } + return null; + } + private IAccessibilityManager getServiceLocked() { if (mService == null) { tryConnectToServiceLocked(null); diff --git a/core/java/android/view/accessibility/IAccessibilityManager.aidl b/core/java/android/view/accessibility/IAccessibilityManager.aidl index 3e2ef186f793..61a8a1da2b42 100644 --- a/core/java/android/view/accessibility/IAccessibilityManager.aidl +++ b/core/java/android/view/accessibility/IAccessibilityManager.aidl @@ -67,9 +67,12 @@ interface IAccessibilityManager { void notifyAccessibilityButtonVisibilityChanged(boolean available); - // Requires WRITE_SECURE_SETTINGS + // Requires Manifest.permission.MANAGE_ACCESSIBILITY void performAccessibilityShortcut(); + // Requires Manifest.permission.MANAGE_ACCESSIBILITY + String getAccessibilityShortcutService(); + // System process only boolean sendFingerprintGesture(int gestureKeyCode); diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java index d24b822279b2..13409554da28 100644 --- a/core/java/android/view/inputmethod/InputMethodManager.java +++ b/core/java/android/view/inputmethod/InputMethodManager.java @@ -62,6 +62,7 @@ import android.view.autofill.AutofillManager; import com.android.internal.annotations.GuardedBy; import com.android.internal.inputmethod.InputMethodDebug; import com.android.internal.inputmethod.InputMethodPrivilegedOperationsRegistry; +import com.android.internal.inputmethod.StartInputFlags; import com.android.internal.inputmethod.StartInputReason; import com.android.internal.inputmethod.UnbindReason; import com.android.internal.os.SomeArgs; @@ -294,30 +295,6 @@ public final class InputMethodManager { private static final SparseArray<InputMethodManager> sInstanceMap = new SparseArray<>(); /** - * @hide Flag for IInputMethodManager.windowGainedFocus: a view in - * the window has input focus. - */ - public static final int CONTROL_WINDOW_VIEW_HAS_FOCUS = 1<<0; - - /** - * @hide Flag for IInputMethodManager.windowGainedFocus: the focus - * is a text editor. - */ - public static final int CONTROL_WINDOW_IS_TEXT_EDITOR = 1<<1; - - /** - * @hide Flag for IInputMethodManager.windowGainedFocus: this is the first - * time the window has gotten focus. - */ - public static final int CONTROL_WINDOW_FIRST = 1<<2; - - /** - * @hide Flag for IInputMethodManager.startInput: this is the first - * time the window has gotten focus. - */ - public static final int CONTROL_START_INITIAL = 1<<8; - - /** * Timeout in milliseconds for delivering a key to an IME. */ static final long INPUT_METHOD_NOT_RESPONDING_TIMEOUT = 2500; @@ -470,26 +447,53 @@ public final class InputMethodManager { } /** - * Checks the consistency between {@link InputMethodManager} state and {@link View} state. + * Returns fallback {@link InputMethodManager} if the called one is not likely to be compatible + * with the given {@code view}. * - * @param view {@link View} to be checked - * @return {@code true} if {@code view} is not {@code null} and there is a {@link Context} - * mismatch between {@link InputMethodManager} and {@code view} - */ - private boolean shouldDispatchToViewContext(@Nullable View view) { + * @param view {@link View} to be checked. + * @return {@code null} when it is unnecessary (or impossible) to use fallback + * {@link InputMethodManager} to which IME API calls need to be re-dispatched. + * Non-{@code null} {@link InputMethodManager} if this method believes it'd be safer to + * re-dispatch IME APIs calls on it. + */ + @Nullable + private InputMethodManager getFallbackInputMethodManagerIfNecessary(@Nullable View view) { if (view == null) { - return false; - } - final int viewDisplayId = view.getContext().getDisplayId(); - if (viewDisplayId != mDisplayId) { - Log.w(TAG, "b/117267690: Context mismatch found. view=" + view + " belongs to" - + " displayId=" + viewDisplayId - + " but InputMethodManager belongs to displayId=" + mDisplayId - + ". Use the right InputMethodManager instance to avoid performance overhead.", - new Throwable()); - return true; - } - return false; + return null; + } + // As evidenced in Bug 118341760, view.getViewRootImpl().getDisplayId() is supposed to be + // more reliable to determine with which display the given view is interacting than + // view.getContext().getDisplayId() / view.getContext().getSystemService(), which can be + // easily messed up by app developers (or library authors) by creating inconsistent + // ContextWrapper objects that re-dispatch those methods to other Context such as + // ApplicationContext. + final ViewRootImpl viewRootImpl = view.getViewRootImpl(); + if (viewRootImpl == null) { + return null; + } + final int viewRootDisplayId = viewRootImpl.getDisplayId(); + if (viewRootDisplayId == mDisplayId) { + // Expected case. Good to go. + return null; + } + final InputMethodManager fallbackImm = + viewRootImpl.mDisplayContext.getSystemService(InputMethodManager.class); + if (fallbackImm == null) { + Log.e(TAG, "b/117267690: Failed to get non-null fallback IMM. view=" + view); + return null; + } + if (fallbackImm.mDisplayId != viewRootDisplayId) { + Log.e(TAG, "b/117267690: Failed to get fallback IMM with expected displayId=" + + viewRootDisplayId + " actual IMM#displayId=" + fallbackImm.mDisplayId + + " view=" + view); + return null; + } + Log.w(TAG, "b/117267690: Display ID mismatch found." + + " ViewRootImpl displayId=" + viewRootDisplayId + + " InputMethodManager displayId=" + mDisplayId + + ". Use the right InputMethodManager instance to avoid performance overhead.", + new Throwable()); + return fallbackImm; } private static boolean canStartInput(View servedView) { @@ -1000,8 +1004,9 @@ public final class InputMethodManager { */ public boolean isActive(View view) { // Re-dispatch if there is a context mismatch. - if (shouldDispatchToViewContext(view)) { - return view.getContext().getSystemService(InputMethodManager.class).isActive(view); + final InputMethodManager fallbackImm = getFallbackInputMethodManagerIfNecessary(view); + if (fallbackImm != null) { + return fallbackImm.isActive(view); } checkFocus(); @@ -1088,9 +1093,9 @@ public final class InputMethodManager { public void displayCompletions(View view, CompletionInfo[] completions) { // Re-dispatch if there is a context mismatch. - if (shouldDispatchToViewContext(view)) { - view.getContext().getSystemService(InputMethodManager.class) - .displayCompletions(view, completions); + final InputMethodManager fallbackImm = getFallbackInputMethodManagerIfNecessary(view); + if (fallbackImm != null) { + fallbackImm.displayCompletions(view, completions); return; } @@ -1113,9 +1118,9 @@ public final class InputMethodManager { public void updateExtractedText(View view, int token, ExtractedText text) { // Re-dispatch if there is a context mismatch. - if (shouldDispatchToViewContext(view)) { - view.getContext().getSystemService(InputMethodManager.class) - .updateExtractedText(view, token, text); + final InputMethodManager fallbackImm = getFallbackInputMethodManagerIfNecessary(view); + if (fallbackImm != null) { + fallbackImm.updateExtractedText(view, token, text); return; } @@ -1161,9 +1166,9 @@ public final class InputMethodManager { */ public boolean showSoftInput(View view, int flags) { // Re-dispatch if there is a context mismatch. - if (shouldDispatchToViewContext(view)) { - return view.getContext().getSystemService(InputMethodManager.class) - .showSoftInput(view, flags); + final InputMethodManager fallbackImm = getFallbackInputMethodManagerIfNecessary(view); + if (fallbackImm != null) { + return fallbackImm.showSoftInput(view, flags); } return showSoftInput(view, flags, null); @@ -1229,9 +1234,9 @@ public final class InputMethodManager { */ public boolean showSoftInput(View view, int flags, ResultReceiver resultReceiver) { // Re-dispatch if there is a context mismatch. - if (shouldDispatchToViewContext(view)) { - return view.getContext().getSystemService(InputMethodManager.class) - .showSoftInput(view, flags, resultReceiver); + final InputMethodManager fallbackImm = getFallbackInputMethodManagerIfNecessary(view); + if (fallbackImm != null) { + return fallbackImm.showSoftInput(view, flags, resultReceiver); } checkFocus(); @@ -1398,8 +1403,9 @@ public final class InputMethodManager { */ public void restartInput(View view) { // Re-dispatch if there is a context mismatch. - if (shouldDispatchToViewContext(view)) { - view.getContext().getSystemService(InputMethodManager.class).restartInput(view); + final InputMethodManager fallbackImm = getFallbackInputMethodManagerIfNecessary(view); + if (fallbackImm != null) { + fallbackImm.restartInput(view); return; } @@ -1417,8 +1423,8 @@ public final class InputMethodManager { } boolean startInputInner(@StartInputReason int startInputReason, - @Nullable IBinder windowGainingFocus, int controlFlags, int softInputMode, - int windowFlags) { + @Nullable IBinder windowGainingFocus, @StartInputFlags int startInputFlags, + @SoftInputModeFlags int softInputMode, int windowFlags) { final View view; synchronized (mH) { view = mServedView; @@ -1440,9 +1446,9 @@ public final class InputMethodManager { Log.e(TAG, "ABORT input: ServedView must be attached to a Window"); return false; } - controlFlags |= CONTROL_WINDOW_VIEW_HAS_FOCUS; + startInputFlags |= StartInputFlags.VIEW_HAS_FOCUS; if (view.onCheckIsTextEditor()) { - controlFlags |= CONTROL_WINDOW_IS_TEXT_EDITOR; + startInputFlags |= StartInputFlags.IS_TEXT_EDITOR; } softInputMode = view.getViewRootImpl().mWindowAttributes.softInputMode; windowFlags = view.getViewRootImpl().mWindowAttributes.flags; @@ -1497,7 +1503,7 @@ public final class InputMethodManager { // If we already have a text box, then this view is already // connected so we want to restart it. if (mCurrentTextBoxAttribute == null) { - controlFlags |= CONTROL_START_INITIAL; + startInputFlags |= StartInputFlags.INITIAL_CONNECTION; } // Hook 'em up and let 'er rip. @@ -1535,11 +1541,11 @@ public final class InputMethodManager { try { if (DEBUG) Log.v(TAG, "START INPUT: view=" + dumpViewInfo(view) + " ic=" - + ic + " tba=" + tba + " controlFlags=#" - + Integer.toHexString(controlFlags)); + + ic + " tba=" + tba + " startInputFlags=" + + InputMethodDebug.startInputFlagsToString(startInputFlags)); final InputBindResult res = mService.startInputOrWindowGainedFocus( - startInputReason, mClient, windowGainingFocus, controlFlags, softInputMode, - windowFlags, tba, servedContext, missingMethodFlags, + startInputReason, mClient, windowGainingFocus, startInputFlags, + softInputMode, windowFlags, tba, servedContext, missingMethodFlags, view.getContext().getApplicationInfo().targetSdkVersion); if (DEBUG) Log.v(TAG, "Starting input: Bind result=" + res); if (res == null) { @@ -1547,7 +1553,8 @@ public final class InputMethodManager { + " null. startInputReason=" + InputMethodDebug.startInputReasonToString(startInputReason) + " editorInfo=" + tba - + " controlFlags=#" + Integer.toHexString(controlFlags)); + + " startInputFlags=" + + InputMethodDebug.startInputFlagsToString(startInputFlags)); return false; } if (res.id != null) { @@ -1754,15 +1761,15 @@ public final class InputMethodManager { focusInLocked(focusedView != null ? focusedView : rootView); } - int controlFlags = 0; + int startInputFlags = 0; if (focusedView != null) { - controlFlags |= CONTROL_WINDOW_VIEW_HAS_FOCUS; + startInputFlags |= StartInputFlags.VIEW_HAS_FOCUS; if (focusedView.onCheckIsTextEditor()) { - controlFlags |= CONTROL_WINDOW_IS_TEXT_EDITOR; + startInputFlags |= StartInputFlags.IS_TEXT_EDITOR; } } if (first) { - controlFlags |= CONTROL_WINDOW_FIRST; + startInputFlags |= StartInputFlags.FIRST_WINDOW_FOCUS_GAIN; } if (checkFocusNoStartInput(forceNewFocus)) { @@ -1771,7 +1778,7 @@ public final class InputMethodManager { // about the window gaining focus, to help make the transition // smooth. if (startInputInner(StartInputReason.WINDOW_FOCUS_GAIN, rootView.getWindowToken(), - controlFlags, softInputMode, windowFlags)) { + startInputFlags, softInputMode, windowFlags)) { return; } } @@ -1783,8 +1790,8 @@ public final class InputMethodManager { if (DEBUG) Log.v(TAG, "Reporting focus gain, without startInput"); mService.startInputOrWindowGainedFocus( StartInputReason.WINDOW_FOCUS_GAIN_REPORT_ONLY, mClient, - rootView.getWindowToken(), controlFlags, softInputMode, windowFlags, null, - null, 0 /* missingMethodFlags */, + rootView.getWindowToken(), startInputFlags, softInputMode, windowFlags, + null, null, 0 /* missingMethodFlags */, rootView.getContext().getApplicationInfo().targetSdkVersion); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); @@ -1827,9 +1834,9 @@ public final class InputMethodManager { public void updateSelection(View view, int selStart, int selEnd, int candidatesStart, int candidatesEnd) { // Re-dispatch if there is a context mismatch. - if (shouldDispatchToViewContext(view)) { - view.getContext().getSystemService(InputMethodManager.class) - .updateSelection(view, selStart, selEnd, candidatesStart, candidatesEnd); + final InputMethodManager fallbackImm = getFallbackInputMethodManagerIfNecessary(view); + if (fallbackImm != null) { + fallbackImm.updateSelection(view, selStart, selEnd, candidatesStart, candidatesEnd); return; } @@ -1871,8 +1878,9 @@ public final class InputMethodManager { */ public void viewClicked(View view) { // Re-dispatch if there is a context mismatch. - if (shouldDispatchToViewContext(view)) { - view.getContext().getSystemService(InputMethodManager.class).viewClicked(view); + final InputMethodManager fallbackImm = getFallbackInputMethodManagerIfNecessary(view); + if (fallbackImm != null) { + fallbackImm.viewClicked(view); return; } @@ -1941,9 +1949,9 @@ public final class InputMethodManager { @Deprecated public void updateCursor(View view, int left, int top, int right, int bottom) { // Re-dispatch if there is a context mismatch. - if (shouldDispatchToViewContext(view)) { - view.getContext().getSystemService(InputMethodManager.class) - .updateCursor(view, left, top, right, bottom); + final InputMethodManager fallbackImm = getFallbackInputMethodManagerIfNecessary(view); + if (fallbackImm != null) { + fallbackImm.updateCursor(view, left, top, right, bottom); return; } @@ -1979,9 +1987,9 @@ public final class InputMethodManager { return; } // Re-dispatch if there is a context mismatch. - if (shouldDispatchToViewContext(view)) { - view.getContext().getSystemService(InputMethodManager.class) - .updateCursorAnchorInfo(view, cursorAnchorInfo); + final InputMethodManager fallbackImm = getFallbackInputMethodManagerIfNecessary(view); + if (fallbackImm != null) { + fallbackImm.updateCursorAnchorInfo(view, cursorAnchorInfo); return; } @@ -2031,9 +2039,9 @@ public final class InputMethodManager { */ public void sendAppPrivateCommand(View view, String action, Bundle data) { // Re-dispatch if there is a context mismatch. - if (shouldDispatchToViewContext(view)) { - view.getContext().getSystemService(InputMethodManager.class) - .sendAppPrivateCommand(view, action, data); + final InputMethodManager fallbackImm = getFallbackInputMethodManagerIfNecessary(view); + if (fallbackImm != null) { + fallbackImm.sendAppPrivateCommand(view, action, data); return; } @@ -2209,9 +2217,9 @@ public final class InputMethodManager { public void dispatchKeyEventFromInputMethod(@Nullable View targetView, @NonNull KeyEvent event) { // Re-dispatch if there is a context mismatch. - if (shouldDispatchToViewContext(targetView)) { - targetView.getContext().getSystemService(InputMethodManager.class) - .dispatchKeyEventFromInputMethod(targetView, event); + final InputMethodManager fallbackImm = getFallbackInputMethodManagerIfNecessary(targetView); + if (fallbackImm != null) { + fallbackImm.dispatchKeyEventFromInputMethod(targetView, event); return; } diff --git a/core/java/android/view/textclassifier/ConversationActions.java b/core/java/android/view/textclassifier/ConversationActions.java new file mode 100644 index 000000000000..5fcf22771ec1 --- /dev/null +++ b/core/java/android/view/textclassifier/ConversationActions.java @@ -0,0 +1,779 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.view.textclassifier; + +import static java.lang.annotation.RetentionPolicy.SOURCE; + +import android.annotation.FloatRange; +import android.annotation.IntRange; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.StringDef; +import android.app.Person; +import android.app.RemoteAction; +import android.os.Bundle; +import android.os.Parcel; +import android.os.Parcelable; +import android.text.SpannedString; +import android.util.ArraySet; + +import com.android.internal.util.Preconditions; + +import java.lang.annotation.Retention; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Set; + +/** + * Represents a list of actions suggested by a {@link TextClassifier} on a given conversation. + * + * @see TextClassifier#suggestConversationActions(Request) + */ +public final class ConversationActions implements Parcelable { + + public static final Creator<ConversationActions> CREATOR = + new Creator<ConversationActions>() { + @Override + public ConversationActions createFromParcel(Parcel in) { + return new ConversationActions(in); + } + + @Override + public ConversationActions[] newArray(int size) { + return new ConversationActions[size]; + } + }; + + /** @hide */ + @Retention(SOURCE) + @StringDef( + value = { + TYPE_VIEW_CALENDAR, + TYPE_VIEW_MAP, + TYPE_TRACK_FLIGHT, + TYPE_OPEN_URL, + TYPE_SEND_SMS, + TYPE_CALL_PHONE, + TYPE_SEND_EMAIL, + TYPE_TEXT_REPLY, + TYPE_CREATE_REMINDER, + TYPE_SHARE_LOCATION + }, + prefix = "TYPE_") + public @interface ActionType {} + + /** + * Indicates an action to view a calendar at a specified time. + */ + public static final String TYPE_VIEW_CALENDAR = "view_calendar"; + /** + * Indicates an action to view the map at a specified location. + */ + public static final String TYPE_VIEW_MAP = "view_map"; + /** + * Indicates an action to track a flight. + */ + public static final String TYPE_TRACK_FLIGHT = "track_flight"; + /** + * Indicates an action to open an URL. + */ + public static final String TYPE_OPEN_URL = "open_url"; + /** + * Indicates an action to send a SMS. + */ + public static final String TYPE_SEND_SMS = "send_sms"; + /** + * Indicates an action to call a phone number. + */ + public static final String TYPE_CALL_PHONE = "call_phone"; + /** + * Indicates an action to send an email. + */ + public static final String TYPE_SEND_EMAIL = "send_email"; + /** + * Indicates an action to reply with a text message. + */ + public static final String TYPE_TEXT_REPLY = "text_reply"; + /** + * Indicates an action to create a reminder. + */ + public static final String TYPE_CREATE_REMINDER = "create_reminder"; + /** + * Indicates an action to reply with a location. + */ + public static final String TYPE_SHARE_LOCATION = "share_location"; + + /** @hide */ + @Retention(SOURCE) + @StringDef( + value = { + HINT_FOR_NOTIFICATION, + HINT_FOR_IN_APP, + }, + prefix = "HINT_") + public @interface Hint {} + /** + * To indicate the generated actions will be used within the app. + */ + public static final String HINT_FOR_IN_APP = "in_app"; + /** + * To indicate the generated actions will be used for notification. + */ + public static final String HINT_FOR_NOTIFICATION = "notification"; + + private List<ConversationAction> mConversationActions; + + /** Constructs a {@link ConversationActions} object. */ + public ConversationActions(@NonNull List<ConversationAction> conversationActions) { + mConversationActions = + Collections.unmodifiableList(Preconditions.checkNotNull(conversationActions)); + } + + private ConversationActions(Parcel in) { + mConversationActions = + Collections.unmodifiableList(in.createTypedArrayList(ConversationAction.CREATOR)); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel parcel, int flags) { + parcel.writeTypedList(mConversationActions); + } + + /** Returns an immutable list of {@link ConversationAction} objects. */ + @NonNull + public List<ConversationAction> getConversationActions() { + return mConversationActions; + } + + /** Represents the action suggested by a {@link TextClassifier} on a given conversation. */ + public static final class ConversationAction implements Parcelable { + + public static final Creator<ConversationAction> CREATOR = + new Creator<ConversationAction>() { + @Override + public ConversationAction createFromParcel(Parcel in) { + return new ConversationAction(in); + } + + @Override + public ConversationAction[] newArray(int size) { + return new ConversationAction[size]; + } + }; + + @NonNull + @ActionType + private final String mType; + @NonNull + private final CharSequence mTextReply; + @Nullable + private final RemoteAction mAction; + + @FloatRange(from = 0, to = 1) + private final float mScore; + + @NonNull + private final Bundle mExtras; + + private ConversationAction( + @NonNull String type, + @Nullable RemoteAction action, + @Nullable CharSequence textReply, + float score, + @NonNull Bundle extras) { + mType = Preconditions.checkNotNull(type); + mAction = action; + mTextReply = textReply; + mScore = score; + mExtras = Preconditions.checkNotNull(extras); + } + + private ConversationAction(Parcel in) { + mType = in.readString(); + mAction = in.readParcelable(null); + mTextReply = in.readCharSequence(); + mScore = in.readFloat(); + mExtras = in.readBundle(); + } + + @Override + public void writeToParcel(Parcel parcel, int flags) { + parcel.writeString(mType); + parcel.writeParcelable(mAction, flags); + parcel.writeCharSequence(mTextReply); + parcel.writeFloat(mScore); + parcel.writeBundle(mExtras); + } + + @Override + public int describeContents() { + return 0; + } + + @NonNull + @ActionType + /** Returns the type of this action, for example, {@link #TYPE_VIEW_CALENDAR}. */ + public String getType() { + return mType; + } + + @Nullable + /** + * Returns a RemoteAction object, which contains the icon, label and a PendingIntent, for + * the specified action type. + */ + public RemoteAction getAction() { + return mAction; + } + + /** + * Returns the confidence score for the specified action. The value ranges from 0 (low + * confidence) to 1 (high confidence). + */ + @FloatRange(from = 0, to = 1) + public float getConfidenceScore() { + return mScore; + } + + /** + * Returns the text reply that could be sent as a reply to the given conversation. + * <p> + * This is only available when the type of the action is {@link #TYPE_TEXT_REPLY}. + */ + @Nullable + public CharSequence getTextReply() { + return mTextReply; + } + + /** + * Returns the extended data related to this conversation action. + * + * <p><b>NOTE: </b>Each call to this method returns a new bundle copy so clients should + * prefer to hold a reference to the returned bundle rather than frequently calling this + * method. + */ + @NonNull + public Bundle getExtras() { + return mExtras.deepCopy(); + } + + /** Builder class to construct {@link ConversationAction}. */ + public static final class Builder { + @Nullable + @ActionType + private String mType; + @Nullable + private RemoteAction mAction; + @Nullable + private CharSequence mTextReply; + private float mScore; + @Nullable + private Bundle mExtras; + + public Builder(@NonNull @ActionType String actionType) { + mType = Preconditions.checkNotNull(actionType); + } + + /** + * Sets an action that may be performed on the given conversation. + */ + @NonNull + public Builder setAction(@Nullable RemoteAction action) { + mAction = action; + return this; + } + + /** + * Sets a text reply that may be performed on the given conversation. + */ + @NonNull + public Builder setTextReply(@Nullable CharSequence textReply) { + mTextReply = textReply; + return this; + } + + /** Sets the confident score. */ + @NonNull + public Builder setConfidenceScore(@FloatRange(from = 0, to = 1) float score) { + mScore = score; + return this; + } + + /** + * Sets the extended data for the conversation action object. + */ + @NonNull + public Builder setExtras(@Nullable Bundle extras) { + mExtras = extras; + return this; + } + + /** Builds the {@link ConversationAction} object. */ + @NonNull + public ConversationAction build() { + return new ConversationAction( + mType, + mAction, + mTextReply, + mScore, + mExtras == null ? Bundle.EMPTY : mExtras.deepCopy()); + } + } + } + + /** Represents a message in the conversation. */ + public static final class Message implements Parcelable { + @Nullable + private final Person mAuthor; + @Nullable + private final ZonedDateTime mComposeTime; + @Nullable + private final CharSequence mText; + @NonNull + private final Bundle mExtras; + + private Message( + @Nullable Person author, + @Nullable ZonedDateTime composeTime, + @Nullable CharSequence text, + @NonNull Bundle bundle) { + mAuthor = author; + mComposeTime = composeTime; + mText = text; + mExtras = Preconditions.checkNotNull(bundle); + } + + private Message(Parcel in) { + mAuthor = in.readParcelable(null); + mComposeTime = + in.readInt() == 0 + ? null + : ZonedDateTime.parse( + in.readString(), DateTimeFormatter.ISO_ZONED_DATE_TIME); + mText = in.readCharSequence(); + mExtras = in.readBundle(); + } + + @Override + public void writeToParcel(Parcel parcel, int flags) { + parcel.writeParcelable(mAuthor, flags); + parcel.writeInt(mComposeTime != null ? 1 : 0); + if (mComposeTime != null) { + parcel.writeString(mComposeTime.format(DateTimeFormatter.ISO_ZONED_DATE_TIME)); + } + parcel.writeCharSequence(mText); + parcel.writeBundle(mExtras); + } + + @Override + public int describeContents() { + return 0; + } + + public static final Creator<Message> CREATOR = + new Creator<Message>() { + @Override + public Message createFromParcel(Parcel in) { + return new Message(in); + } + + @Override + public Message[] newArray(int size) { + return new Message[size]; + } + }; + + /** Returns the person that composed the message. */ + @Nullable + public Person getAuthor() { + return mAuthor; + } + + /** Returns the compose time of the message. */ + @Nullable + public ZonedDateTime getTime() { + return mComposeTime; + } + + /** Returns the text of the message. */ + @Nullable + public CharSequence getText() { + return mText; + } + + /** + * Returns the extended data related to this conversation action. + * + * <p><b>NOTE: </b>Each call to this method returns a new bundle copy so clients should + * prefer to hold a reference to the returned bundle rather than frequently calling this + * method. + */ + @NonNull + public Bundle getExtras() { + return mExtras.deepCopy(); + } + + /** Builder class to construct a {@link Message} */ + public static final class Builder { + @Nullable + private Person mAuthor; + @Nullable + private ZonedDateTime mComposeTime; + @Nullable + private CharSequence mText; + @Nullable + private Bundle mExtras; + + /** Sets the person who composed this message. */ + @NonNull + public Builder setAuthor(@Nullable Person author) { + mAuthor = author; + return this; + } + + /** Sets the text of this message */ + @NonNull + public Builder setText(@Nullable CharSequence text) { + mText = text; + return this; + } + + /** Sets the compose time of this message */ + @NonNull + public Builder setComposeTime(@Nullable ZonedDateTime composeTime) { + mComposeTime = composeTime; + return this; + } + + /** Sets a set of extended data to the message. */ + @NonNull + public Builder setExtras(@Nullable Bundle bundle) { + this.mExtras = bundle; + return this; + } + + /** Builds the {@link Message} object. */ + @NonNull + public Message build() { + return new Message( + mAuthor, + mComposeTime, + mText == null ? null : new SpannedString(mText), + mExtras == null ? new Bundle() : mExtras.deepCopy()); + } + } + } + + /** Configuration object for specifying what action types to identify. */ + public static final class TypeConfig implements Parcelable { + @NonNull + @ActionType + private final Set<String> mExcludedTypes; + @NonNull + @ActionType + private final Set<String> mIncludedTypes; + private final boolean mIncludeTypesFromTextClassifier; + + private TypeConfig( + @NonNull Set<String> includedTypes, + @NonNull Set<String> excludedTypes, + boolean includeTypesFromTextClassifier) { + mIncludedTypes = Preconditions.checkNotNull(includedTypes); + mExcludedTypes = Preconditions.checkNotNull(excludedTypes); + mIncludeTypesFromTextClassifier = includeTypesFromTextClassifier; + } + + private TypeConfig(Parcel in) { + mIncludedTypes = new ArraySet<>(in.createStringArrayList()); + mExcludedTypes = new ArraySet<>(in.createStringArrayList()); + mIncludeTypesFromTextClassifier = in.readByte() != 0; + } + + @Override + public void writeToParcel(Parcel parcel, int flags) { + parcel.writeStringList(new ArrayList<>(mIncludedTypes)); + parcel.writeStringList(new ArrayList<>(mExcludedTypes)); + parcel.writeByte((byte) (mIncludeTypesFromTextClassifier ? 1 : 0)); + } + + @Override + public int describeContents() { + return 0; + } + + public static final Creator<TypeConfig> CREATOR = + new Creator<TypeConfig>() { + @Override + public TypeConfig createFromParcel(Parcel in) { + return new TypeConfig(in); + } + + @Override + public TypeConfig[] newArray(int size) { + return new TypeConfig[size]; + } + }; + + /** + * Returns a final list of types that the text classifier should look for. + * + * <p>NOTE: This method is intended for use by a text classifier. + * + * @param defaultTypes types the text classifier thinks should be included before factoring + * in the included/excluded types given by the client. + */ + @NonNull + public Collection<String> resolveTypes(@Nullable Collection<String> defaultTypes) { + Set<String> types = new ArraySet<>(); + if (mIncludeTypesFromTextClassifier && defaultTypes != null) { + types.addAll(defaultTypes); + } + types.addAll(mIncludedTypes); + types.removeAll(mExcludedTypes); + return Collections.unmodifiableCollection(types); + } + + /** + * Return whether the client allows the text classifier to include its own list of default + * types. If this function returns {@code true}, the text classifier can consider specifying + * a default list of entity types in {@link #resolveTypes(Collection)}. + * + * <p>NOTE: This method is intended for use by a text classifier. + * + * @see #resolveTypes(Collection) + */ + public boolean shouldIncludeTypesFromTextClassifier() { + return mIncludeTypesFromTextClassifier; + } + + /** Builder class to construct the {@link TypeConfig} object. */ + public static final class Builder { + @Nullable + private Collection<String> mExcludedTypes; + @Nullable + private Collection<String> mIncludedTypes; + private boolean mIncludeTypesFromTextClassifier = true; + + /** + * Sets a collection of types that are explicitly included, for example, {@link + * #TYPE_VIEW_CALENDAR}. + */ + @NonNull + public Builder setIncludedTypes( + @Nullable @ActionType Collection<String> includedTypes) { + mIncludedTypes = includedTypes; + return this; + } + + /** + * Sets a collection of types that are explicitly excluded, for example, {@link + * #TYPE_VIEW_CALENDAR}. + */ + @NonNull + public Builder setExcludedTypes( + @Nullable @ActionType Collection<String> excludedTypes) { + mExcludedTypes = excludedTypes; + return this; + } + + /** + * Specifies whether or not to include the types suggested by the text classifier. By + * default, it is included. + */ + @NonNull + public Builder includeTypesFromTextClassifier(boolean includeTypesFromTextClassifier) { + mIncludeTypesFromTextClassifier = includeTypesFromTextClassifier; + return this; + } + + /** + * Combines all of the options that have been set and returns a new {@link TypeConfig} + * object. + */ + @NonNull + public TypeConfig build() { + return new TypeConfig( + mIncludedTypes == null + ? Collections.emptySet() + : new ArraySet<>(mIncludedTypes), + mExcludedTypes == null + ? Collections.emptySet() + : new ArraySet<>(mExcludedTypes), + mIncludeTypesFromTextClassifier); + } + } + } + + /** + * A request object for generating conversation action suggestions. + * + * @see TextClassifier#suggestConversationActions(Request) + */ + public static final class Request implements Parcelable { + @NonNull + private final List<Message> mConversation; + @NonNull + private final TypeConfig mTypeConfig; + private final int mMaxSuggestions; + @NonNull + @Hint + private final List<String> mHints; + + private Request( + @NonNull List<Message> conversation, + @NonNull TypeConfig typeConfig, + int maxSuggestions, + @Nullable @Hint List<String> hints) { + mConversation = Preconditions.checkNotNull(conversation); + mTypeConfig = Preconditions.checkNotNull(typeConfig); + mMaxSuggestions = maxSuggestions; + mHints = hints; + } + + private Request(Parcel in) { + List<Message> conversation = new ArrayList<>(); + in.readParcelableList(conversation, null); + mConversation = Collections.unmodifiableList(conversation); + mTypeConfig = in.readParcelable(null); + mMaxSuggestions = in.readInt(); + List<String> hints = new ArrayList<>(); + in.readStringList(hints); + mHints = Collections.unmodifiableList(hints); + } + + @Override + public void writeToParcel(Parcel parcel, int flags) { + parcel.writeParcelableList(mConversation, flags); + parcel.writeParcelable(mTypeConfig, flags); + parcel.writeInt(mMaxSuggestions); + parcel.writeStringList(mHints); + } + + @Override + public int describeContents() { + return 0; + } + + public static final Creator<Request> CREATOR = + new Creator<Request>() { + @Override + public Request createFromParcel(Parcel in) { + return new Request(in); + } + + @Override + public Request[] newArray(int size) { + return new Request[size]; + } + }; + + /** Returns the type config. */ + @NonNull + public TypeConfig getTypeConfig() { + return mTypeConfig; + } + + /** Returns an immutable list of messages that make up the conversation. */ + @NonNull + public List<Message> getConversation() { + return mConversation; + } + + /** + * Return the maximal number of suggestions the caller wants, value 0 means no restriction. + */ + @IntRange(from = 0) + public int getMaxSuggestions() { + return mMaxSuggestions; + } + + /** Returns an immutable list of hints */ + @Nullable + @Hint + public List<String> getHints() { + return mHints; + } + + /** Builder object to construct the {@link Request} object. */ + public static final class Builder { + @NonNull + private List<Message> mConversation; + @Nullable + private TypeConfig mTypeConfig; + private int mMaxSuggestions; + @Nullable + @Hint + private List<String> mHints; + + /** + * Constructs a builder. + * + * @param conversation the conversation that the text classifier is going to generate + * actions for. + */ + public Builder(@NonNull List<Message> conversation) { + mConversation = Preconditions.checkNotNull(conversation); + } + + /** + * Sets the hints to help text classifier to generate actions. It could be used to help + * text classifier to infer what types of actions the caller may be interested in. + */ + public Builder setHints(@Nullable @Hint List<String> hints) { + mHints = hints; + return this; + } + + /** Sets the type config. */ + @NonNull + public Builder setTypeConfig(@Nullable TypeConfig typeConfig) { + mTypeConfig = typeConfig; + return this; + } + + /** Sets the maximum number of suggestions you want. + * <p> + * Value 0 means no restriction. + */ + @NonNull + public Builder setMaxSuggestions(@IntRange(from = 0) int maxSuggestions) { + mMaxSuggestions = Preconditions.checkArgumentNonnegative(maxSuggestions); + return this; + } + + /** Builds the {@link Request} object. */ + @NonNull + public Request build() { + return new Request( + Collections.unmodifiableList(mConversation), + mTypeConfig == null ? new TypeConfig.Builder().build() : mTypeConfig, + mMaxSuggestions, + mHints == null + ? Collections.emptyList() + : Collections.unmodifiableList(mHints)); + } + } + } +} diff --git a/core/java/android/view/textclassifier/TextClassification.java b/core/java/android/view/textclassifier/TextClassification.java index 9511a9efb3d5..f6c3d770d2a8 100644 --- a/core/java/android/view/textclassifier/TextClassification.java +++ b/core/java/android/view/textclassifier/TextClassification.java @@ -33,6 +33,7 @@ import android.graphics.drawable.AdaptiveIconDrawable; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.graphics.drawable.Icon; +import android.os.Bundle; import android.os.LocaleList; import android.os.Parcel; import android.os.Parcelable; @@ -126,6 +127,7 @@ public final class TextClassification implements Parcelable { @NonNull private final List<RemoteAction> mActions; @NonNull private final EntityConfidence mEntityConfidence; @Nullable private final String mId; + @NonNull private final Bundle mExtras; private TextClassification( @Nullable String text, @@ -135,7 +137,8 @@ public final class TextClassification implements Parcelable { @Nullable OnClickListener legacyOnClickListener, @NonNull List<RemoteAction> actions, @NonNull Map<String, Float> entityConfidence, - @Nullable String id) { + @Nullable String id, + @NonNull Bundle extras) { mText = text; mLegacyIcon = legacyIcon; mLegacyLabel = legacyLabel; @@ -144,6 +147,7 @@ public final class TextClassification implements Parcelable { mActions = Collections.unmodifiableList(actions); mEntityConfidence = new EntityConfidence(entityConfidence); mId = id; + mExtras = extras; } /** @@ -255,6 +259,18 @@ public final class TextClassification implements Parcelable { return mId; } + /** + * Returns the extended data. + * + * <p><b>NOTE: </b>Each call to this method returns a new bundle copy so clients should + * prefer to hold a reference to the returned bundle rather than frequently calling this + * method. + */ + @NonNull + public Bundle getExtras() { + return mExtras.deepCopy(); + } + @Override public String toString() { return String.format(Locale.US, @@ -359,6 +375,7 @@ public final class TextClassification implements Parcelable { @Nullable private Intent mLegacyIntent; @Nullable private OnClickListener mLegacyOnClickListener; @Nullable private String mId; + @Nullable private Bundle mExtras; /** * Sets the classified text. @@ -471,12 +488,22 @@ public final class TextClassification implements Parcelable { } /** + * Sets the extended data. + */ + @NonNull + public Builder setExtras(@Nullable Bundle extras) { + mExtras = extras; + return this; + } + + /** * Builds and returns a {@link TextClassification} object. */ @NonNull public TextClassification build() { return new TextClassification(mText, mLegacyIcon, mLegacyLabel, mLegacyIntent, - mLegacyOnClickListener, mActions, mEntityConfidence, mId); + mLegacyOnClickListener, mActions, mEntityConfidence, mId, + mExtras == null ? Bundle.EMPTY : mExtras.deepCopy()); } } @@ -490,18 +517,21 @@ public final class TextClassification implements Parcelable { private final int mEndIndex; @Nullable private final LocaleList mDefaultLocales; @Nullable private final ZonedDateTime mReferenceTime; + @NonNull private final Bundle mExtras; private Request( CharSequence text, int startIndex, int endIndex, LocaleList defaultLocales, - ZonedDateTime referenceTime) { + ZonedDateTime referenceTime, + Bundle extras) { mText = text; mStartIndex = startIndex; mEndIndex = endIndex; mDefaultLocales = defaultLocales; mReferenceTime = referenceTime; + mExtras = extras; } /** @@ -548,6 +578,18 @@ public final class TextClassification implements Parcelable { } /** + * Returns the extended data. + * + * <p><b>NOTE: </b>Each call to this method returns a new bundle copy so clients should + * prefer to hold a reference to the returned bundle rather than frequently calling this + * method. + */ + @NonNull + public Bundle getExtras() { + return mExtras.deepCopy(); + } + + /** * A builder for building TextClassification requests. */ public static final class Builder { @@ -555,6 +597,7 @@ public final class TextClassification implements Parcelable { private final CharSequence mText; private final int mStartIndex; private final int mEndIndex; + private Bundle mExtras; @Nullable private LocaleList mDefaultLocales; @Nullable private ZonedDateTime mReferenceTime; @@ -602,11 +645,23 @@ public final class TextClassification implements Parcelable { } /** + * Sets the extended data. + * + * @return this builder + */ + @NonNull + public Builder setExtras(@Nullable Bundle extras) { + mExtras = extras; + return this; + } + + /** * Builds and returns the request object. */ @NonNull public Request build() { - return new Request(mText, mStartIndex, mEndIndex, mDefaultLocales, mReferenceTime); + return new Request(mText, mStartIndex, mEndIndex, mDefaultLocales, mReferenceTime, + mExtras == null ? Bundle.EMPTY : mExtras.deepCopy()); } } @@ -628,6 +683,7 @@ public final class TextClassification implements Parcelable { if (mReferenceTime != null) { dest.writeString(mReferenceTime.toString()); } + dest.writeBundle(mExtras); } public static final Parcelable.Creator<Request> CREATOR = @@ -649,6 +705,7 @@ public final class TextClassification implements Parcelable { mEndIndex = in.readInt(); mDefaultLocales = in.readInt() == 0 ? null : LocaleList.CREATOR.createFromParcel(in); mReferenceTime = in.readInt() == 0 ? null : ZonedDateTime.parse(in.readString()); + mExtras = in.readBundle(); } } @@ -664,6 +721,7 @@ public final class TextClassification implements Parcelable { dest.writeTypedList(mActions); mEntityConfidence.writeToParcel(dest, flags); dest.writeString(mId); + dest.writeBundle(mExtras); } public static final Parcelable.Creator<TextClassification> CREATOR = @@ -695,6 +753,7 @@ public final class TextClassification implements Parcelable { mLegacyIntent = null; // mLegacyIntent is not parcelled. mEntityConfidence = EntityConfidence.CREATOR.createFromParcel(in); mId = in.readString(); + mExtras = in.readBundle(); } // Best effort attempt to try to load a drawable from the provided icon. @@ -717,67 +776,4 @@ public final class TextClassification implements Parcelable { } return null; } - - // TODO: Remove once apps can build against the latest sdk. - /** - * Optional input parameters for generating TextClassification. - * @hide - */ - public static final class Options { - - @Nullable private final TextClassificationSessionId mSessionId; - @Nullable private final Request mRequest; - @Nullable private LocaleList mDefaultLocales; - @Nullable private ZonedDateTime mReferenceTime; - - public Options() { - this(null, null); - } - - private Options( - @Nullable TextClassificationSessionId sessionId, @Nullable Request request) { - mSessionId = sessionId; - mRequest = request; - } - - /** Helper to create Options from a Request. */ - public static Options from(TextClassificationSessionId sessionId, Request request) { - final Options options = new Options(sessionId, request); - options.setDefaultLocales(request.getDefaultLocales()); - options.setReferenceTime(request.getReferenceTime()); - return options; - } - - /** @param defaultLocales ordered list of locale preferences. */ - public Options setDefaultLocales(@Nullable LocaleList defaultLocales) { - mDefaultLocales = defaultLocales; - return this; - } - - /** @param referenceTime refrence time used for interpreting relatives dates */ - public Options setReferenceTime(@Nullable ZonedDateTime referenceTime) { - mReferenceTime = referenceTime; - return this; - } - - @Nullable - public LocaleList getDefaultLocales() { - return mDefaultLocales; - } - - @Nullable - public ZonedDateTime getReferenceTime() { - return mReferenceTime; - } - - @Nullable - public Request getRequest() { - return mRequest; - } - - @Nullable - public TextClassificationSessionId getSessionId() { - return mSessionId; - } - } } diff --git a/core/java/android/view/textclassifier/TextClassifier.java b/core/java/android/view/textclassifier/TextClassifier.java index 2e92f14c931c..e6757448b5bb 100644 --- a/core/java/android/view/textclassifier/TextClassifier.java +++ b/core/java/android/view/textclassifier/TextClassifier.java @@ -325,6 +325,17 @@ public interface TextClassifier { } /** + * Suggests and returns a list of actions according to the given conversation. + */ + @WorkerThread + default ConversationActions suggestConversationActions( + @NonNull ConversationActions.Request request) { + Preconditions.checkNotNull(request); + Utils.checkMainThread(); + return new ConversationActions(Collections.emptyList()); + } + + /** * Reports a selection event. * * <p><strong>NOTE: </strong>If a TextClassifier has been destroyed, calls to this method should diff --git a/core/java/android/view/textclassifier/TextLinks.java b/core/java/android/view/textclassifier/TextLinks.java index 1cac3ede363a..b31438f757e2 100644 --- a/core/java/android/view/textclassifier/TextLinks.java +++ b/core/java/android/view/textclassifier/TextLinks.java @@ -20,7 +20,6 @@ import android.annotation.FloatRange; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; -import android.annotation.UnsupportedAppUsage; import android.content.Context; import android.os.LocaleList; import android.os.Parcel; @@ -29,8 +28,6 @@ import android.text.Spannable; import android.text.method.MovementMethod; import android.text.style.ClickableSpan; import android.text.style.URLSpan; -import android.text.util.Linkify; -import android.text.util.Linkify.LinkifyMask; import android.view.View; import android.view.textclassifier.TextClassifier.EntityType; import android.widget.TextView; @@ -634,125 +631,4 @@ public final class TextLinks implements Parcelable { return new TextLinks(mFullText, mLinks); } } - - // TODO: Remove once apps can build against the latest sdk. - /** - * Optional input parameters for generating TextLinks. - * @hide - */ - public static final class Options { - - @Nullable private final TextClassificationSessionId mSessionId; - @Nullable private final Request mRequest; - @Nullable private LocaleList mDefaultLocales; - @Nullable private TextClassifier.EntityConfig mEntityConfig; - private boolean mLegacyFallback; - - private @ApplyStrategy int mApplyStrategy; - private Function<TextLink, TextLinkSpan> mSpanFactory; - - private String mCallingPackageName; - - @UnsupportedAppUsage - public Options() { - this(null, null); - } - - private Options( - @Nullable TextClassificationSessionId sessionId, @Nullable Request request) { - mSessionId = sessionId; - mRequest = request; - } - - /** Helper to create Options from a Request. */ - public static Options from(TextClassificationSessionId sessionId, Request request) { - final Options options = new Options(sessionId, request); - options.setDefaultLocales(request.getDefaultLocales()); - options.setEntityConfig(request.getEntityConfig()); - return options; - } - - /** Returns a new options object based on the specified link mask. */ - public static Options fromLinkMask(@LinkifyMask int mask) { - final List<String> entitiesToFind = new ArrayList<>(); - - if ((mask & Linkify.WEB_URLS) != 0) { - entitiesToFind.add(TextClassifier.TYPE_URL); - } - if ((mask & Linkify.EMAIL_ADDRESSES) != 0) { - entitiesToFind.add(TextClassifier.TYPE_EMAIL); - } - if ((mask & Linkify.PHONE_NUMBERS) != 0) { - entitiesToFind.add(TextClassifier.TYPE_PHONE); - } - if ((mask & Linkify.MAP_ADDRESSES) != 0) { - entitiesToFind.add(TextClassifier.TYPE_ADDRESS); - } - - return new Options().setEntityConfig( - TextClassifier.EntityConfig.createWithEntityList(entitiesToFind)); - } - - /** @param defaultLocales ordered list of locale preferences. */ - public Options setDefaultLocales(@Nullable LocaleList defaultLocales) { - mDefaultLocales = defaultLocales; - return this; - } - - /** @param entityConfig definition of which entity types to look for. */ - public Options setEntityConfig(@Nullable TextClassifier.EntityConfig entityConfig) { - mEntityConfig = entityConfig; - return this; - } - - /** @param applyStrategy strategy to use when resolving conflicts. */ - public Options setApplyStrategy(@ApplyStrategy int applyStrategy) { - checkValidApplyStrategy(applyStrategy); - mApplyStrategy = applyStrategy; - return this; - } - - /** @param spanFactory factory for converting TextLink to TextLinkSpan. */ - public Options setSpanFactory(@Nullable Function<TextLink, TextLinkSpan> spanFactory) { - mSpanFactory = spanFactory; - return this; - } - - @Nullable - public LocaleList getDefaultLocales() { - return mDefaultLocales; - } - - @Nullable - public TextClassifier.EntityConfig getEntityConfig() { - return mEntityConfig; - } - - @ApplyStrategy - public int getApplyStrategy() { - return mApplyStrategy; - } - - @Nullable - public Function<TextLink, TextLinkSpan> getSpanFactory() { - return mSpanFactory; - } - - @Nullable - public Request getRequest() { - return mRequest; - } - - @Nullable - public TextClassificationSessionId getSessionId() { - return mSessionId; - } - - private static void checkValidApplyStrategy(int applyStrategy) { - if (applyStrategy != APPLY_STRATEGY_IGNORE && applyStrategy != APPLY_STRATEGY_REPLACE) { - throw new IllegalArgumentException( - "Invalid apply strategy. See TextLinks.ApplyStrategy for options."); - } - } - } } diff --git a/core/java/android/view/textclassifier/TextSelection.java b/core/java/android/view/textclassifier/TextSelection.java index a4550afed15a..52d01ea6b035 100644 --- a/core/java/android/view/textclassifier/TextSelection.java +++ b/core/java/android/view/textclassifier/TextSelection.java @@ -375,56 +375,4 @@ public final class TextSelection implements Parcelable { mEntityConfidence = EntityConfidence.CREATOR.createFromParcel(in); mId = in.readString(); } - - - // TODO: Remove once apps can build against the latest sdk. - /** - * Optional input parameters for generating TextSelection. - * @hide - */ - public static final class Options { - - @Nullable private final TextClassificationSessionId mSessionId; - @Nullable private final Request mRequest; - @Nullable private LocaleList mDefaultLocales; - private boolean mDarkLaunchAllowed; - - public Options() { - this(null, null); - } - - private Options( - @Nullable TextClassificationSessionId sessionId, @Nullable Request request) { - mSessionId = sessionId; - mRequest = request; - } - - /** Helper to create Options from a Request. */ - public static Options from(TextClassificationSessionId sessionId, Request request) { - final Options options = new Options(sessionId, request); - options.setDefaultLocales(request.getDefaultLocales()); - return options; - } - - /** @param defaultLocales ordered list of locale preferences. */ - public Options setDefaultLocales(@Nullable LocaleList defaultLocales) { - mDefaultLocales = defaultLocales; - return this; - } - - @Nullable - public LocaleList getDefaultLocales() { - return mDefaultLocales; - } - - @Nullable - public Request getRequest() { - return mRequest; - } - - @Nullable - public TextClassificationSessionId getSessionId() { - return mSessionId; - } - } } diff --git a/core/java/com/android/internal/inputmethod/InputMethodDebug.java b/core/java/com/android/internal/inputmethod/InputMethodDebug.java index a5dc3d1b7138..025e27bc45c9 100644 --- a/core/java/com/android/internal/inputmethod/InputMethodDebug.java +++ b/core/java/com/android/internal/inputmethod/InputMethodDebug.java @@ -19,6 +19,8 @@ package com.android.internal.inputmethod; import android.view.WindowManager; import android.view.WindowManager.LayoutParams.SoftInputModeFlags; +import java.util.StringJoiner; + /** * Provides useful methods for debugging. */ @@ -96,7 +98,7 @@ public final class InputMethodDebug { * @return {@link String} message corresponds for the given {@code softInputMode}. */ public static String softInputModeToString(@SoftInputModeFlags int softInputMode) { - final StringBuilder sb = new StringBuilder(); + final StringJoiner joiner = new StringJoiner("|"); final int state = softInputMode & WindowManager.LayoutParams.SOFT_INPUT_MASK_STATE; final int adjust = softInputMode & WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST; final boolean isForwardNav = @@ -104,55 +106,75 @@ public final class InputMethodDebug { switch (state) { case WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED: - sb.append("STATE_UNSPECIFIED"); + joiner.add("STATE_UNSPECIFIED"); break; case WindowManager.LayoutParams.SOFT_INPUT_STATE_UNCHANGED: - sb.append("STATE_UNCHANGED"); + joiner.add("STATE_UNCHANGED"); break; case WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN: - sb.append("STATE_HIDDEN"); + joiner.add("STATE_HIDDEN"); break; case WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN: - sb.append("STATE_ALWAYS_HIDDEN"); + joiner.add("STATE_ALWAYS_HIDDEN"); break; case WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE: - sb.append("STATE_VISIBLE"); + joiner.add("STATE_VISIBLE"); break; case WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE: - sb.append("STATE_ALWAYS_VISIBLE"); + joiner.add("STATE_ALWAYS_VISIBLE"); break; default: - sb.append("STATE_UNKNOWN("); - sb.append(state); - sb.append(")"); + joiner.add("STATE_UNKNOWN(" + state + ")"); break; } switch (adjust) { case WindowManager.LayoutParams.SOFT_INPUT_ADJUST_UNSPECIFIED: - sb.append("|ADJUST_UNSPECIFIED"); + joiner.add("ADJUST_UNSPECIFIED"); break; case WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE: - sb.append("|ADJUST_RESIZE"); + joiner.add("ADJUST_RESIZE"); break; case WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN: - sb.append("|ADJUST_PAN"); + joiner.add("ADJUST_PAN"); break; case WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING: - sb.append("|ADJUST_NOTHING"); + joiner.add("ADJUST_NOTHING"); break; default: - sb.append("|ADJUST_UNKNOWN("); - sb.append(adjust); - sb.append(")"); + joiner.add("ADJUST_UNKNOWN(" + adjust + ")"); break; } if (isForwardNav) { // This is a special bit that is set by the system only during the window navigation. - sb.append("|IS_FORWARD_NAVIGATION"); + joiner.add("IS_FORWARD_NAVIGATION"); + } + + return joiner.setEmptyValue("(none)").toString(); + } + + /** + * Converts {@link StartInputFlags} to {@link String} for debug logging. + * + * @param startInputFlags integer constant for {@link StartInputFlags}. + * @return {@link String} message corresponds for the given {@code startInputFlags}. + */ + public static String startInputFlagsToString(@StartInputFlags int startInputFlags) { + final StringJoiner joiner = new StringJoiner("|"); + if ((startInputFlags & StartInputFlags.VIEW_HAS_FOCUS) != 0) { + joiner.add("VIEW_HAS_FOCUS"); + } + if ((startInputFlags & StartInputFlags.IS_TEXT_EDITOR) != 0) { + joiner.add("IS_TEXT_EDITOR"); + } + if ((startInputFlags & StartInputFlags.FIRST_WINDOW_FOCUS_GAIN) != 0) { + joiner.add("FIRST_WINDOW_FOCUS_GAIN"); + } + if ((startInputFlags & StartInputFlags.INITIAL_CONNECTION) != 0) { + joiner.add("INITIAL_CONNECTION"); } - return sb.toString(); + return joiner.setEmptyValue("(none)").toString(); } } diff --git a/core/java/com/android/internal/inputmethod/StartInputFlags.java b/core/java/com/android/internal/inputmethod/StartInputFlags.java new file mode 100644 index 000000000000..ba26d8db533c --- /dev/null +++ b/core/java/com/android/internal/inputmethod/StartInputFlags.java @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.inputmethod; + +import static java.lang.annotation.RetentionPolicy.SOURCE; + +import android.annotation.IntDef; + +import java.lang.annotation.Retention; + +/** + * Describes additional info in + * {@link com.android.internal.view.IInputMethodManager#startInputOrWindowGainedFocus}. + */ +@Retention(SOURCE) +@IntDef(flag = true, value = { + StartInputFlags.VIEW_HAS_FOCUS, + StartInputFlags.IS_TEXT_EDITOR, + StartInputFlags.FIRST_WINDOW_FOCUS_GAIN, + StartInputFlags.INITIAL_CONNECTION}) +public @interface StartInputFlags { + /** + * There is a focused view in the focused window. + */ + int VIEW_HAS_FOCUS = 1; + + /** + * The focused view is a text editor. + */ + int IS_TEXT_EDITOR = 2; + + /** + * This is the first time the window has gotten focus. + */ + int FIRST_WINDOW_FOCUS_GAIN = 4; + + /** + * An internal concept to distinguish "start" and "restart". This concept doesn't look well + * documented hence we probably need to revisit this though. + */ + int INITIAL_CONNECTION = 8; +} diff --git a/core/java/com/android/internal/net/NetworkStatsFactory.java b/core/java/com/android/internal/net/NetworkStatsFactory.java index d1c279918bb4..0a7cff6856e5 100644 --- a/core/java/com/android/internal/net/NetworkStatsFactory.java +++ b/core/java/com/android/internal/net/NetworkStatsFactory.java @@ -113,11 +113,12 @@ public class NetworkStatsFactory { /** * Applies 464xlat adjustments with ifaces noted with {@link #noteStackedIface(String, String)}. - * @see NetworkStats#apply464xlatAdjustments(NetworkStats, NetworkStats, Map) + * @see NetworkStats#apply464xlatAdjustments(NetworkStats, NetworkStats, Map, boolean) */ public static void apply464xlatAdjustments(NetworkStats baseTraffic, - NetworkStats stackedTraffic) { - NetworkStats.apply464xlatAdjustments(baseTraffic, stackedTraffic, sStackedIfaces); + NetworkStats stackedTraffic, boolean useBpfStats) { + NetworkStats.apply464xlatAdjustments(baseTraffic, stackedTraffic, sStackedIfaces, + useBpfStats); } @VisibleForTesting @@ -263,7 +264,7 @@ public class NetworkStatsFactory { // No locking here: apply464xlatAdjustments behaves fine with an add-only ConcurrentHashMap. // TODO: remove this and only apply adjustments in NetworkStatsService. - stats.apply464xlatAdjustments(sStackedIfaces); + stats.apply464xlatAdjustments(sStackedIfaces, mUseBpfStats); return stats; } diff --git a/core/java/com/android/internal/os/KernelCpuThreadReader.java b/core/java/com/android/internal/os/KernelCpuThreadReader.java new file mode 100644 index 000000000000..6b277a0bd512 --- /dev/null +++ b/core/java/com/android/internal/os/KernelCpuThreadReader.java @@ -0,0 +1,306 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.os; + +import android.annotation.Nullable; +import android.os.Process; +import android.util.Slog; + +import com.android.internal.annotations.VisibleForTesting; + +import java.io.IOException; +import java.nio.file.DirectoryStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; + +/** + * Given a process, will iterate over the child threads of the process, and return the CPU usage + * statistics for each child thread. The CPU usage statistics contain the amount of time spent in a + * frequency band. + */ +public class KernelCpuThreadReader { + + private static final String TAG = "KernelCpuThreadReader"; + + private static final boolean DEBUG = false; + + /** + * The name of the file to read CPU statistics from, must be found in {@code + * /proc/$PID/task/$TID} + */ + private static final String CPU_STATISTICS_FILENAME = "time_in_state"; + + /** + * The name of the file to read process command line invocation from, must be found in + * {@code /proc/$PID/} + */ + private static final String PROCESS_NAME_FILENAME = "cmdline"; + + /** + * The name of the file to read thread name from, must be found in + * {@code /proc/$PID/task/$TID} + */ + private static final String THREAD_NAME_FILENAME = "comm"; + + /** + * Default process name when the name can't be read + */ + private static final String DEFAULT_PROCESS_NAME = "unknown_process"; + + /** + * Default thread name when the name can't be read + */ + private static final String DEFAULT_THREAD_NAME = "unknown_thread"; + + /** + * Default mount location of the {@code proc} filesystem + */ + private static final Path DEFAULT_PROC_PATH = Paths.get("/proc"); + + /** + * The initial {@code time_in_state} file for {@link ProcTimeInStateReader} + */ + private static final Path DEFAULT_INITIAL_TIME_IN_STATE_PATH = + DEFAULT_PROC_PATH.resolve("self/time_in_state"); + + /** + * Where the proc filesystem is mounted + */ + private final Path mProcPath; + + /** + * Frequencies read from the {@code time_in_state} file. Read from {@link + * #mProcTimeInStateReader#getCpuFrequenciesKhz()} and cast to {@code int[]} + */ + private final int[] mFrequenciesKhz; + + /** + * Used to read and parse {@code time_in_state} files + */ + private final ProcTimeInStateReader mProcTimeInStateReader; + + private KernelCpuThreadReader() throws IOException { + this(DEFAULT_PROC_PATH, DEFAULT_INITIAL_TIME_IN_STATE_PATH); + } + + /** + * Create with a path where `proc` is mounted. Used primarily for testing + * + * @param procPath where `proc` is mounted (to find, see {@code mount | grep ^proc}) + * @param initialTimeInStatePath where the initial {@code time_in_state} file exists to define + * format + */ + @VisibleForTesting + public KernelCpuThreadReader(Path procPath, Path initialTimeInStatePath) throws IOException { + mProcPath = procPath; + mProcTimeInStateReader = new ProcTimeInStateReader(initialTimeInStatePath); + + // Copy mProcTimeInState's frequencies, casting the longs to ints + long[] frequenciesKhz = mProcTimeInStateReader.getFrequenciesKhz(); + mFrequenciesKhz = new int[frequenciesKhz.length]; + for (int i = 0; i < frequenciesKhz.length; i++) { + mFrequenciesKhz[i] = (int) frequenciesKhz[i]; + } + } + + /** + * Create the reader and handle exceptions during creation + * + * @return the reader, null if an exception was thrown during creation + */ + @Nullable + public static KernelCpuThreadReader create() { + try { + return new KernelCpuThreadReader(); + } catch (IOException e) { + Slog.e(TAG, "Failed to initialize KernelCpuThreadReader", e); + return null; + } + } + + /** + * Read all of the CPU usage statistics for each child thread of the current process + * + * @return process CPU usage containing usage of all child threads + */ + @Nullable + public ProcessCpuUsage getCurrentProcessCpuUsage() { + return getProcessCpuUsage( + mProcPath.resolve("self"), + Process.myPid(), + Process.myUid()); + } + + /** + * Read all of the CPU usage statistics for each child thread of a process + * + * @param processPath the {@code /proc} path of the thread + * @param processId the ID of the process + * @param uid the ID of the user who owns the process + * @return process CPU usage containing usage of all child threads + */ + @Nullable + private ProcessCpuUsage getProcessCpuUsage(Path processPath, int processId, int uid) { + if (DEBUG) { + Slog.d(TAG, "Reading CPU thread usages with directory " + processPath + + " process ID " + processId + + " and user ID " + uid); + } + + final Path allThreadsPath = processPath.resolve("task"); + final ArrayList<ThreadCpuUsage> threadCpuUsages = new ArrayList<>(); + try (DirectoryStream<Path> threadPaths = Files.newDirectoryStream(allThreadsPath)) { + for (Path threadDirectory : threadPaths) { + ThreadCpuUsage threadCpuUsage = getThreadCpuUsage(threadDirectory); + if (threadCpuUsage != null) { + threadCpuUsages.add(threadCpuUsage); + } + } + } catch (IOException e) { + Slog.w(TAG, "Failed to iterate over thread paths", e); + return null; + } + + // If we found no threads, then the process has exited while we were reading from it + if (threadCpuUsages.isEmpty()) { + return null; + } + + if (DEBUG) { + Slog.d(TAG, "Read CPU usage of " + threadCpuUsages.size() + " threads"); + } + return new ProcessCpuUsage( + processId, + getProcessName(processPath), + uid, + threadCpuUsages); + } + + /** + * Get the CPU frequencies that correspond to the times reported in + * {@link ThreadCpuUsage#usageTimesMillis} + */ + @Nullable + public int[] getCpuFrequenciesKhz() { + return mFrequenciesKhz; + } + + /** + * Get a thread's CPU usage + * + * @param threadDirectory the {@code /proc} directory of the thread + * @return null in the case that the directory read failed + */ + @Nullable + private ThreadCpuUsage getThreadCpuUsage(Path threadDirectory) { + // Get the thread ID from the directory name + final int threadId; + try { + final String directoryName = threadDirectory.getFileName().toString(); + threadId = Integer.parseInt(directoryName); + } catch (NumberFormatException e) { + Slog.w(TAG, "Failed to parse thread ID when iterating over /proc/*/task", e); + return null; + } + + // Get the thread name from the thread directory + final String threadName = getThreadName(threadDirectory); + + // Get the CPU statistics from the directory + final Path threadCpuStatPath = threadDirectory.resolve(CPU_STATISTICS_FILENAME); + final long[] cpuUsagesLong = mProcTimeInStateReader.getUsageTimesMillis(threadCpuStatPath); + if (cpuUsagesLong == null) { + return null; + } + + // Convert long[] to int[] + final int[] cpuUsages = new int[cpuUsagesLong.length]; + for (int i = 0; i < cpuUsagesLong.length; i++) { + cpuUsages[i] = (int) cpuUsagesLong[i]; + } + + return new ThreadCpuUsage(threadId, threadName, cpuUsages); + } + + /** + * Get the command used to start a process + */ + private String getProcessName(Path processPath) { + final Path processNamePath = processPath.resolve(PROCESS_NAME_FILENAME); + + final String processName = + ProcStatsUtil.readSingleLineProcFile(processNamePath.toString()); + if (processName != null) { + return processName; + } + return DEFAULT_PROCESS_NAME; + } + + /** + * Get the name of a thread, given the {@code /proc} path of the thread + */ + private String getThreadName(Path threadPath) { + final Path threadNamePath = threadPath.resolve(THREAD_NAME_FILENAME); + final String threadName = + ProcStatsUtil.readNullSeparatedFile(threadNamePath.toString()); + if (threadName == null) { + return DEFAULT_THREAD_NAME; + } + return threadName; + } + + /** + * CPU usage of a process + */ + public static class ProcessCpuUsage { + public final int processId; + public final String processName; + public final int uid; + public final ArrayList<ThreadCpuUsage> threadCpuUsages; + + ProcessCpuUsage( + int processId, + String processName, + int uid, + ArrayList<ThreadCpuUsage> threadCpuUsages) { + this.processId = processId; + this.processName = processName; + this.uid = uid; + this.threadCpuUsages = threadCpuUsages; + } + } + + /** + * CPU usage of a thread + */ + public static class ThreadCpuUsage { + public final int threadId; + public final String threadName; + public final int[] usageTimesMillis; + + ThreadCpuUsage( + int threadId, + String threadName, + int[] usageTimesMillis) { + this.threadId = threadId; + this.threadName = threadName; + this.usageTimesMillis = usageTimesMillis; + } + } +} diff --git a/core/java/com/android/internal/os/ProcStatsUtil.java b/core/java/com/android/internal/os/ProcStatsUtil.java new file mode 100644 index 000000000000..3d4df890852e --- /dev/null +++ b/core/java/com/android/internal/os/ProcStatsUtil.java @@ -0,0 +1,150 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.os; + +import android.annotation.Nullable; +import android.os.StrictMode; +import android.util.Slog; + +import com.android.internal.annotations.VisibleForTesting; + +import java.io.ByteArrayOutputStream; +import java.io.FileInputStream; +import java.io.IOException; + +/** + * Utility functions for reading {@code proc} files + */ +@VisibleForTesting(visibility = VisibleForTesting.Visibility.PROTECTED) +public final class ProcStatsUtil { + + private static final String TAG = "ProcStatsUtil"; + + /** + * How much to read into a buffer when reading a proc file + */ + private static final int READ_SIZE = 1024; + + /** + * Class only contains static utility functions, and should not be instantiated + */ + private ProcStatsUtil() { + } + + /** + * Read a {@code proc} file where the contents are separated by null bytes. Replaces the null + * bytes with spaces, and removes any trailing null bytes + * + * @param path path of the file to read + */ + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PROTECTED) + @Nullable + public static String readNullSeparatedFile(String path) { + String contents = readSingleLineProcFile(path); + if (contents == null) { + return null; + } + + // Content is either double-null terminated, or terminates at end of line. Remove anything + // after the double-null + final int endIndex = contents.indexOf("\0\0"); + if (endIndex != -1) { + contents = contents.substring(0, endIndex); + } + + // Change the null-separated contents into space-seperated + return contents.replace("\0", " "); + } + + /** + * Read a {@code proc} file that contains a single line (e.g. {@code /proc/$PID/cmdline}, {@code + * /proc/$PID/comm}) + * + * @param path path of the file to read + */ + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PROTECTED) + @Nullable + public static String readSingleLineProcFile(String path) { + return readTerminatedProcFile(path, (byte) '\n'); + } + + /** + * Read a {@code proc} file that terminates with a specific byte + * + * @param path path of the file to read + * @param terminator byte that terminates the file. We stop reading once this character is + * seen, or at the end of the file + */ + @Nullable + public static String readTerminatedProcFile(String path, byte terminator) { + // Permit disk reads here, as /proc isn't really "on disk" and should be fast. + // TODO: make BlockGuard ignore /proc/ and /sys/ files perhaps? + final StrictMode.ThreadPolicy savedPolicy = StrictMode.allowThreadDiskReads(); + try (FileInputStream is = new FileInputStream(path)) { + ByteArrayOutputStream byteStream = null; + final byte[] buffer = new byte[READ_SIZE]; + while (true) { + // Read file into buffer + final int len = is.read(buffer); + if (len <= 0) { + // If we've read nothing, we're done + break; + } + + // Find the terminating character + int terminatingIndex = -1; + for (int i = 0; i < len; i++) { + if (buffer[i] == terminator) { + terminatingIndex = i; + break; + } + } + final boolean foundTerminator = terminatingIndex != -1; + + // If we have found it and the byte stream isn't initialized, we don't need to + // initialize it and can return the string here + if (foundTerminator && byteStream == null) { + return new String(buffer, 0, terminatingIndex); + } + + // Initialize the byte stream + if (byteStream == null) { + byteStream = new ByteArrayOutputStream(READ_SIZE); + } + + // Write the whole buffer if terminator not found, or up to the terminator if found + byteStream.write(buffer, 0, foundTerminator ? terminatingIndex : len); + + // If we've found the terminator, we can finish + if (foundTerminator) { + break; + } + } + + // If the byte stream is null at the end, this means that we have read an empty file + if (byteStream == null) { + return ""; + } + return byteStream.toString(); + } catch (IOException e) { + Slog.w(TAG, "Failed to open proc file", e); + return null; + } finally { + StrictMode.setThreadPolicy(savedPolicy); + } + } +} diff --git a/core/java/com/android/internal/os/ProcTimeInStateReader.java b/core/java/com/android/internal/os/ProcTimeInStateReader.java new file mode 100644 index 000000000000..3a634984a4ec --- /dev/null +++ b/core/java/com/android/internal/os/ProcTimeInStateReader.java @@ -0,0 +1,186 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.os; + +import android.annotation.Nullable; +import android.os.Process; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; + +/** + * Reads and parses {@code time_in_state} files in the {@code proc} filesystem. + * + * Every line in a {@code time_in_state} file contains two numbers, separated by a single space + * character. The first number is the frequency of the CPU used in kilohertz. The second number is + * the time spent in this frequency. In the {@code time_in_state} file, this is given in 10s of + * milliseconds, but this class returns in milliseconds. This can be per user, process, or thread + * depending on which {@code time_in_state} file is used. + * + * For example, a {@code time_in_state} file would look like this: + * <pre> + * 300000 3 + * 364800 0 + * ... + * 1824000 0 + * 1900800 1 + * </pre> + * + * This file would indicate that the CPU has spent 30 milliseconds at frequency 300,000KHz (300Mhz) + * and 10 milliseconds at frequency 1,900,800KHz (1.9GHz). + */ +public class ProcTimeInStateReader { + private static final String TAG = "ProcTimeInStateReader"; + + /** + * The format of a single line of the {@code time_in_state} file that exports the frequency + * values + */ + private static final int[] TIME_IN_STATE_LINE_FREQUENCY_FORMAT = { + Process.PROC_OUT_LONG | Process.PROC_SPACE_TERM, + Process.PROC_NEWLINE_TERM, + }; + + /** + * The format of a single line of the {@code time_in_state} file that exports the time values + */ + private static final int[] TIME_IN_STATE_LINE_TIME_FORMAT = { + Process.PROC_SPACE_TERM, + Process.PROC_OUT_LONG | Process.PROC_NEWLINE_TERM, + }; + + /** + * The format of the {@code time_in_state} file, defined using {@link Process}'s {@code + * PROC_OUT_LONG} and related variables + * + * Defined on first successful read of {@code time_in_state} file. + */ + private int[] mTimeInStateTimeFormat; + + /** + * The frequencies reported in each {@code time_in_state} file + * + * Defined on first successful read of {@code time_in_state} file. + */ + private long[] mFrequenciesKhz; + + /** + * @param initialTimeInStateFile the file to base the format of the frequency files on, and to + * read frequencies from. Expected to be in the same format as all other {@code time_in_state} + * files, and contain the same frequencies. + * @throws IOException if reading the initial {@code time_in_state} file failed + */ + public ProcTimeInStateReader(Path initialTimeInStateFile) throws IOException { + initializeTimeInStateFormat(initialTimeInStateFile); + } + + /** + * Read the CPU usages from a file + * + * @param timeInStatePath path where the CPU usages are read from + * @return list of CPU usage times from the file. These correspond to the CPU frequencies given + * by {@link ProcTimeInStateReader#getFrequenciesKhz} + */ + @Nullable + public long[] getUsageTimesMillis(final Path timeInStatePath) { + // Read in the time_in_state file + final long[] readLongs = new long[mFrequenciesKhz.length]; + final boolean readSuccess = Process.readProcFile( + timeInStatePath.toString(), + mTimeInStateTimeFormat, + null, readLongs, null); + if (!readSuccess) { + return null; + } + // Usage time is given in 10ms, so convert to ms + for (int i = 0; i < readLongs.length; i++) { + readLongs[i] *= 10; + } + return readLongs; + } + + /** + * Get the frequencies found in each {@code time_in_state} file + * + * @return list of CPU frequencies. These correspond to the CPU times given by {@link + * ProcTimeInStateReader#getUsageTimesMillis(Path)}()}. + */ + @Nullable + public long[] getFrequenciesKhz() { + return mFrequenciesKhz; + } + + /** + * Set the {@link #mTimeInStateTimeFormat} and {@link #mFrequenciesKhz} variables based on the + * an input file. If the file is empty, these variables aren't set + * + * This needs to be run once on the first invocation of {@link #getUsageTimesMillis(Path)}. This + * is because we need to know how many frequencies are available in order to parse time + * {@code time_in_state} file using {@link Process#readProcFile}, which only accepts + * fixed-length formats. Also, as the frequencies do not change between {@code time_in_state} + * files, we read and store them here. + * + * @param timeInStatePath the input file to base the format off of + */ + private void initializeTimeInStateFormat(final Path timeInStatePath) throws IOException { + // Read the bytes of the `time_in_state` file + byte[] timeInStateBytes = Files.readAllBytes(timeInStatePath); + + // The number of lines in the `time_in_state` file is the number of frequencies available + int numFrequencies = 0; + for (int i = 0; i < timeInStateBytes.length; i++) { + if (timeInStateBytes[i] == '\n') { + numFrequencies++; + } + } + if (numFrequencies == 0) { + throw new IOException("Empty time_in_state file"); + } + + // Set `mTimeInStateTimeFormat` and `timeInStateFrequencyFormat` to the correct length, and + // then copy in the `TIME_IN_STATE_{FREQUENCY,TIME}_LINE_FORMAT` until it's full. As we only + // use the frequency format in this method, it is not an member variable. + final int[] timeInStateTimeFormat = + new int[numFrequencies * TIME_IN_STATE_LINE_TIME_FORMAT.length]; + final int[] timeInStateFrequencyFormat = + new int[numFrequencies * TIME_IN_STATE_LINE_FREQUENCY_FORMAT.length]; + for (int i = 0; i < numFrequencies; i++) { + System.arraycopy( + TIME_IN_STATE_LINE_FREQUENCY_FORMAT, 0, timeInStateFrequencyFormat, + i * TIME_IN_STATE_LINE_FREQUENCY_FORMAT.length, + TIME_IN_STATE_LINE_FREQUENCY_FORMAT.length); + System.arraycopy( + TIME_IN_STATE_LINE_TIME_FORMAT, 0, timeInStateTimeFormat, + i * TIME_IN_STATE_LINE_TIME_FORMAT.length, + TIME_IN_STATE_LINE_TIME_FORMAT.length); + } + + // Read the frequencies from the `time_in_state` file and store them, as they will be the + // same for every `time_in_state` file + final long[] readLongs = new long[numFrequencies]; + final boolean readSuccess = Process.parseProcLine( + timeInStateBytes, 0, timeInStateBytes.length, timeInStateFrequencyFormat, + null, readLongs, null); + if (!readSuccess) { + throw new IOException("Failed to parse time_in_state file"); + } + + mTimeInStateTimeFormat = timeInStateTimeFormat; + mFrequenciesKhz = readLongs; + } +} diff --git a/core/java/com/android/internal/os/ProcessCpuTracker.java b/core/java/com/android/internal/os/ProcessCpuTracker.java index 1ee4269d974b..4b878c7c4808 100644 --- a/core/java/com/android/internal/os/ProcessCpuTracker.java +++ b/core/java/com/android/internal/os/ProcessCpuTracker.java @@ -28,10 +28,7 @@ import android.util.Slog; import com.android.internal.util.FastPrintWriter; -import libcore.io.IoUtils; - import java.io.File; -import java.io.FileInputStream; import java.io.PrintWriter; import java.io.StringWriter; import java.text.SimpleDateFormat; @@ -40,7 +37,6 @@ import java.util.Collections; import java.util.Comparator; import java.util.Date; import java.util.List; -import java.util.StringTokenizer; public class ProcessCpuTracker { private static final String TAG = "ProcessCpuTracker"; @@ -176,8 +172,6 @@ public class ProcessCpuTracker { private boolean mFirst = true; - private byte[] mBuffer = new byte[4096]; - public interface FilterStats { /** Which stats to pick when filtering */ boolean needed(Stats stats); @@ -863,40 +857,11 @@ public class ProcessCpuTracker { pw.println(); } - private String readFile(String file, char endChar) { - // Permit disk reads here, as /proc/meminfo isn't really "on - // disk" and should be fast. TODO: make BlockGuard ignore - // /proc/ and /sys/ files perhaps? - StrictMode.ThreadPolicy savedPolicy = StrictMode.allowThreadDiskReads(); - FileInputStream is = null; - try { - is = new FileInputStream(file); - int len = is.read(mBuffer); - is.close(); - - if (len > 0) { - int i; - for (i=0; i<len; i++) { - if (mBuffer[i] == endChar) { - break; - } - } - return new String(mBuffer, 0, i); - } - } catch (java.io.FileNotFoundException e) { - } catch (java.io.IOException e) { - } finally { - IoUtils.closeQuietly(is); - StrictMode.setThreadPolicy(savedPolicy); - } - return null; - } - private void getName(Stats st, String cmdlineFile) { String newName = st.name; if (st.name == null || st.name.equals("app_process") || st.name.equals("<pre-initialized>")) { - String cmdName = readFile(cmdlineFile, '\0'); + String cmdName = ProcStatsUtil.readTerminatedProcFile(cmdlineFile, (byte) '\0'); if (cmdName != null && cmdName.length() > 1) { newName = cmdName; int i = newName.lastIndexOf("/"); diff --git a/core/java/com/android/internal/os/Zygote.java b/core/java/com/android/internal/os/Zygote.java index 98b7b5d28779..65213c0a1085 100644 --- a/core/java/com/android/internal/os/Zygote.java +++ b/core/java/com/android/internal/os/Zygote.java @@ -216,6 +216,11 @@ public final class Zygote { */ native protected static void nativeUnmountStorageOnInit(); + private static void callPostForkSystemServerHooks() { + // SystemServer specific post fork hooks run before child post fork hooks. + VM_HOOKS.postForkSystemServer(); + } + private static void callPostForkChildHooks(int runtimeFlags, boolean isSystemServer, boolean isZygote, String instructionSet) { VM_HOOKS.postForkChild(runtimeFlags, isSystemServer, isZygote, instructionSet); diff --git a/core/java/com/android/internal/view/IInputMethodManager.aidl b/core/java/com/android/internal/view/IInputMethodManager.aidl index 29c55c234677..1e71bd171eea 100644 --- a/core/java/com/android/internal/view/IInputMethodManager.aidl +++ b/core/java/com/android/internal/view/IInputMethodManager.aidl @@ -55,7 +55,8 @@ interface IInputMethodManager { // @NonNull InputBindResult startInputOrWindowGainedFocus( /* @StartInputReason */ int startInputReason, - in IInputMethodClient client, in IBinder windowToken, int controlFlags, + in IInputMethodClient client, in IBinder windowToken, + /* @StartInputFlags */ int startInputFlags, /* @android.view.WindowManager.LayoutParams.SoftInputModeFlags */ int softInputMode, int windowFlags, in EditorInfo attribute, IInputContext inputContext, /* @InputConnectionInspector.MissingMethodFlags */ int missingMethodFlags, diff --git a/core/jni/android/opengl/util.cpp b/core/jni/android/opengl/util.cpp index a45b4933a900..d50e60c1a899 100644 --- a/core/jni/android/opengl/util.cpp +++ b/core/jni/android/opengl/util.cpp @@ -657,6 +657,7 @@ static int getPixelFormatFromInternalFormat(uint32_t internalFormat) { switch (internalFormat) { // For sized internal format. case GL_RGBA16F: + case GL_SRGB8_ALPHA8: return GL_RGBA; // Base internal formats and pixel formats are still the same, see Table 1 in // https://www.khronos.org/registry/OpenGL-Refpages/es3.1/html/glTexImage2D.xhtml diff --git a/core/jni/android_util_Binder.cpp b/core/jni/android_util_Binder.cpp index ec980800c410..fd042b39f7c2 100644 --- a/core/jni/android_util_Binder.cpp +++ b/core/jni/android_util_Binder.cpp @@ -865,17 +865,17 @@ void signalExceptionForError(JNIEnv* env, jobject obj, status_t err, // ---------------------------------------------------------------------------- -static jint android_os_Binder_getCallingPid(JNIEnv* env, jobject clazz) +static jint android_os_Binder_getCallingPid() { return IPCThreadState::self()->getCallingPid(); } -static jint android_os_Binder_getCallingUid(JNIEnv* env, jobject clazz) +static jint android_os_Binder_getCallingUid() { return IPCThreadState::self()->getCallingUid(); } -static jlong android_os_Binder_clearCallingIdentity(JNIEnv* env, jobject clazz) +static jlong android_os_Binder_clearCallingIdentity() { return IPCThreadState::self()->clearCallingIdentity(); } @@ -894,12 +894,12 @@ static void android_os_Binder_restoreCallingIdentity(JNIEnv* env, jobject clazz, IPCThreadState::self()->restoreCallingIdentity(token); } -static void android_os_Binder_setThreadStrictModePolicy(JNIEnv* env, jobject clazz, jint policyMask) +static void android_os_Binder_setThreadStrictModePolicy(jint policyMask) { IPCThreadState::self()->setStrictModePolicy(policyMask); } -static jint android_os_Binder_getThreadStrictModePolicy(JNIEnv* env, jobject clazz) +static jint android_os_Binder_getThreadStrictModePolicy() { return IPCThreadState::self()->getStrictModePolicy(); } @@ -950,11 +950,16 @@ static void android_os_Binder_blockUntilThreadAvailable(JNIEnv* env, jobject cla static const JNINativeMethod gBinderMethods[] = { /* name, signature, funcPtr */ + // @CriticalNative { "getCallingPid", "()I", (void*)android_os_Binder_getCallingPid }, + // @CriticalNative { "getCallingUid", "()I", (void*)android_os_Binder_getCallingUid }, + // @CriticalNative { "clearCallingIdentity", "()J", (void*)android_os_Binder_clearCallingIdentity }, { "restoreCallingIdentity", "(J)V", (void*)android_os_Binder_restoreCallingIdentity }, + // @CriticalNative { "setThreadStrictModePolicy", "(I)V", (void*)android_os_Binder_setThreadStrictModePolicy }, + // @CriticalNative { "getThreadStrictModePolicy", "()I", (void*)android_os_Binder_getThreadStrictModePolicy }, // @CriticalNative { "setThreadWorkSource", "(I)I", (void*)android_os_Binder_setThreadWorkSource }, diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp index 3e04bb3a2019..4e20e294350f 100644 --- a/core/jni/com_android_internal_os_Zygote.cpp +++ b/core/jni/com_android_internal_os_Zygote.cpp @@ -84,6 +84,7 @@ static pid_t gSystemServerPid = 0; static const char kIsolatedStorage[] = "persist.sys.isolated_storage"; static const char kZygoteClassName[] = "com/android/internal/os/Zygote"; static jclass gZygoteClass; +static jmethodID gCallPostForkSystemServerHooks; static jmethodID gCallPostForkChildHooks; static bool g_is_security_enforced = true; @@ -886,6 +887,18 @@ static void SpecializeCommon(JNIEnv* env, uid_t uid, gid_t gid, jintArray javaGi // Unset the SIGCHLD handler, but keep ignoring SIGHUP (rationale in SetSignalHandlers). UnsetChldSignalHandler(); + if (is_system_server) { + env->CallStaticVoidMethod(gZygoteClass, gCallPostForkSystemServerHooks); + if (env->ExceptionCheck()) { + fail_fn("Error calling post fork system server hooks."); + } + // TODO(oth): Remove hardcoded label here (b/117874058). + static const char* kSystemServerLabel = "u:r:system_server:s0"; + if (selinux_android_setcon(kSystemServerLabel) != 0) { + fail_fn(CREATE_ERROR("selinux_android_setcon(%s)", kSystemServerLabel)); + } + } + env->CallStaticVoidMethod(gZygoteClass, gCallPostForkChildHooks, runtime_flags, is_system_server, is_child_zygote, instructionSet); if (env->ExceptionCheck()) { @@ -1181,6 +1194,9 @@ static const JNINativeMethod gMethods[] = { int register_com_android_internal_os_Zygote(JNIEnv* env) { gZygoteClass = MakeGlobalRefOrDie(env, FindClassOrDie(env, kZygoteClassName)); + gCallPostForkSystemServerHooks = GetStaticMethodIDOrDie(env, gZygoteClass, + "callPostForkSystemServerHooks", + "()V"); gCallPostForkChildHooks = GetStaticMethodIDOrDie(env, gZygoteClass, "callPostForkChildHooks", "(IZZLjava/lang/String;)V"); diff --git a/core/jni/fd_utils.cpp b/core/jni/fd_utils.cpp index f56f7eca5f00..2465759a9fa5 100644 --- a/core/jni/fd_utils.cpp +++ b/core/jni/fd_utils.cpp @@ -37,6 +37,7 @@ static const char* kPathWhitelist[] = { "/dev/socket/zygote", "/dev/socket/zygote_secondary", "/dev/socket/webview_zygote", + "/dev/socket/heapprofd", "/sys/kernel/debug/tracing/trace_marker", "/system/framework/framework-res.apk", "/dev/urandom", diff --git a/core/proto/android/app/settings_enums.proto b/core/proto/android/app/settings_enums.proto index 2797550a1ab2..1dc435c0c7ce 100644 --- a/core/proto/android/app/settings_enums.proto +++ b/core/proto/android/app/settings_enums.proto @@ -56,5 +56,8 @@ enum PageId { // Settings > Display > Lock screen display > On lock screen LOCK_SCREEN_NOTIFICATION_CONTENT = 1584; + + // ConfirmDeviceCredentials > BiometricPrompt + BIOMETRIC_FRAGMENT = 1585; } diff --git a/core/proto/android/app/launcher/launcher.proto b/core/proto/android/stats/launcher/launcher.proto index 6451eb4b08b3..dbd0e038c40c 100644 --- a/core/proto/android/app/launcher/launcher.proto +++ b/core/proto/android/stats/launcher/launcher.proto @@ -15,7 +15,7 @@ */ syntax = "proto2"; -package android.app.launcher; +package android.stats.launcher; option java_multiple_files = true; enum LauncherAction { diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 176fedb1cc2e..e728eadfc40f 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -2940,6 +2940,12 @@ <permission android:name="android.permission.BIND_COMPANION_DEVICE_MANAGER_SERVICE" android:protectionLevel="signature" /> + <!-- Must be required by the RoleControllerService to ensure that only the system can bind to + it. + @hide --> + <permission android:name="android.permission.BIND_ROLE_CONTROLLER_SERVICE" + android:protectionLevel="signature" /> + <!-- @SystemApi Must be required by the RuntimePermissionPresenterService to ensure that only the system can bind to it. @hide --> @@ -3274,6 +3280,11 @@ <permission android:name="android.permission.OBSERVE_GRANT_REVOKE_PERMISSIONS" android:protectionLevel="signature|privileged" /> + <!-- @SystemApi Allows an application to manage the holders of a role. + @hide --> + <permission android:name="android.permission.MANAGE_ROLE_HOLDERS" + android:protectionLevel="signature|installer" /> + <!-- @SystemApi Allows an application to use SurfaceFlinger's low level features. <p>Not for use by third-party applications. @hide @@ -4188,6 +4199,11 @@ <permission android:name="android.permission.READ_CLIPBOARD_IN_BACKGROUND" android:protectionLevel="signature" /> + <!-- @SystemApi Allows modifying accessibility state. + @hide --> + <permission android:name="android.permission.MANAGE_ACCESSIBILITY" + android:protectionLevel="signature|setup" /> + <application android:process="system" android:persistent="true" android:hasCode="false" diff --git a/core/res/res/values-bs/strings.xml b/core/res/res/values-bs/strings.xml index 425dfe529775..453bedfa9a59 100644 --- a/core/res/res/values-bs/strings.xml +++ b/core/res/res/values-bs/strings.xml @@ -262,7 +262,7 @@ <string name="notification_channel_vpn" msgid="8330103431055860618">"Status VPN-a"</string> <string name="notification_channel_device_admin" msgid="1568154104368069249">"Administracija uređaja"</string> <string name="notification_channel_alerts" msgid="4496839309318519037">"Upozorenja"</string> - <string name="notification_channel_retail_mode" msgid="6088920674914038779">"Promotivna demonstracija u maloprodaji"</string> + <string name="notification_channel_retail_mode" msgid="6088920674914038779">"Prodajna demonstracija"</string> <string name="notification_channel_usb" msgid="9006850475328924681">"USB veza"</string> <string name="notification_channel_heavy_weight_app" msgid="6218742927792852607">"Pokrenuta je aplikacija"</string> <string name="notification_channel_foreground_service" msgid="3931987440602669158">"Aplikacije koje troše bateriju"</string> diff --git a/core/res/res/values-es/strings.xml b/core/res/res/values-es/strings.xml index 3ac65fd49f90..2472e2b1e3ec 100644 --- a/core/res/res/values-es/strings.xml +++ b/core/res/res/values-es/strings.xml @@ -651,14 +651,14 @@ <string name="policydesc_watchLogin_secondaryUser" product="tablet" msgid="4280246270601044505">"Controla el número de contraseñas incorrectas introducidas para desbloquear la pantalla y bloquea el tablet o borra todos los datos del usuario si se introducen demasiadas contraseñas incorrectas."</string> <string name="policydesc_watchLogin_secondaryUser" product="TV" msgid="3484832653564483250">"Controla el número de contraseñas incorrectas introducidas para desbloquear la pantalla y bloquea la TV o borra todos los datos del usuario si se introducen demasiadas contraseñas incorrectas."</string> <string name="policydesc_watchLogin_secondaryUser" product="default" msgid="2185480427217127147">"Controla el número de contraseñas incorrectas introducidas para desbloquear la pantalla y bloquea el teléfono o borra todos los datos del usuario si se introducen demasiadas contraseñas incorrectas."</string> - <string name="policylab_resetPassword" msgid="4934707632423915395">"Cambiar el bloqueo de pantalla"</string> - <string name="policydesc_resetPassword" msgid="1278323891710619128">"Cambiar el bloqueo de pantalla"</string> + <string name="policylab_resetPassword" msgid="4934707632423915395">"Cambia el bloqueo de pantalla"</string> + <string name="policydesc_resetPassword" msgid="1278323891710619128">"Cambia el bloqueo de pantalla"</string> <string name="policylab_forceLock" msgid="2274085384704248431">"Bloquear la pantalla"</string> - <string name="policydesc_forceLock" msgid="1141797588403827138">"Controlar cómo y cuándo se bloquea la pantalla"</string> + <string name="policydesc_forceLock" msgid="1141797588403827138">"Controla cómo y cuándo se bloquea la pantalla"</string> <string name="policylab_wipeData" msgid="3910545446758639713">"Borrar todos los datos"</string> <string name="policydesc_wipeData" product="tablet" msgid="4306184096067756876">"Borrar los datos del tablet sin avisar restableciendo el estado de fábrica"</string> <string name="policydesc_wipeData" product="tv" msgid="5816221315214527028">"Borra los datos de la TV sin advertencia previa restableciendo la TV a los valores predeterminados de fábrica."</string> - <string name="policydesc_wipeData" product="default" msgid="5096895604574188391">"Borrar los datos del teléfono sin avisar restableciendo el estado de fábrica"</string> + <string name="policydesc_wipeData" product="default" msgid="5096895604574188391">"Borra los datos del teléfono sin avisar restableciendo el estado de fábrica"</string> <string name="policylab_wipeData_secondaryUser" msgid="8362863289455531813">"Borrar datos del usuario"</string> <string name="policydesc_wipeData_secondaryUser" product="tablet" msgid="6336255514635308054">"Borra los datos del usuario en este tablet sin avisar."</string> <string name="policydesc_wipeData_secondaryUser" product="tv" msgid="2086473496848351810">"Borra los datos del usuario en esta TV sin avisar."</string> @@ -670,7 +670,7 @@ <string name="policylab_encryptedStorage" msgid="8901326199909132915">"Cifrado del almacenamiento"</string> <string name="policydesc_encryptedStorage" msgid="2637732115325316992">"Exige que se cifren los datos de la aplicación almacenados."</string> <string name="policylab_disableCamera" msgid="6395301023152297826">"Inhabilitar cámaras"</string> - <string name="policydesc_disableCamera" msgid="2306349042834754597">"Evitar el uso de las cámaras del dispositivo"</string> + <string name="policydesc_disableCamera" msgid="2306349042834754597">"Evita el uso de las cámaras del dispositivo"</string> <string name="policylab_disableKeyguardFeatures" msgid="8552277871075367771">"Inhabilitar algunas funciones del bloqueo de pantalla"</string> <string name="policydesc_disableKeyguardFeatures" msgid="2044755691354158439">"Evitar el uso de algunas funciones del bloqueo de pantalla"</string> <string-array name="phoneTypes"> diff --git a/core/tests/coretests/src/android/os/BinderProxyTest.java b/core/tests/coretests/src/android/os/BinderProxyTest.java new file mode 100644 index 000000000000..4c36b5c359a2 --- /dev/null +++ b/core/tests/coretests/src/android/os/BinderProxyTest.java @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.os; + +import android.annotation.Nullable; +import android.content.Context; +import android.test.AndroidTestCase; +import android.test.suitebuilder.annotation.MediumTest; + +public class BinderProxyTest extends AndroidTestCase { + private static class CountingListener implements Binder.ProxyTransactListener { + int mStartedCount; + int mEndedCount; + + public Object onTransactStarted(IBinder binder, int transactionCode) { + mStartedCount++; + return null; + } + + public void onTransactEnded(@Nullable Object session) { + mEndedCount++; + } + }; + + private PowerManager mPowerManager; + + /** + * Setup any common data for the upcoming tests. + */ + @Override + public void setUp() throws Exception { + super.setUp(); + mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE); + } + + @MediumTest + public void testNoListener() throws Exception { + CountingListener listener = new CountingListener(); + Binder.setProxyTransactListener(listener); + Binder.setProxyTransactListener(null); + + mPowerManager.isInteractive(); + + assertEquals(0, listener.mStartedCount); + assertEquals(0, listener.mEndedCount); + } + + @MediumTest + public void testListener() throws Exception { + CountingListener listener = new CountingListener(); + Binder.setProxyTransactListener(listener); + + mPowerManager.isInteractive(); + + assertEquals(1, listener.mStartedCount); + assertEquals(1, listener.mEndedCount); + } + + @MediumTest + public void testSessionPropagated() throws Exception { + Binder.setProxyTransactListener(new Binder.ProxyTransactListener() { + public Object onTransactStarted(IBinder binder, int transactionCode) { + return "foo"; + } + + public void onTransactEnded(@Nullable Object session) { + assertEquals("foo", session); + } + }); + + // Check it does not throw.. + mPowerManager.isInteractive(); + } +} diff --git a/core/tests/coretests/src/android/provider/SettingsBackupTest.java b/core/tests/coretests/src/android/provider/SettingsBackupTest.java index 9778acba8213..182f1892e8cd 100644 --- a/core/tests/coretests/src/android/provider/SettingsBackupTest.java +++ b/core/tests/coretests/src/android/provider/SettingsBackupTest.java @@ -547,6 +547,7 @@ public class SettingsBackupTest { Settings.Secure.BACKUP_PROVISIONED, Settings.Secure.BACKUP_TRANSPORT, Settings.Secure.CALL_REDIRECTION_DEFAULT_APPLICATION, + Settings.Secure.CALL_SCREENING_DEFAULT_COMPONENT, Settings.Secure.CAMERA_LIFT_TRIGGER_ENABLED, // Candidate for backup? Settings.Secure.CARRIER_APPS_HANDLED, Settings.Secure.CMAS_ADDITIONAL_BROADCAST_PKG, diff --git a/core/tests/coretests/src/android/text/format/FormatterTest.java b/core/tests/coretests/src/android/text/format/FormatterTest.java index c02d97ce98a0..82e4bff200fd 100644 --- a/core/tests/coretests/src/android/text/format/FormatterTest.java +++ b/core/tests/coretests/src/android/text/format/FormatterTest.java @@ -162,7 +162,7 @@ public class FormatterTest { // Make sure it works on different locales. setLocale(Locale.FRANCE); - assertEquals("2 j", Formatter.formatShortElapsedTime(mContext, 2 * DAY)); + assertEquals("2\u202fj", Formatter.formatShortElapsedTime(mContext, 2 * DAY)); } @Test diff --git a/core/tests/coretests/src/android/view/textclassifier/TextClassificationTest.java b/core/tests/coretests/src/android/view/textclassifier/TextClassificationTest.java index de863d743442..91a54409608a 100644 --- a/core/tests/coretests/src/android/view/textclassifier/TextClassificationTest.java +++ b/core/tests/coretests/src/android/view/textclassifier/TextClassificationTest.java @@ -29,6 +29,7 @@ import android.graphics.Color; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.graphics.drawable.Icon; +import android.os.Bundle; import android.os.LocaleList; import android.os.Parcel; import android.support.test.InstrumentationRegistry; @@ -48,6 +49,13 @@ import java.util.Locale; @RunWith(AndroidJUnit4.class) public class TextClassificationTest { + private static final String BUNDLE_KEY = "key"; + private static final String BUNDLE_VALUE = "value"; + private static final Bundle BUNDLE = new Bundle(); + static { + BUNDLE.putString(BUNDLE_KEY, BUNDLE_VALUE); + } + public Icon generateTestIcon(int width, int height, int colorValue) { final int numPixels = width * height; final int[] colors = new int[numPixels]; @@ -89,6 +97,7 @@ public class TextClassificationTest { .setEntityType(TextClassifier.TYPE_ADDRESS, 0.3f) .setEntityType(TextClassifier.TYPE_PHONE, 0.7f) .setId(id) + .setExtras(BUNDLE) .build(); // Parcel and unparcel @@ -119,6 +128,9 @@ public class TextClassificationTest { assertEquals(TextClassifier.TYPE_ADDRESS, result.getEntity(1)); assertEquals(0.7f, result.getConfidenceScore(TextClassifier.TYPE_PHONE), 1e-7f); assertEquals(0.3f, result.getConfidenceScore(TextClassifier.TYPE_ADDRESS), 1e-7f); + + // Extras + assertEquals(BUNDLE_VALUE, result.getExtras().getString(BUNDLE_KEY)); } @Test @@ -182,6 +194,7 @@ public class TextClassificationTest { new TextClassification.Request.Builder(text, 0, text.length()) .setDefaultLocales(new LocaleList(Locale.US, Locale.GERMANY)) .setReferenceTime(referenceTime) + .setExtras(BUNDLE) .build(); // Parcel and unparcel. @@ -197,5 +210,6 @@ public class TextClassificationTest { assertEquals(referenceTime, result.getReferenceTime()); assertEquals("en-US,de-DE", result.getDefaultLocales().toLanguageTags()); assertEquals(referenceTime, result.getReferenceTime()); + assertEquals(BUNDLE_VALUE, result.getExtras().getString(BUNDLE_KEY)); } } diff --git a/core/tests/coretests/src/com/android/internal/inputmethod/InputMethodDebugTest.java b/core/tests/coretests/src/com/android/internal/inputmethod/InputMethodDebugTest.java new file mode 100644 index 000000000000..629f7b60471c --- /dev/null +++ b/core/tests/coretests/src/com/android/internal/inputmethod/InputMethodDebugTest.java @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.inputmethod; + +import static org.junit.Assert.assertEquals; + +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; +import android.view.WindowManager.LayoutParams; + +import org.junit.Test; +import org.junit.runner.RunWith; + +@SmallTest +@RunWith(AndroidJUnit4.class) +public class InputMethodDebugTest { + @Test + public void testStartInputReasonToString() { + // TODO: Use reflection to make sure that all the constants defined in StartInputReason are + // covered. + assertEquals("UNSPECIFIED", + InputMethodDebug.startInputReasonToString(StartInputReason.UNSPECIFIED)); + } + + @Test + public void testUnbindReasonToString() { + // TODO: Use reflection to make sure that all the constants defined in UnbindReason are + // covered. + assertEquals("UNSPECIFIED", + InputMethodDebug.startInputReasonToString(UnbindReason.UNSPECIFIED)); + } + + @Test + public void testSoftInputModeToString() { + // TODO: add more tests + assertEquals("STATE_UNCHANGED|ADJUST_RESIZE|IS_FORWARD_NAVIGATION", + InputMethodDebug.softInputModeToString( + LayoutParams.SOFT_INPUT_STATE_UNCHANGED + | LayoutParams.SOFT_INPUT_ADJUST_RESIZE + | LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION)); + } + + @Test + public void testStartInputFlagsToString() { + // TODO: add more tests + assertEquals("(none)", InputMethodDebug.startInputFlagsToString(0)); + assertEquals("IS_TEXT_EDITOR", + InputMethodDebug.startInputFlagsToString(StartInputFlags.IS_TEXT_EDITOR)); + assertEquals("VIEW_HAS_FOCUS|INITIAL_CONNECTION", + InputMethodDebug.startInputFlagsToString( + StartInputFlags.VIEW_HAS_FOCUS | StartInputFlags.INITIAL_CONNECTION)); + } +} diff --git a/core/tests/coretests/src/com/android/internal/os/KernelCpuThreadReaderTest.java b/core/tests/coretests/src/com/android/internal/os/KernelCpuThreadReaderTest.java new file mode 100644 index 000000000000..b9ef4349e414 --- /dev/null +++ b/core/tests/coretests/src/com/android/internal/os/KernelCpuThreadReaderTest.java @@ -0,0 +1,142 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.os; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import android.content.Context; +import android.os.FileUtils; +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 java.io.File; +import java.io.IOException; +import java.io.OutputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Comparator; + +@SmallTest +@RunWith(AndroidJUnit4.class) +public class KernelCpuThreadReaderTest { + + private static final String PROCESS_NAME = "test_process"; + private static final int[] THREAD_IDS = {0, 1000, 1235, 4321}; + private static final String[] THREAD_NAMES = { + "test_thread_1", "test_thread_2", "test_thread_3", "test_thread_4" + }; + private static final int[] THREAD_CPU_FREQUENCIES = { + 1000, 2000, 3000, 4000, + }; + private static final int[][] THREAD_CPU_TIMES = { + {1, 0, 0, 1}, + {0, 0, 0, 0}, + {1000, 1000, 1000, 1000}, + {0, 1, 2, 3}, + }; + + private File mProcDirectory; + + @Before + public void setUp() { + Context context = InstrumentationRegistry.getContext(); + mProcDirectory = context.getDir("proc", Context.MODE_PRIVATE); + } + + @After + public void tearDown() throws Exception { + FileUtils.deleteContents(mProcDirectory); + } + + @Test + public void testSimple() throws IOException { + // Make /proc/self + final Path selfPath = mProcDirectory.toPath().resolve("self"); + assertTrue(selfPath.toFile().mkdirs()); + + // Make /proc/self/task + final Path selfThreadsPath = selfPath.resolve("task"); + assertTrue(selfThreadsPath.toFile().mkdirs()); + + // Make /proc/self/cmdline + Files.write(selfPath.resolve("cmdline"), PROCESS_NAME.getBytes()); + + // Make thread directories in reverse order, as they are read in order of creation by + // CpuThreadProcReader + for (int i = 0; i < THREAD_IDS.length; i++) { + // Make /proc/self/task/$TID + final Path threadPath = selfThreadsPath.resolve(String.valueOf(THREAD_IDS[i])); + assertTrue(threadPath.toFile().mkdirs()); + + // Make /proc/self/task/$TID/comm + Files.write(threadPath.resolve("comm"), THREAD_NAMES[i].getBytes()); + + // Make /proc/self/task/$TID/time_in_state + final OutputStream timeInStateStream = + Files.newOutputStream(threadPath.resolve("time_in_state")); + for (int j = 0; j < THREAD_CPU_FREQUENCIES.length; j++) { + final String line = String.valueOf(THREAD_CPU_FREQUENCIES[j]) + " " + + String.valueOf(THREAD_CPU_TIMES[i][j]) + "\n"; + timeInStateStream.write(line.getBytes()); + } + timeInStateStream.close(); + } + + final KernelCpuThreadReader kernelCpuThreadReader = new KernelCpuThreadReader( + mProcDirectory.toPath(), + mProcDirectory.toPath().resolve("self/task/" + THREAD_IDS[0] + "/time_in_state")); + final KernelCpuThreadReader.ProcessCpuUsage processCpuUsage = + kernelCpuThreadReader.getCurrentProcessCpuUsage(); + + assertNotNull(processCpuUsage); + assertEquals(android.os.Process.myPid(), processCpuUsage.processId); + assertEquals(android.os.Process.myUid(), processCpuUsage.uid); + assertEquals(PROCESS_NAME, processCpuUsage.processName); + + // Sort the thread CPU usages to compare with test case + final ArrayList<KernelCpuThreadReader.ThreadCpuUsage> threadCpuUsages = + new ArrayList<>(processCpuUsage.threadCpuUsages); + threadCpuUsages.sort(Comparator.comparingInt(a -> a.threadId)); + + int threadCount = 0; + for (KernelCpuThreadReader.ThreadCpuUsage threadCpuUsage : threadCpuUsages) { + assertEquals(THREAD_IDS[threadCount], threadCpuUsage.threadId); + assertEquals(THREAD_NAMES[threadCount], threadCpuUsage.threadName); + + for (int i = 0; i < threadCpuUsage.usageTimesMillis.length; i++) { + assertEquals( + THREAD_CPU_TIMES[threadCount][i] * 10, + threadCpuUsage.usageTimesMillis[i]); + assertEquals( + THREAD_CPU_FREQUENCIES[i], + kernelCpuThreadReader.getCpuFrequenciesKhz()[i]); + } + threadCount++; + } + + assertEquals(threadCount, THREAD_IDS.length); + } +} diff --git a/core/tests/coretests/src/com/android/internal/os/ProcStatsUtilTest.java b/core/tests/coretests/src/com/android/internal/os/ProcStatsUtilTest.java new file mode 100644 index 000000000000..489e164e0b12 --- /dev/null +++ b/core/tests/coretests/src/com/android/internal/os/ProcStatsUtilTest.java @@ -0,0 +1,192 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.os; + +import static org.junit.Assert.assertEquals; + +import android.content.Context; +import android.os.FileUtils; +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 java.io.File; +import java.io.IOException; +import java.nio.file.Files; + +@SmallTest +@RunWith(AndroidJUnit4.class) +public class ProcStatsUtilTest { + + private File mProcDirectory; + + @Before + public void setUp() { + Context context = InstrumentationRegistry.getContext(); + mProcDirectory = context.getDir("proc", Context.MODE_PRIVATE); + } + + @After + public void tearDown() throws Exception { + FileUtils.deleteContents(mProcDirectory); + } + + @Test + public void testReadNullSeparatedFile_empty() throws IOException { + assertEquals( + "", + runReadNullSeparatedFile("")); + } + + @Test + public void testReadNullSeparatedFile_simple() throws IOException { + assertEquals( + "abc def ghi", + runReadNullSeparatedFile("abc\0def\0ghi")); + } + + @Test + public void testReadNullSeparatedFile_trailingNulls() throws IOException { + assertEquals( + "abc", + runReadNullSeparatedFile("abc\0\0\0\0")); + } + + @Test + public void testReadNullSeparatedFile_doubleNullEnds() throws IOException { + assertEquals( + "abc", + runReadNullSeparatedFile("abc\0\0def")); + } + + @Test + public void testReadSingleLineProcFile_simple() throws IOException { + assertEquals( + "abc", + runReadSingleLineProcFile("abc")); + } + + @Test + public void testReadSingleLineProcFile_empty() throws IOException { + assertEquals( + "", + runReadSingleLineProcFile("")); + } + + @Test + public void testReadSingleLineProcFile_newLine() throws IOException { + assertEquals( + "abc", + runReadSingleLineProcFile("abc\ndef")); + } + + @Test + public void testReadSingleLineProcFile_doubleNewLine() throws IOException { + assertEquals( + "abc", + runReadSingleLineProcFile("abc\n\ndef")); + } + + @Test + public void testReadTerminatedProcFile_simple() throws IOException { + assertEquals( + "abc", + runReadTerminatedProcFile("abc\0", (byte) '\0')); + } + + @Test + public void testReadTerminatedProcFile_withExtra() throws IOException { + assertEquals( + "123", + runReadTerminatedProcFile("123\0extra", (byte) '\0')); + } + + @Test + public void testReadTerminatedProcFile_noTerminator() throws IOException { + assertEquals( + "noterm", + runReadTerminatedProcFile("noterm", (byte) '\0')); + } + + @Test + public void testReadTerminatedProcFile_newLineTerm() throws IOException { + assertEquals( + "123", + runReadTerminatedProcFile("123\n456", (byte) '\n')); + } + + @Test + public void testReadTerminatedProcFile_normalCharTerm() throws IOException { + assertEquals( + "abc", + runReadTerminatedProcFile("abcdef", (byte) 'd')); + } + + @Test + public void testReadTerminatedProcFile_largeUnterminated() throws IOException { + String longString = new String(new char[10000]).replace('\0', 'a'); + assertEquals( + longString, + runReadTerminatedProcFile(longString, (byte) '\0')); + } + + @Test + public void testReadTerminatedProcFile_largeTerminated() throws IOException { + String longString = new String(new char[10000]).replace('\0', 'a'); + assertEquals( + longString, + runReadTerminatedProcFile(longString + "\0", (byte) '\0')); + } + + @Test + public void testReadTerminatedProcFile_largeExtra() throws IOException { + String longString = new String(new char[10000]).replace('\0', 'a'); + assertEquals( + longString, + runReadTerminatedProcFile(longString + "\0abc", (byte) '\0')); + } + + private String runReadNullSeparatedFile(String fileContents) throws IOException { + File tempFile = File.createTempFile("null-separated-file", null, mProcDirectory); + Files.write(tempFile.toPath(), fileContents.getBytes()); + String result = ProcStatsUtil.readNullSeparatedFile(tempFile.toString()); + Files.delete(tempFile.toPath()); + return result; + } + + private String runReadSingleLineProcFile(String fileContents) throws IOException { + File tempFile = File.createTempFile("single-line-proc-file", null, mProcDirectory); + Files.write(tempFile.toPath(), fileContents.getBytes()); + String result = ProcStatsUtil.readSingleLineProcFile(tempFile.toString()); + Files.delete(tempFile.toPath()); + return result; + } + + private String runReadTerminatedProcFile( + String fileContents, byte terminator) throws IOException { + File tempFile = File.createTempFile("terminated-proc-file", null, mProcDirectory); + Files.write(tempFile.toPath(), fileContents.getBytes()); + String result = ProcStatsUtil.readTerminatedProcFile(tempFile.toString(), terminator); + Files.delete(tempFile.toPath()); + return result; + } +} diff --git a/core/tests/coretests/src/com/android/internal/os/ProcTimeInStateReaderTest.java b/core/tests/coretests/src/com/android/internal/os/ProcTimeInStateReaderTest.java new file mode 100644 index 000000000000..f2a531fb938a --- /dev/null +++ b/core/tests/coretests/src/com/android/internal/os/ProcTimeInStateReaderTest.java @@ -0,0 +1,107 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.os; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.fail; + +import android.content.Context; +import android.os.FileUtils; +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 java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; + +@SmallTest +@RunWith(AndroidJUnit4.class) +public class ProcTimeInStateReaderTest { + + private File mProcDirectory; + + @Before + public void setUp() { + Context context = InstrumentationRegistry.getContext(); + mProcDirectory = context.getDir("proc", Context.MODE_PRIVATE); + } + + @After + public void tearDown() throws Exception { + FileUtils.deleteContents(mProcDirectory); + } + + @Test + public void testSimple() throws IOException { + Path initialTimeInStateFile = mProcDirectory.toPath().resolve("initial-time-in-state"); + Files.write(initialTimeInStateFile, "1 2\n3 4\n5 6\n7 8\n".getBytes()); + ProcTimeInStateReader reader = new ProcTimeInStateReader(initialTimeInStateFile); + + assertArrayEquals( + "Reported frequencies are correct", + new long[]{1, 3, 5, 7}, + reader.getFrequenciesKhz()); + assertArrayEquals( + "Reported usage times are correct", + new long[]{20, 40, 60, 80}, + reader.getUsageTimesMillis(initialTimeInStateFile)); + } + + @Test + public void testDifferentFile() throws IOException { + Path initialTimeInStateFile = mProcDirectory.toPath().resolve("initial-time-in-state"); + Files.write(initialTimeInStateFile, "1 2\n3 4\n5 6\n7 8\n".getBytes()); + ProcTimeInStateReader reader = new ProcTimeInStateReader(initialTimeInStateFile); + + Path timeInStateFile = mProcDirectory.toPath().resolve("time-in-state"); + Files.write(timeInStateFile, "1 20\n3 40\n5 60\n7 80\n".getBytes()); + assertArrayEquals( + "Reported usage times are correct", + new long[]{200, 400, 600, 800}, + reader.getUsageTimesMillis(timeInStateFile)); + } + + @Test + public void testWrongLength() throws IOException { + Path initialTimeInStateFile = mProcDirectory.toPath().resolve("initial-time-in-state"); + Files.write(initialTimeInStateFile, "1 2\n3 4\n5 6\n7 8\n".getBytes()); + ProcTimeInStateReader reader = new ProcTimeInStateReader(initialTimeInStateFile); + + Path timeInStateFile = mProcDirectory.toPath().resolve("time-in-state"); + Files.write(timeInStateFile, "1 2\n3 4\n5 6\n".getBytes()); + assertNull(reader.getUsageTimesMillis(timeInStateFile)); + } + + @Test + public void testEmptyInitialFails() throws IOException { + Path initialTimeInStateFile = mProcDirectory.toPath().resolve("initial-time-in-state"); + Files.write(initialTimeInStateFile, "".getBytes()); + try { + new ProcTimeInStateReader(initialTimeInStateFile); + fail("Instantiation didn't fail with empty initial time_in_state file"); + } catch (IOException ignored) { + } + } +} diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml index 28e92dbe264a..8964ee3046f5 100644 --- a/data/etc/privapp-permissions-platform.xml +++ b/data/etc/privapp-permissions-platform.xml @@ -313,6 +313,7 @@ applications that come with the platform <permission name="android.permission.INSTALL_PACKAGES"/> <permission name="android.permission.INTERACT_ACROSS_USERS"/> <permission name="android.permission.LOCAL_MAC_ADDRESS"/> + <permission name="android.permission.MANAGE_ACCESSIBILITY"/> <permission name="android.permission.MANAGE_ACTIVITY_STACKS"/> <permission name="android.permission.MANAGE_DEVICE_ADMINS"/> <permission name="android.permission.MANAGE_USB"/> diff --git a/graphics/java/android/graphics/text/MeasuredText.java b/graphics/java/android/graphics/text/MeasuredText.java index 3efe655b2565..6f786517ba12 100644 --- a/graphics/java/android/graphics/text/MeasuredText.java +++ b/graphics/java/android/graphics/text/MeasuredText.java @@ -48,9 +48,6 @@ import libcore.util.NativeAllocationRegistry; * </p> */ public class MeasuredText { - private static final NativeAllocationRegistry sRegistry = new NativeAllocationRegistry( - MeasuredText.class.getClassLoader(), nGetReleaseFunc(), 1024); - private long mNativePtr; private @NonNull char[] mChars; @@ -166,6 +163,9 @@ public class MeasuredText { * Note: The appendStyle and appendReplacementRun should be called to cover the text length. */ public static class Builder { + private static final NativeAllocationRegistry sRegistry = new NativeAllocationRegistry( + MeasuredText.class.getClassLoader(), nGetReleaseFunc(), 1024); + private long mNativePtr; private final @NonNull char[] mText; diff --git a/libs/incident/Android.bp b/libs/incident/Android.bp new file mode 100644 index 000000000000..0619a9c100dd --- /dev/null +++ b/libs/incident/Android.bp @@ -0,0 +1,50 @@ +// Copyright (C) 2016 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +cc_library_shared { + name: "libincident", + + cflags: [ + "-Wall", + "-Werror", + "-Wno-missing-field-initializers", + "-Wno-unused-variable", + "-Wunused-parameter", + ], + + shared_libs: [ + "libbinder", + "liblog", + "libutils", + ], + + aidl: { + include_dirs: ["frameworks/base/core/java"], + export_aidl_headers: true, + }, + + srcs: [ + ":libincident_aidl", + "proto/android/os/header.proto", + "proto/android/os/metadata.proto", + "src/IncidentReportArgs.cpp", + ], + + proto: { + type: "lite", + export_proto_headers: true, + }, + + export_include_dirs: ["include"], +} diff --git a/libs/incident/Android.mk b/libs/incident/Android.mk deleted file mode 100644 index 08c834699f40..000000000000 --- a/libs/incident/Android.mk +++ /dev/null @@ -1,43 +0,0 @@ -# Copyright (C) 2016 The Android Open Source Project -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -LOCAL_PATH:= $(call my-dir) -include $(CLEAR_VARS) - -LOCAL_MODULE := libincident - -LOCAL_CFLAGS := \ - -Wall -Werror -Wno-missing-field-initializers -Wno-unused-variable -Wunused-parameter - -LOCAL_SHARED_LIBRARIES := \ - libbinder \ - liblog \ - libutils - -LOCAL_AIDL_INCLUDES := $(LOCAL_PATH)/../../core/java -LOCAL_C_INCLUDES := \ - $(LOCAL_PATH)/include - -LOCAL_SRC_FILES := \ - ../../core/java/android/os/IIncidentManager.aidl \ - ../../core/java/android/os/IIncidentReportStatusListener.aidl \ - proto/android/os/header.proto \ - proto/android/os/metadata.proto \ - src/IncidentReportArgs.cpp - -LOCAL_EXPORT_C_INCLUDE_DIRS := $(LOCAL_PATH)/include -LOCAL_PROTO_OPTIMIZE_TYPE := lite - -include $(BUILD_SHARED_LIBRARY) - diff --git a/media/java/android/media/MediaPlayer2.java b/media/java/android/media/MediaPlayer2.java index 54b5ab19b018..b51caa5adb3b 100644 --- a/media/java/android/media/MediaPlayer2.java +++ b/media/java/android/media/MediaPlayer2.java @@ -345,9 +345,10 @@ public abstract class MediaPlayer2 implements AutoCloseable * playback will start at the beginning. If the source had not been * prepared, the player will prepare the source and play. * + * @return a token which can be used to cancel the operation later with {@link #cancel}. */ // This is an asynchronous call. - public abstract void play(); + public abstract Object play(); /** * Prepares the player for playback, asynchronously. @@ -355,31 +356,35 @@ public abstract class MediaPlayer2 implements AutoCloseable * After setting the datasource and the display surface, you need to * call prepare(). * + * @return a token which can be used to cancel the operation later with {@link #cancel}. */ // This is an asynchronous call. - public abstract void prepare(); + public abstract Object prepare(); /** * Pauses playback. Call play() to resume. + * @return a token which can be used to cancel the operation later with {@link #cancel}. */ // This is an asynchronous call. - public abstract void pause(); + public abstract Object pause(); /** * Tries to play next data source if applicable. + * @return a token which can be used to cancel the operation later with {@link #cancel}. */ // This is an asynchronous call. - public abstract void skipToNext(); + public abstract Object skipToNext(); /** * Moves the media to specified time position. * Same as {@link #seekTo(long, int)} with {@code mode = SEEK_PREVIOUS_SYNC}. * * @param msec the offset in milliseconds from the start to seek to + * @return a token which can be used to cancel the operation later with {@link #cancel}. */ // This is an asynchronous call. - public void seekTo(long msec) { - seekTo(msec, SEEK_PREVIOUS_SYNC /* mode */); + public Object seekTo(long msec) { + return seekTo(msec, SEEK_PREVIOUS_SYNC /* mode */); } /** @@ -467,9 +472,10 @@ public abstract class MediaPlayer2 implements AutoCloseable * You must call this method before {@link #prepare()} in order * for the audio attributes to become effective thereafter. * @param attributes a non-null set of audio attributes + * @return a token which can be used to cancel the operation later with {@link #cancel}. */ // This is an asynchronous call. - public abstract void setAudioAttributes(@NonNull AudioAttributes attributes); + public abstract Object setAudioAttributes(@NonNull AudioAttributes attributes); /** * Gets the audio attributes for this MediaPlayer2. @@ -481,32 +487,36 @@ public abstract class MediaPlayer2 implements AutoCloseable * Sets the data source as described by a DataSourceDesc. * * @param dsd the descriptor of data source you want to play + * @return a token which can be used to cancel the operation later with {@link #cancel}. */ // This is an asynchronous call. - public abstract void setDataSource(@NonNull DataSourceDesc dsd); + public abstract Object setDataSource(@NonNull DataSourceDesc dsd); /** * Sets a single data source as described by a DataSourceDesc which will be played * after current data source is finished. * * @param dsd the descriptor of data source you want to play after current one + * @return a token which can be used to cancel the operation later with {@link #cancel}. */ // This is an asynchronous call. - public abstract void setNextDataSource(@NonNull DataSourceDesc dsd); + public abstract Object setNextDataSource(@NonNull DataSourceDesc dsd); /** * Sets a list of data sources to be played sequentially after current data source is done. * * @param dsds the list of data sources you want to play after current one + * @return a token which can be used to cancel the operation later with {@link #cancel}. */ // This is an asynchronous call. - public abstract void setNextDataSources(@NonNull List<DataSourceDesc> dsds); + public abstract Object setNextDataSources(@NonNull List<DataSourceDesc> dsds); /** * Removes all data sources pending to be played. + * @return a token which can be used to cancel the operation later with {@link #cancel}. */ // This is an asynchronous call. - public abstract void clearNextDataSources(); + public abstract Object clearNextDataSources(); /** * Gets the current data source as described by a DataSourceDesc. @@ -518,9 +528,10 @@ public abstract class MediaPlayer2 implements AutoCloseable /** * Configures the player to loop on the current data source. * @param loop true if the current data source is meant to loop. + * @return a token which can be used to cancel the operation later with {@link #cancel}. */ // This is an asynchronous call. - public abstract void loopCurrent(boolean loop); + public abstract Object loopCurrent(boolean loop); /** * Sets the volume of the audio of the media to play, expressed as a linear multiplier @@ -530,9 +541,10 @@ public abstract class MediaPlayer2 implements AutoCloseable * A value of 0.0f indicates muting, a value of 1.0f is the nominal unattenuated and unamplified * gain. See {@link #getMaxPlayerVolume()} for the volume range supported by this player. * @param volume a value between 0.0f and {@link #getMaxPlayerVolume()}. + * @return a token which can be used to cancel the operation later with {@link #cancel}. */ // This is an asynchronous call. - public abstract void setPlayerVolume(float volume); + public abstract Object setPlayerVolume(float volume); /** * Returns the current volume of this player to this player. @@ -573,9 +585,10 @@ public abstract class MediaPlayer2 implements AutoCloseable * * @param label An application specific Object used to help to identify the completeness * of a batch of commands. + * @return a token which can be used to cancel the operation later with {@link #cancel}. */ // This is an asynchronous call. - public void notifyWhenCommandLabelReached(@NonNull Object label) { } + public abstract Object notifyWhenCommandLabelReached(@NonNull Object label); /** * Sets the {@link SurfaceHolder} to use for displaying the video @@ -590,9 +603,10 @@ public abstract class MediaPlayer2 implements AutoCloseable * @param sh the SurfaceHolder to use for video display * @throws IllegalStateException if the internal player engine has not been * initialized or has been released. + * @return a token which can be used to cancel the operation later with {@link #cancel}. * @hide */ - public abstract void setDisplay(SurfaceHolder sh); + public abstract Object setDisplay(SurfaceHolder sh); /** * Sets the {@link Surface} to be used as the sink for the video portion of @@ -612,9 +626,10 @@ public abstract class MediaPlayer2 implements AutoCloseable * the media. * @throws IllegalStateException if the internal player engine has not been * initialized or has been released. + * @return a token which can be used to cancel the operation later with {@link #cancel}. */ // This is an asynchronous call. - public abstract void setSurface(Surface surface); + public abstract Object setSurface(Surface surface); /* Do not change these video scaling mode values below without updating * their counterparts in system/window.h! Please do not forget to update @@ -653,11 +668,21 @@ public abstract class MediaPlayer2 implements AutoCloseable * * @param mode target video scaling mode. Must be one of the supported * video scaling modes; otherwise, IllegalArgumentException will be thrown. + * @return a token which can be used to cancel the operation later with {@link #cancel}. * * @see MediaPlayer2#VIDEO_SCALING_MODE_SCALE_TO_FIT * @hide */ - public void setVideoScalingMode(int mode) { } + public abstract Object setVideoScalingMode(int mode); + + /** + * Cancels a pending command. + * + * @param token the command to be canceled. This is the returned Object when command is issued. + * @return {@code false} if the task could not be cancelled; {@code true} otherwise. + */ + // This is a synchronous call. + public abstract boolean cancelCommand(Object token); /** * Discards all pending commands. @@ -820,14 +845,12 @@ public abstract class MediaPlayer2 implements AutoCloseable * The input is a hint to MediaPlayer2. * * @param params the buffering management params. + * @return a token which can be used to cancel the operation later with {@link #cancel}. * - * @throws IllegalStateException if the internal player engine has not been - * initialized or has been released, or {@code setDataSource} has not been called. - * @throws IllegalArgumentException if params is invalid or not supported. * @hide */ // This is an asynchronous call. - public void setBufferingParams(@NonNull BufferingParams params) { } + public abstract Object setBufferingParams(@NonNull BufferingParams params); /** * Change playback speed of audio by resampling the audio. @@ -904,9 +927,10 @@ public abstract class MediaPlayer2 implements AutoCloseable * the object state. * * @param params the playback params. + * @return a token which can be used to cancel the operation later with {@link #cancel}. */ // This is an asynchronous call. - public abstract void setPlaybackParams(@NonNull PlaybackParams params); + public abstract Object setPlaybackParams(@NonNull PlaybackParams params); /** * Gets the playback params, containing the current playback rate. @@ -920,9 +944,10 @@ public abstract class MediaPlayer2 implements AutoCloseable * Sets A/V sync mode. * * @param params the A/V sync params to apply + * @return a token which can be used to cancel the operation later with {@link #cancel}. */ // This is an asynchronous call. - public abstract void setSyncParams(@NonNull SyncParams params); + public abstract Object setSyncParams(@NonNull SyncParams params); /** * Gets the A/V sync mode. @@ -998,9 +1023,10 @@ public abstract class MediaPlayer2 implements AutoCloseable * If msec is negative, time position zero will be used. * If msec is larger than duration, duration will be used. * @param mode the mode indicating where exactly to seek to. + * @return a token which can be used to cancel the operation later with {@link #cancel}. */ // This is an asynchronous call. - public abstract void seekTo(long msec, @SeekMode int mode); + public abstract Object seekTo(long msec, @SeekMode int mode); /** * Get current playback position as a {@link MediaTimestamp}. @@ -1055,9 +1081,10 @@ public abstract class MediaPlayer2 implements AutoCloseable * However, it is possible to force this player to be part of an already existing audio session * by calling this method. * This method must be called before one of the overloaded <code> setDataSource </code> methods. + * @return a token which can be used to cancel the operation later with {@link #cancel}. */ // This is an asynchronous call. - public abstract void setAudioSessionId(int sessionId); + public abstract Object setAudioSessionId(int sessionId); /** * Returns the audio session ID. @@ -1080,9 +1107,10 @@ public abstract class MediaPlayer2 implements AutoCloseable * <p>This method must be called after one of the overloaded <code> setDataSource </code> * methods. * @param effectId system wide unique id of the effect to attach + * @return a token which can be used to cancel the operation later with {@link #cancel}. */ // This is an asynchronous call. - public abstract void attachAuxEffect(int effectId); + public abstract Object attachAuxEffect(int effectId); /** @@ -1096,9 +1124,10 @@ public abstract class MediaPlayer2 implements AutoCloseable * x == 0 -> level = 0 * 0 < x <= R -> level = 10^(72*(x-R)/20/R) * @param level send level scalar + * @return a token which can be used to cancel the operation later with {@link #cancel}. */ // This is an asynchronous call. - public abstract void setAuxEffectSendLevel(float level); + public abstract Object setAuxEffectSendLevel(float level); /** * Class for MediaPlayer2 to return each audio/video/subtitle track's metadata. @@ -1221,12 +1250,12 @@ public abstract class MediaPlayer2 implements AutoCloseable * @param index the index of the track to be selected. The valid range of the index * is 0..total number of track - 1. The total number of tracks as well as the type of * each individual track can be found by calling {@link #getTrackInfo()} method. - * @throws IllegalStateException if called in an invalid state. + * @return a token which can be used to cancel the operation later with {@link #cancel}. * * @see MediaPlayer2#getTrackInfo */ // This is an asynchronous call. - public abstract void selectTrack(int index); + public abstract Object selectTrack(int index); /** * Deselect a track. @@ -1238,12 +1267,12 @@ public abstract class MediaPlayer2 implements AutoCloseable * @param index the index of the track to be deselected. The valid range of the index * is 0..total number of tracks - 1. The total number of tracks as well as the type of * each individual track can be found by calling {@link #getTrackInfo()} method. - * @throws IllegalStateException if called in an invalid state. + * @return a token which can be used to cancel the operation later with {@link #cancel}. * * @see MediaPlayer2#getTrackInfo */ // This is an asynchronous call. - public abstract void deselectTrack(int index); + public abstract Object deselectTrack(int index); /** * Interface definition for callbacks to be invoked when the player has the corresponding @@ -1614,16 +1643,6 @@ public abstract class MediaPlayer2 implements AutoCloseable */ public static final int CALL_COMPLETED_PREPARE = 6; - /** The player just completed a call {@link #releaseDrm}. - * @see EventCallback#onCallCompleted - */ - public static final int CALL_COMPLETED_RELEASE_DRM = 12; - - /** The player just completed a call {@link #restoreDrmKeys}. - * @see EventCallback#onCallCompleted - */ - public static final int CALL_COMPLETED_RESTORE_DRM_KEYS = 13; - /** The player just completed a call {@link #seekTo}. * @see EventCallback#onCallCompleted */ @@ -1706,6 +1725,12 @@ public abstract class MediaPlayer2 implements AutoCloseable */ public static final int CALL_COMPLETED_SET_VIDEO_SCALING_MODE = 32; + /** The player just completed a call {@link #setDisplay}. + * @see EventCallback#onCallCompleted + * @hide + */ + public static final int CALL_COMPLETED_SET_DISPLAY = 33; + /** * The start of the methods which have separate call complete callback. * @hide @@ -1736,8 +1761,6 @@ public abstract class MediaPlayer2 implements AutoCloseable CALL_COMPLETED_PAUSE, CALL_COMPLETED_PLAY, CALL_COMPLETED_PREPARE, - CALL_COMPLETED_RELEASE_DRM, - CALL_COMPLETED_RESTORE_DRM_KEYS, CALL_COMPLETED_SEEK_TO, CALL_COMPLETED_SELECT_TRACK, CALL_COMPLETED_SET_AUDIO_ATTRIBUTES, @@ -1754,6 +1777,7 @@ public abstract class MediaPlayer2 implements AutoCloseable CALL_COMPLETED_CLEAR_NEXT_DATA_SOURCES, CALL_COMPLETED_SET_BUFFERING_PARAMS, CALL_COMPLETED_SET_VIDEO_SCALING_MODE, + CALL_COMPLETED_SET_DISPLAY, CALL_COMPLETED_NOTIFY_WHEN_COMMAND_LABEL_REACHED, CALL_COMPLETED_PREPARE_DRM, }) @@ -1972,9 +1996,11 @@ public abstract class MediaPlayer2 implements AutoCloseable * @param uuid The UUID of the crypto scheme. If not known beforehand, it can be retrieved * from the source through {@code getDrmInfo} or registering a * {@link DrmEventCallback#onDrmInfo}. + * + * @return a token which can be used to cancel the operation later with {@link #cancel}. */ // This is an asynchronous call. - public abstract void prepareDrm(@NonNull UUID uuid); + public abstract Object prepareDrm(@NonNull UUID uuid); /** * Releases the DRM session @@ -1985,8 +2011,9 @@ public abstract class MediaPlayer2 implements AutoCloseable * * @throws NoDrmSchemeException if there is no active DRM session to release */ - // This is an asynchronous call. - public abstract void releaseDrm() throws NoDrmSchemeException; + // This is a synchronous call. + public abstract void releaseDrm() + throws NoDrmSchemeException; /** * A key request/response exchange occurs between the app and a license server @@ -2037,7 +2064,7 @@ public abstract class MediaPlayer2 implements AutoCloseable * provided to the DRM engine plugin using provideDrmKeyResponse. When the * response is for an offline key request, a key-set identifier is returned that * can be used to later restore the keys to a new session with the method - * {@ link # restoreDrmKeys}. + * {@link # restoreDrmKeys}. * When the response is for a streaming or release request, null is returned. * * @param keySetId When the response is for a release request, keySetId identifies @@ -2061,8 +2088,10 @@ public abstract class MediaPlayer2 implements AutoCloseable * keys to load, obtained from a prior call to {@link #provideDrmKeyResponse}. * * @param keySetId identifies the saved key set to restore + * + * @throws NoDrmSchemeException if there is no active DRM session */ - // This is an asynchronous call. + // This is a synchronous call. public abstract void restoreDrmKeys(@NonNull byte[] keySetId) throws NoDrmSchemeException; diff --git a/media/java/android/media/MediaPlayer2Impl.java b/media/java/android/media/MediaPlayer2Impl.java index a5eb1fb12674..ef8db1d4dcba 100644 --- a/media/java/android/media/MediaPlayer2Impl.java +++ b/media/java/android/media/MediaPlayer2Impl.java @@ -192,8 +192,8 @@ public final class MediaPlayer2Impl extends MediaPlayer2 { * beginning. */ @Override - public void play() { - addTask(new Task(CALL_COMPLETED_PLAY, false) { + public Object play() { + return addTask(new Task(CALL_COMPLETED_PLAY, false) { @Override void process() { stayAwake(true); @@ -213,8 +213,8 @@ public final class MediaPlayer2Impl extends MediaPlayer2 { * buffered. */ @Override - public void prepare() { - addTask(new Task(CALL_COMPLETED_PREPARE, true) { + public Object prepare() { + return addTask(new Task(CALL_COMPLETED_PREPARE, true) { @Override void process() { _prepare(); @@ -228,8 +228,8 @@ public final class MediaPlayer2Impl extends MediaPlayer2 { * Pauses playback. Call play() to resume. */ @Override - public void pause() { - addTask(new Task(CALL_COMPLETED_PAUSE, false) { + public Object pause() { + return addTask(new Task(CALL_COMPLETED_PAUSE, false) { @Override void process() { stayAwake(false); @@ -247,8 +247,8 @@ public final class MediaPlayer2Impl extends MediaPlayer2 { * @throws IllegalStateException if it is called in an invalid state */ @Override - public void skipToNext() { - addTask(new Task(CALL_COMPLETED_SKIP_TO_NEXT, false) { + public Object skipToNext() { + return addTask(new Task(CALL_COMPLETED_SKIP_TO_NEXT, false) { @Override void process() { if (getState() == PLAYER_STATE_PLAYING) { @@ -307,8 +307,8 @@ public final class MediaPlayer2Impl extends MediaPlayer2 { * @throws IllegalArgumentException if the attributes are null or invalid. */ @Override - public void setAudioAttributes(@NonNull AudioAttributes attributes) { - addTask(new Task(CALL_COMPLETED_SET_AUDIO_ATTRIBUTES, false) { + public Object setAudioAttributes(@NonNull AudioAttributes attributes) { + return addTask(new Task(CALL_COMPLETED_SET_AUDIO_ATTRIBUTES, false) { @Override void process() { if (attributes == null) { @@ -332,8 +332,8 @@ public final class MediaPlayer2Impl extends MediaPlayer2 { * @param dsd the descriptor of data source you want to play */ @Override - public void setDataSource(@NonNull DataSourceDesc dsd) { - addTask(new Task(CALL_COMPLETED_SET_DATA_SOURCE, false) { + public Object setDataSource(@NonNull DataSourceDesc dsd) { + return addTask(new Task(CALL_COMPLETED_SET_DATA_SOURCE, false) { @Override void process() throws IOException { checkArgument(dsd != null, "the DataSourceDesc cannot be null"); @@ -358,8 +358,8 @@ public final class MediaPlayer2Impl extends MediaPlayer2 { * @param dsd the descriptor of data source you want to play after current one */ @Override - public void setNextDataSource(@NonNull DataSourceDesc dsd) { - addTask(new Task(CALL_COMPLETED_SET_NEXT_DATA_SOURCE, false) { + public Object setNextDataSource(@NonNull DataSourceDesc dsd) { + return addTask(new Task(CALL_COMPLETED_SET_NEXT_DATA_SOURCE, false) { @Override void process() { checkArgument(dsd != null, "the DataSourceDesc cannot be null"); @@ -380,8 +380,8 @@ public final class MediaPlayer2Impl extends MediaPlayer2 { * @param dsds the list of data sources you want to play after current one */ @Override - public void setNextDataSources(@NonNull List<DataSourceDesc> dsds) { - addTask(new Task(CALL_COMPLETED_SET_NEXT_DATA_SOURCES, false) { + public Object setNextDataSources(@NonNull List<DataSourceDesc> dsds) { + return addTask(new Task(CALL_COMPLETED_SET_NEXT_DATA_SOURCES, false) { @Override void process() { if (dsds == null || dsds.size() == 0) { @@ -405,8 +405,8 @@ public final class MediaPlayer2Impl extends MediaPlayer2 { } @Override - public void clearNextDataSources() { - addTask(new Task(CALL_COMPLETED_CLEAR_NEXT_DATA_SOURCES, false) { + public Object clearNextDataSources() { + return addTask(new Task(CALL_COMPLETED_CLEAR_NEXT_DATA_SOURCES, false) { @Override void process() { synchronized (mSrcLock) { @@ -433,8 +433,8 @@ public final class MediaPlayer2Impl extends MediaPlayer2 { * @param loop true if the current data source is meant to loop. */ @Override - public void loopCurrent(boolean loop) { - addTask(new Task(CALL_COMPLETED_LOOP_CURRENT, false) { + public Object loopCurrent(boolean loop) { + return addTask(new Task(CALL_COMPLETED_LOOP_CURRENT, false) { @Override void process() { // TODO: set the looping mode, send notification @@ -455,8 +455,8 @@ public final class MediaPlayer2Impl extends MediaPlayer2 { * @param volume a value between 0.0f and {@link #getMaxPlayerVolume()}. */ @Override - public void setPlayerVolume(float volume) { - addTask(new Task(CALL_COMPLETED_SET_PLAYER_VOLUME, false) { + public Object setPlayerVolume(float volume) { + return addTask(new Task(CALL_COMPLETED_SET_PLAYER_VOLUME, false) { @Override void process() { mVolume = volume; @@ -532,8 +532,8 @@ public final class MediaPlayer2Impl extends MediaPlayer2 { private native byte[] _invoke(byte[] request); @Override - public void notifyWhenCommandLabelReached(Object label) { - addTask(new Task(CALL_COMPLETED_NOTIFY_WHEN_COMMAND_LABEL_REACHED, false) { + public Object notifyWhenCommandLabelReached(Object label) { + return addTask(new Task(CALL_COMPLETED_NOTIFY_WHEN_COMMAND_LABEL_REACHED, false) { @Override void process() { sendEvent(new EventNotifier() { @@ -547,57 +547,27 @@ public final class MediaPlayer2Impl extends MediaPlayer2 { }); } - /** - * Sets the {@link SurfaceHolder} to use for displaying the video - * portion of the media. - * - * Either a surface holder or surface must be set if a display or video sink - * is needed. Not calling this method or {@link #setSurface(Surface)} - * when playing back a video will result in only the audio track being played. - * A null surface holder or surface will result in only the audio track being - * played. - * - * @param sh the SurfaceHolder to use for video display - * @throws IllegalStateException if the internal player engine has not been - * initialized or has been released. - * @hide - */ @Override - public void setDisplay(SurfaceHolder sh) { - mSurfaceHolder = sh; - Surface surface; - if (sh != null) { - surface = sh.getSurface(); - } else { - surface = null; - } - _setVideoSurface(surface); - updateSurfaceScreenOn(); + public Object setDisplay(SurfaceHolder sh) { + return addTask(new Task(CALL_COMPLETED_SET_DISPLAY, false) { + @Override + void process() { + mSurfaceHolder = sh; + Surface surface; + if (sh != null) { + surface = sh.getSurface(); + } else { + surface = null; + } + _setVideoSurface(surface); + updateSurfaceScreenOn(); + } + }); } - /** - * Sets the {@link Surface} to be used as the sink for the video portion of - * the media. This is similar to {@link #setDisplay(SurfaceHolder)}, but - * does not support {@link #setScreenOnWhilePlaying(boolean)}. Setting a - * Surface will un-set any Surface or SurfaceHolder that was previously set. - * A null surface will result in only the audio track being played. - * - * If the Surface sends frames to a {@link SurfaceTexture}, the timestamps - * returned from {@link SurfaceTexture#getTimestamp()} will have an - * unspecified zero point. These timestamps cannot be directly compared - * between different media sources, different instances of the same media - * source, or multiple runs of the same program. The timestamp is normally - * monotonically increasing and is unaffected by time-of-day adjustments, - * but it is reset when the position is set. - * - * @param surface The {@link Surface} to be used for the video portion of - * the media. - * @throws IllegalStateException if the internal player engine has not been - * initialized or has been released. - */ @Override - public void setSurface(Surface surface) { - addTask(new Task(CALL_COMPLETED_SET_SURFACE, false) { + public Object setSurface(Surface surface) { + return addTask(new Task(CALL_COMPLETED_SET_SURFACE, false) { @Override void process() { if (mScreenOnWhilePlaying && surface != null) { @@ -630,8 +600,8 @@ public final class MediaPlayer2Impl extends MediaPlayer2 { * @hide */ @Override - public void setVideoScalingMode(int mode) { - addTask(new Task(CALL_COMPLETED_SET_VIDEO_SCALING_MODE, false) { + public Object setVideoScalingMode(int mode) { + return addTask(new Task(CALL_COMPLETED_SET_VIDEO_SCALING_MODE, false) { @Override void process() { if (!isVideoScalingModeSupported(mode)) { @@ -648,18 +618,26 @@ public final class MediaPlayer2Impl extends MediaPlayer2 { }); } - /** - * Discards all pending commands. - */ + @Override + public boolean cancelCommand(Object token) { + synchronized (mTaskLock) { + return mPendingTasks.remove(token); + } + } + @Override public void clearPendingCommands() { + synchronized (mTaskLock) { + mPendingTasks.clear(); + } } - private void addTask(Task task) { + private Object addTask(Task task) { synchronized (mTaskLock) { mPendingTasks.add(task); processPendingTask_l(); } + return task; } @GuardedBy("mTaskLock") @@ -1282,23 +1260,9 @@ public final class MediaPlayer2Impl extends MediaPlayer2 { @NonNull public native BufferingParams getBufferingParams(); - /** - * Sets buffering management params. - * The object sets its internal BufferingParams to the input, except that the input is - * invalid or not supported. - * Call it only after {@code setDataSource} has been called. - * The input is a hint to MediaPlayer2. - * - * @param params the buffering management params. - * - * @throws IllegalStateException if the internal player engine has not been - * initialized or has been released, or {@code setDataSource} has not been called. - * @throws IllegalArgumentException if params is invalid or not supported. - * @hide - */ @Override - public void setBufferingParams(@NonNull BufferingParams params) { - addTask(new Task(CALL_COMPLETED_SET_BUFFERING_PARAMS, false) { + public Object setBufferingParams(@NonNull BufferingParams params) { + return addTask(new Task(CALL_COMPLETED_SET_BUFFERING_PARAMS, false) { @Override void process() { checkArgument(params != null, "the BufferingParams cannot be null"); @@ -1345,24 +1309,9 @@ public final class MediaPlayer2Impl extends MediaPlayer2 { return params; } - /** - * Sets playback rate using {@link PlaybackParams}. The object sets its internal - * PlaybackParams to the input, except that the object remembers previous speed - * when input speed is zero. This allows the object to resume at previous speed - * when play() is called. Calling it before the object is prepared does not change - * the object state. After the object is prepared, calling it with zero speed is - * equivalent to calling pause(). After the object is prepared, calling it with - * non-zero speed is equivalent to calling play(). - * - * @param params the playback params. - * - * @throws IllegalStateException if the internal player engine has not been - * initialized or has been released. - * @throws IllegalArgumentException if params is not supported. - */ @Override - public void setPlaybackParams(@NonNull PlaybackParams params) { - addTask(new Task(CALL_COMPLETED_SET_PLAYBACK_PARAMS, false) { + public Object setPlaybackParams(@NonNull PlaybackParams params) { + return addTask(new Task(CALL_COMPLETED_SET_PLAYBACK_PARAMS, false) { @Override void process() { checkArgument(params != null, "the PlaybackParams cannot be null"); @@ -1394,8 +1343,8 @@ public final class MediaPlayer2Impl extends MediaPlayer2 { * @throws IllegalArgumentException if params are not supported. */ @Override - public void setSyncParams(@NonNull SyncParams params) { - addTask(new Task(CALL_COMPLETED_SET_SYNC_PARAMS, false) { + public Object setSyncParams(@NonNull SyncParams params) { + return addTask(new Task(CALL_COMPLETED_SET_SYNC_PARAMS, false) { @Override void process() { checkArgument(params != null, "the SyncParams cannot be null"); @@ -1449,8 +1398,8 @@ public final class MediaPlayer2Impl extends MediaPlayer2 { * @throws IllegalArgumentException if the mode is invalid. */ @Override - public void seekTo(final long msec, @SeekMode int mode) { - addTask(new Task(CALL_COMPLETED_SEEK_TO, true) { + public Object seekTo(final long msec, @SeekMode int mode) { + return addTask(new Task(CALL_COMPLETED_SEEK_TO, true) { @Override void process() { if (mode < SEEK_PREVIOUS_SYNC || mode > SEEK_CLOSEST) { @@ -1582,8 +1531,8 @@ public final class MediaPlayer2Impl extends MediaPlayer2 { * @throws IllegalArgumentException if the sessionId is invalid. */ @Override - public void setAudioSessionId(int sessionId) { - addTask(new Task(CALL_COMPLETED_SET_AUDIO_SESSION_ID, false) { + public Object setAudioSessionId(int sessionId) { + return addTask(new Task(CALL_COMPLETED_SET_AUDIO_SESSION_ID, false) { @Override void process() { _setAudioSessionId(sessionId); @@ -1617,8 +1566,8 @@ public final class MediaPlayer2Impl extends MediaPlayer2 { * @param effectId system wide unique id of the effect to attach */ @Override - public void attachAuxEffect(int effectId) { - addTask(new Task(CALL_COMPLETED_ATTACH_AUX_EFFECT, false) { + public Object attachAuxEffect(int effectId) { + return addTask(new Task(CALL_COMPLETED_ATTACH_AUX_EFFECT, false) { @Override void process() { _attachAuxEffect(effectId); @@ -1641,8 +1590,8 @@ public final class MediaPlayer2Impl extends MediaPlayer2 { * @param level send level scalar */ @Override - public void setAuxEffectSendLevel(float level) { - addTask(new Task(CALL_COMPLETED_SET_AUX_EFFECT_SEND_LEVEL, false) { + public Object setAuxEffectSendLevel(float level) { + return addTask(new Task(CALL_COMPLETED_SET_AUX_EFFECT_SEND_LEVEL, false) { @Override void process() { _setAuxEffectSendLevel(level); @@ -1858,8 +1807,8 @@ public final class MediaPlayer2Impl extends MediaPlayer2 { * @see android.media.MediaPlayer2#getTrackInfo */ @Override - public void selectTrack(int index) { - addTask(new Task(CALL_COMPLETED_SELECT_TRACK, false) { + public Object selectTrack(int index) { + return addTask(new Task(CALL_COMPLETED_SELECT_TRACK, false) { @Override void process() { selectOrDeselectTrack(index, true /* select */); @@ -1882,8 +1831,8 @@ public final class MediaPlayer2Impl extends MediaPlayer2 { * @see android.media.MediaPlayer2#getTrackInfo */ @Override - public void deselectTrack(int index) { - addTask(new Task(CALL_COMPLETED_DESELECT_TRACK, false) { + public Object deselectTrack(int index) { + return addTask(new Task(CALL_COMPLETED_DESELECT_TRACK, false) { @Override void process() { selectOrDeselectTrack(index, false /* select */); @@ -2526,8 +2475,8 @@ public final class MediaPlayer2Impl extends MediaPlayer2 { } @Override - public void prepareDrm(@NonNull UUID uuid) { - addTask(new Task(CALL_COMPLETED_PREPARE_DRM, true) { + public Object prepareDrm(@NonNull UUID uuid) { + return addTask(new Task(CALL_COMPLETED_PREPARE_DRM, true) { @Override void process() { int status = PREPARE_DRM_STATUS_SUCCESS; @@ -2682,57 +2631,40 @@ public final class MediaPlayer2Impl extends MediaPlayer2 { } // synchronized } - - private native void _releaseDrm(); - - /** - * Releases the DRM session - * <p> - * The player has to have an active DRM session and be in stopped, or prepared - * state before this call is made. - * A {@code reset()} call will release the DRM session implicitly. - * - * @throws NoDrmSchemeException if there is no active DRM session to release - */ @Override public void releaseDrm() - throws NoDrmSchemeException - { - addTask(new Task(CALL_COMPLETED_RELEASE_DRM, false) { - @Override - void process() throws NoDrmSchemeException { - synchronized (mDrmLock) { - Log.v(TAG, "releaseDrm:"); - - if (!mActiveDrmScheme) { - Log.e(TAG, "releaseDrm(): No active DRM scheme to release."); - throw new NoDrmSchemeExceptionImpl( - "releaseDrm: No active DRM scheme to release."); - } + throws NoDrmSchemeException { + synchronized (mDrmLock) { + Log.v(TAG, "releaseDrm:"); - try { - // we don't have the player's state in this layer. The below call raises - // exception if we're in a non-stopped/prepared state. + if (!mActiveDrmScheme) { + Log.e(TAG, "releaseDrm(): No active DRM scheme to release."); + throw new NoDrmSchemeExceptionImpl( + "releaseDrm: No active DRM scheme to release."); + } + + try { + // we don't have the player's state in this layer. The below call raises + // exception if we're in a non-stopped/prepared state. - // for cleaning native/mediaserver crypto object - _releaseDrm(); + // for cleaning native/mediaserver crypto object + _releaseDrm(); - // for cleaning client-side MediaDrm object; only called if above has succeeded - cleanDrmObj(); + // for cleaning client-side MediaDrm object; only called if above has succeeded + cleanDrmObj(); - mActiveDrmScheme = false; - } catch (IllegalStateException e) { - Log.w(TAG, "releaseDrm: Exception ", e); - throw new IllegalStateException( - "releaseDrm: The player is not in a valid state."); - } catch (Exception e) { - Log.e(TAG, "releaseDrm: Exception ", e); - } - } // synchronized + mActiveDrmScheme = false; + } catch (IllegalStateException e) { + Log.w(TAG, "releaseDrm: Exception ", e); + throw new IllegalStateException( + "releaseDrm: The player is not in a valid state."); + } catch (Exception e) { + Log.e(TAG, "releaseDrm: Exception ", e); } - }); + } // synchronized } + private native void _releaseDrm(); /** * A key request/response exchange occurs between the app and a license server @@ -2823,7 +2755,7 @@ public final class MediaPlayer2Impl extends MediaPlayer2 { * provided to the DRM engine plugin using provideDrmKeyResponse. When the * response is for an offline key request, a key-set identifier is returned that * can be used to later restore the keys to a new session with the method - * {@ link # restoreDrmKeys}. + * {@link # restoreDrmKeys}. * When the response is for a streaming or release request, null is returned. * * @param keySetId When the response is for a release request, keySetId identifies @@ -2876,43 +2808,27 @@ public final class MediaPlayer2Impl extends MediaPlayer2 { } // synchronized } - - /** - * Restore persisted offline keys into a new session. keySetId identifies the - * keys to load, obtained from a prior call to {@link #provideDrmKeyResponse}. - * - * @param keySetId identifies the saved key set to restore - */ @Override public void restoreDrmKeys(@NonNull byte[] keySetId) - throws NoDrmSchemeException - { - addTask(new Task(CALL_COMPLETED_RESTORE_DRM_KEYS, false) { - @Override - void process() throws NoDrmSchemeException { - Log.v(TAG, "restoreDrmKeys: keySetId: " + keySetId); - - synchronized (mDrmLock) { - - if (!mActiveDrmScheme) { - Log.w(TAG, "restoreDrmKeys NoDrmSchemeException"); - throw new NoDrmSchemeExceptionImpl( - "restoreDrmKeys: Has to set a DRM scheme first."); - } + throws NoDrmSchemeException { + Log.v(TAG, "restoreDrmKeys: keySetId: " + keySetId); - try { - mDrmObj.restoreKeys(mDrmSessionId, keySetId); - } catch (Exception e) { - Log.w(TAG, "restoreKeys Exception " + e); - throw e; - } + synchronized (mDrmLock) { + if (!mActiveDrmScheme) { + Log.w(TAG, "restoreDrmKeys NoDrmSchemeException"); + throw new NoDrmSchemeExceptionImpl( + "restoreDrmKeys: Has to set a DRM scheme first."); + } - } // synchronized + try { + mDrmObj.restoreKeys(mDrmSessionId, keySetId); + } catch (Exception e) { + Log.w(TAG, "restoreKeys Exception " + e); + throw e; } - }); + } // synchronized } - /** * Read a DRM engine plugin String property value, given the property name string. * <p> @@ -3601,7 +3517,7 @@ public final class MediaPlayer2Impl extends MediaPlayer2 { private void sendCompleteNotification(int status) { // In {@link #notifyWhenCommandLabelReached} case, a separate callback // {@link #onCommandLabelReached} is already called in {@code process()}. - // CALL_COMPLETED_PREPARE_DRM is sent via DrmEventCallback + // CALL_COMPLETED_PREPARE_DRM is sent via DrmEventCallback#onDrmPrepared if (mMediaCallType == CALL_COMPLETED_NOTIFY_WHEN_COMMAND_LABEL_REACHED || mMediaCallType == CALL_COMPLETED_PREPARE_DRM) { return; diff --git a/packages/BackupRestoreConfirmation/res/values-mr/strings.xml b/packages/BackupRestoreConfirmation/res/values-mr/strings.xml index 4be28db10c3a..b3541717c96e 100644 --- a/packages/BackupRestoreConfirmation/res/values-mr/strings.xml +++ b/packages/BackupRestoreConfirmation/res/values-mr/strings.xml @@ -24,10 +24,10 @@ <string name="restore_confirm_text" msgid="7499866728030461776">"कनेक्ट केलेल्या डेस्कटॉप काँप्युटरवरील सर्व डेटाच्या पूर्ण पुनर्संचयनाची विनंती केली गेली आहे. तुम्ही असे होण्यासाठी अनुमती देऊ इच्छिता?\n\nतुम्ही स्वत: पुनर्संचयनाची विनंती केली नसल्यास, कार्य पुढे सुरु राहण्यास अनुमती देऊ नका. हे आपल्या डिव्हाइसवरील कोणत्याही वर्तमान डेटास पुनर्स्थित करेल!"</string> <string name="allow_restore_button_label" msgid="3081286752277127827">"माझा डेटा पुनर्संचयित करा"</string> <string name="deny_restore_button_label" msgid="1724367334453104378">"पुनर्संचयित करू नका"</string> - <string name="current_password_text" msgid="8268189555578298067">"कृपया आपला वर्तमान बॅकअप संकेतशब्द खाली प्रविष्ट करा:"</string> + <string name="current_password_text" msgid="8268189555578298067">"कृपया तुमचा वर्तमान बॅकअप संकेतशब्द खाली प्रविष्ट करा:"</string> <string name="device_encryption_restore_text" msgid="1570864916855208992">"कृपया तुमचे डिव्हाइस एंक्रिप्शन पासवर्ड खाली एंटर करा."</string> <string name="device_encryption_backup_text" msgid="5866590762672844664">"कृपया तुमचे डिव्हाइस एंक्रिप्शन पासवर्ड खाली एंटर करा. हा बॅकअप संग्रह एंक्रिप्ट करण्यासाठी देखील वापरला जाईल."</string> - <string name="backup_enc_password_text" msgid="4981585714795233099">"कृपया पूर्ण बॅकअप डेटा एंक्रिप्ट करण्यासाठी वापरण्याकरिता पासवर्ड एंटर करा. हे रिक्त सोडल्यास, आपला वर्तमान बॅकअप पासवर्ड वापरला जाईल:"</string> + <string name="backup_enc_password_text" msgid="4981585714795233099">"कृपया पूर्ण बॅकअप डेटा एंक्रिप्ट करण्यासाठी वापरण्याकरिता पासवर्ड एंटर करा. हे रिक्त सोडल्यास, तुमचा वर्तमान बॅकअप पासवर्ड वापरला जाईल:"</string> <string name="backup_enc_password_optional" msgid="1350137345907579306">"तुम्ही पूर्ण बॅकअप डेटा एंक्रिप्ट करू इच्छित असल्यास, खालील पासवर्ड एंटर करा:"</string> <string name="backup_enc_password_required" msgid="7889652203371654149">"तुमचे डिव्हाइस एंक्रिप्ट केले असल्यामुळे, तुम्हाला तुमचा बॅक अप एंक्रिप्ट करणे आवश्यक आहे. कृपया खाली एक पासवर्ड एंटर करा:"</string> <string name="restore_enc_password_text" msgid="6140898525580710823">"पुनर्स्टोअर केलेला डेटा एंक्रिप्ट केला असल्यास, कृपया पासवर्ड खाली एंटर करा:"</string> diff --git a/packages/CarrierDefaultApp/res/values-mr/strings.xml b/packages/CarrierDefaultApp/res/values-mr/strings.xml index e1442c2ca48e..79cc4aa03cfc 100644 --- a/packages/CarrierDefaultApp/res/values-mr/strings.xml +++ b/packages/CarrierDefaultApp/res/values-mr/strings.xml @@ -4,7 +4,7 @@ <string name="app_name" msgid="5247871339820894594">"CarrierDefaultApp"</string> <string name="android_system_label" msgid="2797790869522345065">"मोबाइल वाहक"</string> <string name="portal_notification_id" msgid="5155057562457079297">"मोबाइल डेटा संपला आहे"</string> - <string name="no_data_notification_id" msgid="668400731803969521">"आपला मोबाइल डेटा निष्क्रिय केला गेला"</string> + <string name="no_data_notification_id" msgid="668400731803969521">"तुमचा मोबाइल डेटा निष्क्रिय केला गेला"</string> <string name="portal_notification_detail" msgid="2295729385924660881">"%s वेबसाइटला भेट देण्यासाठी टॅप करा"</string> <string name="no_data_notification_detail" msgid="3112125343857014825">"कृपया आपल्या %s सेवा प्रदात्याशी संपर्क साधा"</string> <string name="no_mobile_data_connection_title" msgid="7449525772416200578">"मोबाइल डेटा कनेक्शन नाही"</string> diff --git a/packages/OsuLogin/Android.mk b/packages/OsuLogin/Android.mk index f04227bda263..2c076159ec12 100644 --- a/packages/OsuLogin/Android.mk +++ b/packages/OsuLogin/Android.mk @@ -3,6 +3,7 @@ include $(CLEAR_VARS) LOCAL_MODULE_TAGS := optional LOCAL_USE_AAPT2 := true +LOCAL_STATIC_ANDROID_LIBRARIES := androidx.legacy_legacy-support-v4 LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res LOCAL_SRC_FILES := $(call all-java-files-under, src) diff --git a/packages/OsuLogin/AndroidManifest.xml b/packages/OsuLogin/AndroidManifest.xml index bc32d103bdd4..123559adf6fd 100644 --- a/packages/OsuLogin/AndroidManifest.xml +++ b/packages/OsuLogin/AndroidManifest.xml @@ -17,10 +17,10 @@ */ --> <manifest xmlns:android="http://schemas.android.com/apk/res/android" - package="com.android.hotspot2"> - <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> - <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" /> - <uses-permission android:name="android.permission.INTERNET" /> + package="com.android.hotspot2"> + <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/> + <uses-permission android:name="android.permission.CHANGE_WIFI_STATE"/> + <uses-permission android:name="android.permission.INTERNET"/> <application android:networkSecurityConfig="@xml/network_security_config" @@ -28,7 +28,10 @@ android:label="@string/app_name" android:configChanges="keyboardHidden|orientation|screenSize" android:supportsRtl="true"> - <activity android:name="com.android.hotspot2.osu.OsuLoginActivity"> + <activity android:name="com.android.hotspot2.osu.OsuLoginActivity" + android:label="@string/action_bar_label" + android:theme="@style/AppTheme" + android:configChanges="keyboardHidden|orientation|screenSize"> <intent-filter> <action android:name="android.net.wifi.action.PASSPOINT_LAUNCH_OSU_VIEW"/> <category android:name="android.intent.category.DEFAULT"/> diff --git a/packages/OsuLogin/res/layout/osu_web_view.xml b/packages/OsuLogin/res/layout/osu_web_view.xml index 4eafb39d1713..fb3c06513430 100644 --- a/packages/OsuLogin/res/layout/osu_web_view.xml +++ b/packages/OsuLogin/res/layout/osu_web_view.xml @@ -1,13 +1,38 @@ <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:tools="http://schemas.android.com/tools" - android:id="@+id/container" - android:layout_width="match_parent" - android:layout_height="match_parent"> - <WebView - android:id="@+id/webview" + xmlns:tools="http://schemas.android.com/tools" + android:id="@+id/container" + android:layout_width="match_parent" + android:layout_height="match_parent" + tools:context="com.android.hotspot2.osu.OsuLoginActivity"> + <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" - android:layout_alignParentBottom="true" - android:layout_alignParentRight="true" /> + android:orientation="vertical"> + <FrameLayout + android:layout_width="match_parent" + android:layout_height="4dp"> + <!-- Eliminates ProgressBar padding by boxing it into a 4dp high container --> + <ProgressBar + android:id="@+id/progress_bar" + style="@android:style/Widget.Material.Light.ProgressBar.Horizontal" + android:indeterminate="false" + android:max="100" + android:progress="0" + android:layout_gravity="center" + android:layout_width="match_parent" + android:layout_height="wrap_content"/> + </FrameLayout> + <androidx.swiperefreshlayout.widget.SwipeRefreshLayout + android:id="@+id/swipe_refresh" + android:layout_width="match_parent" + android:layout_height="match_parent"> + <WebView + android:id="@+id/webview" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:layout_alignParentBottom="false" + android:layout_alignParentRight="false"/> + </androidx.swiperefreshlayout.widget.SwipeRefreshLayout> + </LinearLayout> </FrameLayout> diff --git a/packages/OsuLogin/res/values/strings.xml b/packages/OsuLogin/res/values/strings.xml index 9fafeeff4348..06fa8c7b1e8f 100644 --- a/packages/OsuLogin/res/values/strings.xml +++ b/packages/OsuLogin/res/values/strings.xml @@ -1,4 +1,6 @@ <resources> <!-- application name [CHAR LIMIT=32] --> <string name="app_name">OsuLogin</string> + <!-- action bar label [CHAR LIMIT=32] --> + <string name="action_bar_label">Online Sign Up</string> </resources> diff --git a/packages/OsuLogin/src/com/android/hotspot2/osu/OsuLoginActivity.java b/packages/OsuLogin/src/com/android/hotspot2/osu/OsuLoginActivity.java index 6a4890467686..0312b81493be 100644 --- a/packages/OsuLogin/src/com/android/hotspot2/osu/OsuLoginActivity.java +++ b/packages/OsuLogin/src/com/android/hotspot2/osu/OsuLoginActivity.java @@ -16,9 +16,12 @@ package com.android.hotspot2.osu; +import static android.net.NetworkCapabilities.NET_CAPABILITY_TRUSTED; + import android.annotation.Nullable; import android.app.Activity; import android.content.Context; +import android.graphics.Bitmap; import android.net.ConnectivityManager; import android.net.Network; import android.net.NetworkCapabilities; @@ -27,17 +30,22 @@ import android.net.wifi.WifiManager; import android.os.Bundle; import android.util.Log; import android.view.KeyEvent; +import android.view.View; +import android.webkit.WebChromeClient; import android.webkit.WebResourceError; import android.webkit.WebResourceRequest; import android.webkit.WebSettings; import android.webkit.WebView; import android.webkit.WebViewClient; +import android.widget.ProgressBar; import com.android.hotspot2.R; import java.net.MalformedURLException; import java.net.URL; +import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; + /** * Online Sign Up Login Web View launched during Provision Process of Hotspot 2.0 rel2. */ @@ -52,6 +60,8 @@ public class OsuLoginActivity extends Activity { private ConnectivityManager.NetworkCallback mNetworkCallback; private WifiManager mWifiManager; private WebView mWebView; + private SwipeRefreshLayout mSwipeRefreshLayout; + private ProgressBar mProgressBar; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { @@ -117,6 +127,9 @@ public class OsuLoginActivity extends Activity { } getActionBar().setDisplayShowHomeEnabled(false); + getActionBar().setElevation(0); // remove shadow + getActionBar().setTitle(getString(R.string.action_bar_label)); + getActionBar().setSubtitle(""); setContentView(R.layout.osu_web_view); // Exit this app if network disappeared. @@ -134,7 +147,8 @@ public class OsuLoginActivity extends Activity { mCm.registerNetworkCallback( new NetworkRequest.Builder().addTransportType( - NetworkCapabilities.TRANSPORT_WIFI).build(), + NetworkCapabilities.TRANSPORT_WIFI).removeCapability( + NET_CAPABILITY_TRUSTED).build(), mNetworkCallback); mWebView = findViewById(R.id.webview); @@ -147,12 +161,25 @@ public class OsuLoginActivity extends Activity { webSettings.setSupportZoom(true); webSettings.setBuiltInZoomControls(true); webSettings.setDisplayZoomControls(false); - + mProgressBar = findViewById(R.id.progress_bar); mWebView.setWebViewClient(new OsuWebViewClient()); + mWebView.setWebChromeClient(new WebChromeClient() { + @Override + public void onProgressChanged(WebView view, int newProgress) { + mProgressBar.setProgress(newProgress); + } + }); + if (DBG) { Log.d(TAG, "OSU Web View to " + mUrl); } + mWebView.loadUrl(mUrl); + mSwipeRefreshLayout = findViewById(R.id.swipe_refresh); + mSwipeRefreshLayout.setOnRefreshListener(() -> { + mWebView.reload(); + mSwipeRefreshLayout.setRefreshing(true); + }); } @Override @@ -191,11 +218,31 @@ public class OsuLoginActivity extends Activity { return null; } + private String getHeaderSubtitle(String urlString) { + try { + URL url = new URL(urlString); + return url.getProtocol() + "://" + url.getHost(); + } catch (MalformedURLException e) { + Log.e(TAG, "Invalid URL " + urlString); + } + return ""; + } + private class OsuWebViewClient extends WebViewClient { boolean mPageError = false; @Override + public void onPageStarted(WebView view, String urlString, Bitmap favicon) { + String subtitle = getHeaderSubtitle(urlString); + getActionBar().setSubtitle(subtitle); + mProgressBar.setVisibility(View.VISIBLE); + } + + @Override public void onPageFinished(WebView view, String url) { + mProgressBar.setVisibility(View.INVISIBLE); + mSwipeRefreshLayout.setRefreshing(false); + // Do not show the page error on UI. if (mPageError) { finishAndRemoveTask(); @@ -213,4 +260,5 @@ public class OsuLoginActivity extends Activity { } } } + } diff --git a/packages/PackageInstaller/res/values-af/strings.xml b/packages/PackageInstaller/res/values-af/strings.xml index 2e7c4aa3dc2f..dfee4e83d7a3 100644 --- a/packages/PackageInstaller/res/values-af/strings.xml +++ b/packages/PackageInstaller/res/values-af/strings.xml @@ -88,4 +88,7 @@ <string name="anonymous_source_continue" msgid="4375745439457209366">"Gaan voort"</string> <string name="external_sources_settings" msgid="4046964413071713807">"Instellings"</string> <string name="wear_app_channel" msgid="1960809674709107850">"Installeer/deïnstalleer Wear-programme"</string> + <string name="app_installed_notification_channel_description" msgid="2695385797601574123">"Kennisgewing dat program geïnstalleer is"</string> + <string name="notification_installation_success_message" msgid="6450467996056038442">"Suksesvol geïnstalleer"</string> + <string name="notification_installation_success_status" msgid="3172502643504323321">"“<xliff:g id="APPNAME">%1$s</xliff:g>” is suksesvol geïnstalleer"</string> </resources> diff --git a/packages/PackageInstaller/res/values-am/strings.xml b/packages/PackageInstaller/res/values-am/strings.xml index 4c3d60d9871e..cfdeb59d0b83 100644 --- a/packages/PackageInstaller/res/values-am/strings.xml +++ b/packages/PackageInstaller/res/values-am/strings.xml @@ -88,4 +88,7 @@ <string name="anonymous_source_continue" msgid="4375745439457209366">"ቀጥል"</string> <string name="external_sources_settings" msgid="4046964413071713807">"ቅንብሮች"</string> <string name="wear_app_channel" msgid="1960809674709107850">"የWear መተግበሪያዎችን መጫን/ማራገፍ"</string> + <string name="app_installed_notification_channel_description" msgid="2695385797601574123">"የመተግበሪያ ተጭኗል ማሳወቂያ"</string> + <string name="notification_installation_success_message" msgid="6450467996056038442">"በተሳካ ሁኔታ ተጭኗል"</string> + <string name="notification_installation_success_status" msgid="3172502643504323321">"«<xliff:g id="APPNAME">%1$s</xliff:g>» በተሳካ ሁኔታ ተጭኗል"</string> </resources> diff --git a/packages/PackageInstaller/res/values-ar/strings.xml b/packages/PackageInstaller/res/values-ar/strings.xml index 1721bf53bd34..da919830c761 100644 --- a/packages/PackageInstaller/res/values-ar/strings.xml +++ b/packages/PackageInstaller/res/values-ar/strings.xml @@ -88,4 +88,7 @@ <string name="anonymous_source_continue" msgid="4375745439457209366">"متابعة"</string> <string name="external_sources_settings" msgid="4046964413071713807">"الإعدادات"</string> <string name="wear_app_channel" msgid="1960809674709107850">"تثبيت / إلغاء تثبيت تطبيقات Android Wear"</string> + <string name="app_installed_notification_channel_description" msgid="2695385797601574123">"إشعار \"تم تثبيت التطبيق\""</string> + <string name="notification_installation_success_message" msgid="6450467996056038442">"تم التثبيت بنجاح."</string> + <string name="notification_installation_success_status" msgid="3172502643504323321">"تم تثبيت \"<xliff:g id="APPNAME">%1$s</xliff:g>\" بنجاح."</string> </resources> diff --git a/packages/PackageInstaller/res/values-as/strings.xml b/packages/PackageInstaller/res/values-as/strings.xml new file mode 100644 index 000000000000..172e03143d57 --- /dev/null +++ b/packages/PackageInstaller/res/values-as/strings.xml @@ -0,0 +1,166 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- Copyright (C) 2007 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <!-- no translation found for app_name (7488448184431507488) --> + <skip /> + <!-- no translation found for install (711829760615509273) --> + <skip /> + <!-- no translation found for done (6632441120016885253) --> + <skip /> + <!-- no translation found for cancel (1018267193425558088) --> + <skip /> + <!-- no translation found for installing (4921993079741206516) --> + <skip /> + <!-- no translation found for installing_app (1165095864863849422) --> + <skip /> + <!-- no translation found for install_done (5987363587661783896) --> + <skip /> + <!-- no translation found for install_confirm_question (8176284075816604590) --> + <skip /> + <!-- no translation found for install_confirm_question_update (7942235418781274635) --> + <skip /> + <!-- no translation found for install_confirm_question_update_system (4713001702777910263) --> + <skip /> + <!-- no translation found for install_failed (5777824004474125469) --> + <skip /> + <!-- no translation found for install_failed_blocked (8512284352994752094) --> + <skip /> + <!-- no translation found for install_failed_conflict (3493184212162521426) --> + <skip /> + <!-- no translation found for install_failed_incompatible (6019021440094927928) --> + <skip /> + <!-- no translation found for install_failed_incompatible (2890001324362291683) --> + <skip /> + <!-- no translation found for install_failed_incompatible (7254630419511645826) --> + <skip /> + <!-- no translation found for install_failed_invalid_apk (8581007676422623930) --> + <skip /> + <!-- no translation found for install_failed_msg (6298387264270562442) --> + <skip /> + <!-- no translation found for install_failed_msg (1920009940048975221) --> + <skip /> + <!-- no translation found for install_failed_msg (6484461562647915707) --> + <skip /> + <!-- no translation found for launch (3952550563999890101) --> + <skip /> + <!-- no translation found for unknown_apps_admin_dlg_text (4456572224020176095) --> + <skip /> + <!-- no translation found for unknown_apps_user_restriction_dlg_text (151020786933988344) --> + <skip /> + <!-- no translation found for install_apps_user_restriction_dlg_text (2154119597001074022) --> + <skip /> + <!-- no translation found for ok (7871959885003339302) --> + <skip /> + <!-- no translation found for manage_applications (5400164782453975580) --> + <skip /> + <!-- no translation found for out_of_space_dlg_title (4156690013884649502) --> + <skip /> + <!-- no translation found for out_of_space_dlg_text (8727714096031856231) --> + <skip /> + <!-- no translation found for app_not_found_dlg_title (5107924008597470285) --> + <skip /> + <!-- no translation found for app_not_found_dlg_text (5219983779377811611) --> + <skip /> + <!-- no translation found for user_is_not_allowed_dlg_title (6915293433252210232) --> + <skip /> + <!-- no translation found for user_is_not_allowed_dlg_text (3468447791330611681) --> + <skip /> + <!-- no translation found for generic_error_dlg_title (5863195085927067752) --> + <skip /> + <!-- no translation found for generic_error_dlg_text (5287861443265795232) --> + <skip /> + <!-- no translation found for uninstall_application_title (4045420072401428123) --> + <skip /> + <!-- no translation found for uninstall_update_title (824411791011583031) --> + <skip /> + <!-- no translation found for uninstall_activity_text (1928194674397770771) --> + <skip /> + <!-- no translation found for uninstall_application_text (3816830743706143980) --> + <skip /> + <!-- no translation found for uninstall_application_text_all_users (575491774380227119) --> + <skip /> + <!-- no translation found for uninstall_application_text_user (498072714173920526) --> + <skip /> + <!-- no translation found for uninstall_update_text (863648314632448705) --> + <skip /> + <!-- no translation found for uninstall_update_text_multiuser (8992883151333057227) --> + <skip /> + <!-- no translation found for uninstalling_notification_channel (840153394325714653) --> + <skip /> + <!-- no translation found for uninstall_failure_notification_channel (1136405866767576588) --> + <skip /> + <!-- no translation found for uninstalling (8709566347688966845) --> + <skip /> + <!-- no translation found for uninstalling_app (8866082646836981397) --> + <skip /> + <!-- no translation found for uninstall_done (439354138387969269) --> + <skip /> + <!-- no translation found for uninstall_done_app (4588850984473605768) --> + <skip /> + <!-- no translation found for uninstall_failed (1847750968168364332) --> + <skip /> + <!-- no translation found for uninstall_failed_app (5506028705017601412) --> + <skip /> + <!-- no translation found for uninstall_failed_device_policy_manager (785293813665540305) --> + <skip /> + <!-- no translation found for uninstall_failed_device_policy_manager_of_user (4813104025494168064) --> + <skip /> + <!-- no translation found for uninstall_all_blocked_profile_owner (2009393666026751501) --> + <skip /> + <!-- no translation found for uninstall_blocked_profile_owner (6373897407002404848) --> + <skip /> + <!-- no translation found for uninstall_blocked_device_owner (6724602931761073901) --> + <skip /> + <!-- no translation found for manage_device_administrators (3092696419363842816) --> + <skip /> + <!-- no translation found for manage_users (1243995386982560813) --> + <skip /> + <!-- no translation found for uninstall_failed_msg (2176744834786696012) --> + <skip /> + <!-- no translation found for Parse_error_dlg_text (1661404001063076789) --> + <skip /> + <!-- no translation found for wear_not_allowed_dlg_title (8664785993465117517) --> + <skip /> + <!-- no translation found for wear_not_allowed_dlg_text (704615521550939237) --> + <skip /> + <!-- no translation found for message_staging (8032722385658438567) --> + <skip /> + <!-- no translation found for app_name_unknown (6881210203354323926) --> + <skip /> + <!-- no translation found for untrusted_external_source_warning (6539403649459942547) --> + <skip /> + <!-- no translation found for untrusted_external_source_warning (1206648674551321364) --> + <skip /> + <!-- no translation found for untrusted_external_source_warning (7279739265754475165) --> + <skip /> + <!-- no translation found for anonymous_source_warning (2784902545920822500) --> + <skip /> + <!-- no translation found for anonymous_source_warning (3939101621438855516) --> + <skip /> + <!-- no translation found for anonymous_source_warning (5599483539528168566) --> + <skip /> + <!-- no translation found for anonymous_source_continue (4375745439457209366) --> + <skip /> + <!-- no translation found for external_sources_settings (4046964413071713807) --> + <skip /> + <!-- no translation found for wear_app_channel (1960809674709107850) --> + <skip /> + <string name="app_installed_notification_channel_description" msgid="2695385797601574123">"এপ্ ইনষ্টল কৰাৰ জাননী"</string> + <string name="notification_installation_success_message" msgid="6450467996056038442">"সফলতাৰে ইনষ্টল কৰা হ’ল"</string> + <string name="notification_installation_success_status" msgid="3172502643504323321">"“<xliff:g id="APPNAME">%1$s</xliff:g>” সফলতাৰে ইনষ্টল কৰা হ’ল"</string> +</resources> diff --git a/packages/PackageInstaller/res/values-az/strings.xml b/packages/PackageInstaller/res/values-az/strings.xml index 2248c6d6a80d..478322a1dfff 100644 --- a/packages/PackageInstaller/res/values-az/strings.xml +++ b/packages/PackageInstaller/res/values-az/strings.xml @@ -88,4 +88,7 @@ <string name="anonymous_source_continue" msgid="4375745439457209366">"Davam edin"</string> <string name="external_sources_settings" msgid="4046964413071713807">"Ayarlar"</string> <string name="wear_app_channel" msgid="1960809674709107850">"Wear tətbiqləri quraşdırılır/sistemdən silinir"</string> + <string name="app_installed_notification_channel_description" msgid="2695385797601574123">"Tətbiq quraşdırma bildirişi"</string> + <string name="notification_installation_success_message" msgid="6450467996056038442">"Quraşdırıldı"</string> + <string name="notification_installation_success_status" msgid="3172502643504323321">"\"<xliff:g id="APPNAME">%1$s</xliff:g>\" quraşdırıldı"</string> </resources> diff --git a/packages/PackageInstaller/res/values-b+sr+Latn/strings.xml b/packages/PackageInstaller/res/values-b+sr+Latn/strings.xml index 6cd1f40aa6ea..cab6be5d9902 100644 --- a/packages/PackageInstaller/res/values-b+sr+Latn/strings.xml +++ b/packages/PackageInstaller/res/values-b+sr+Latn/strings.xml @@ -88,4 +88,7 @@ <string name="anonymous_source_continue" msgid="4375745439457209366">"Nastavi"</string> <string name="external_sources_settings" msgid="4046964413071713807">"Podešavanja"</string> <string name="wear_app_channel" msgid="1960809674709107850">"Instaliranje/deinstaliranje Wear aplikac."</string> + <string name="app_installed_notification_channel_description" msgid="2695385797601574123">"Obaveštenje o instaliranju aplikacije"</string> + <string name="notification_installation_success_message" msgid="6450467996056038442">"Instalirana je"</string> + <string name="notification_installation_success_status" msgid="3172502643504323321">"Aplikacija „<xliff:g id="APPNAME">%1$s</xliff:g>“ je instalirana"</string> </resources> diff --git a/packages/PackageInstaller/res/values-be/strings.xml b/packages/PackageInstaller/res/values-be/strings.xml index d432eb7d0140..4433b73de0b0 100644 --- a/packages/PackageInstaller/res/values-be/strings.xml +++ b/packages/PackageInstaller/res/values-be/strings.xml @@ -88,4 +88,7 @@ <string name="anonymous_source_continue" msgid="4375745439457209366">"Далей"</string> <string name="external_sources_settings" msgid="4046964413071713807">"Налады"</string> <string name="wear_app_channel" msgid="1960809674709107850">"Усталяванне і выдаленне праграм Wear"</string> + <string name="app_installed_notification_channel_description" msgid="2695385797601574123">"Апавяшчэнне пра завяршэнне ўсталявання"</string> + <string name="notification_installation_success_message" msgid="6450467996056038442">"Усталявана"</string> + <string name="notification_installation_success_status" msgid="3172502643504323321">"Усталявана праграма \"<xliff:g id="APPNAME">%1$s</xliff:g>\""</string> </resources> diff --git a/packages/PackageInstaller/res/values-bg/strings.xml b/packages/PackageInstaller/res/values-bg/strings.xml index a1ae85c763da..2b76bddce579 100644 --- a/packages/PackageInstaller/res/values-bg/strings.xml +++ b/packages/PackageInstaller/res/values-bg/strings.xml @@ -88,4 +88,7 @@ <string name="anonymous_source_continue" msgid="4375745439457209366">"Напред"</string> <string name="external_sources_settings" msgid="4046964413071713807">"Настройки"</string> <string name="wear_app_channel" msgid="1960809674709107850">"Инсталир./деинсталир. на прилож. за Wear"</string> + <string name="app_installed_notification_channel_description" msgid="2695385797601574123">"Известие, че приложението е инсталирано"</string> + <string name="notification_installation_success_message" msgid="6450467996056038442">"Инсталирането бе успешно"</string> + <string name="notification_installation_success_status" msgid="3172502643504323321">"Приложението <xliff:g id="APPNAME">%1$s</xliff:g> бе инсталирано успешно"</string> </resources> diff --git a/packages/PackageInstaller/res/values-bn/strings.xml b/packages/PackageInstaller/res/values-bn/strings.xml index 0afcb817b8c5..ee264d91b944 100644 --- a/packages/PackageInstaller/res/values-bn/strings.xml +++ b/packages/PackageInstaller/res/values-bn/strings.xml @@ -24,11 +24,11 @@ <string name="installing_app" msgid="1165095864863849422">"<xliff:g id="PACKAGE_LABEL">%1$s</xliff:g> ইনস্টল করা হচ্ছে…"</string> <string name="install_done" msgid="5987363587661783896">"অ্যাপটি ইনস্টল করা হয়ে গেছে।"</string> <string name="install_confirm_question" msgid="8176284075816604590">"আপনি কি এই অ্যাপ্লিকেশনটি ইনস্টল করতে চান?"</string> - <string name="install_confirm_question_update" msgid="7942235418781274635">"আগে থেকেই আছে এই অ্যাপ্লিকেশনটির একটি আপডেট কি আপনি ইনস্টল করতে চান? আপনার আগে থেকেই আছে এমন ডেটা মুছে যাবে না।"</string> - <string name="install_confirm_question_update_system" msgid="4713001702777910263">"এই বিল্ট-ইন অ্যাপ্লিকেশনটির একটি আপডেট কি আপনি ইনস্টল করতে চান? আপনার আগে থেকেই আছে এমন ডেটা মুছে যাবে না।"</string> + <string name="install_confirm_question_update" msgid="7942235418781274635">"আগে থেকেই থাকা এই অ্যাপ্লিকেশনটির একটি আপডেট কি আপনি ইনস্টল করতে চান? আপনার আগে থেকে থাকা ডেটা মুছে যাবে না।"</string> + <string name="install_confirm_question_update_system" msgid="4713001702777910263">"এই বিল্ট-ইন অ্যাপ্লিকেশনটির একটি আপডেট কি আপনি ইনস্টল করতে চান? আপনার আগে থেকে থাকা ডেটা মুছে যাবে না।"</string> <string name="install_failed" msgid="5777824004474125469">"অ্যাপটি ইনস্টল করা হয়নি।"</string> <string name="install_failed_blocked" msgid="8512284352994752094">"ইনস্টল হওয়া থেকে প্যাকেজটিকে ব্লক করা হয়েছে।"</string> - <string name="install_failed_conflict" msgid="3493184212162521426">"আগে থেকেই আছে এমন একটি প্যাকেজের সাথে প্যাকেজটির সমস্যা সৃষ্টি হওয়ায় অ্যাপটি ইনস্টল করা যায়নি।"</string> + <string name="install_failed_conflict" msgid="3493184212162521426">"আগে থেকেই থাকা একটি প্যাকেজের সাথে প্যাকেজটির সমস্যা সৃষ্টি হওয়ায় অ্যাপটি ইনস্টল করা যায়নি।"</string> <string name="install_failed_incompatible" product="tablet" msgid="6019021440094927928">"আপনার ট্যাবলেটের সাথে মানানসই না হওয়ায় অ্যাপটি ইনস্টল করা যায়নি।"</string> <string name="install_failed_incompatible" product="tv" msgid="2890001324362291683">"এই অ্যাপটি আপনার টিভির সাথে মানানসই নয়।"</string> <string name="install_failed_incompatible" product="default" msgid="7254630419511645826">"আপনার ফোনের সাথে মানানসই না হওয়ায় অ্যাপটি ইনস্টল করা যায়নি।"</string> @@ -70,7 +70,7 @@ <string name="uninstall_failed_device_policy_manager_of_user" msgid="4813104025494168064">"<xliff:g id="USERNAME">%1$s</xliff:g>-এর চালু থাকা ডিভাইস অ্যাডমিন অ্যাপটি আনইনস্টল করা যাচ্ছে না"</string> <string name="uninstall_all_blocked_profile_owner" msgid="2009393666026751501">"কিছু ব্যবহারকারী বা প্রোফাইলের জন্য এই অ্যাপটি প্রয়োজন কিন্তু অন্যদের জন্য এটি আনইনস্টল করা হয়ে গেছে"</string> <string name="uninstall_blocked_profile_owner" msgid="6373897407002404848">"এই অ্যাপটি আপনার প্রোফাইলের জন্য প্রয়োজন বলে এটি আনইনস্টল করা যাবে না।"</string> - <string name="uninstall_blocked_device_owner" msgid="6724602931761073901">"এই অ্যাপটি আপনার ডিভাইস অ্যাডমিনিস্ট্রেটরের জন্য প্রয়োজন বলে এটি আনইনস্টল করা যাবে না।"</string> + <string name="uninstall_blocked_device_owner" msgid="6724602931761073901">"অ্যাপটি আপনার ডিভাইস অ্যাডমিনিস্ট্রেটরের প্রয়োজন বলে এটি আনইনস্টল করা যাবে না।"</string> <string name="manage_device_administrators" msgid="3092696419363842816">"ডিভাইস অ্যাডমিন অ্যাপ পরিচালনা করুন"</string> <string name="manage_users" msgid="1243995386982560813">"ব্যবহারকারীদের পরিচালনা করুন"</string> <string name="uninstall_failed_msg" msgid="2176744834786696012">"<xliff:g id="APP_NAME">%1$s</xliff:g> আনইনস্টল করা যায়নি।"</string> @@ -88,4 +88,7 @@ <string name="anonymous_source_continue" msgid="4375745439457209366">"চালিয়ে যান"</string> <string name="external_sources_settings" msgid="4046964413071713807">"সেটিংস"</string> <string name="wear_app_channel" msgid="1960809674709107850">"Wear অ্যাপ ইনস্টল/আনইনস্টল করা হচ্ছে"</string> + <string name="app_installed_notification_channel_description" msgid="2695385797601574123">"\'অ্যাপ ইনস্টল করা হয়েছে\' বিজ্ঞপ্তি"</string> + <string name="notification_installation_success_message" msgid="6450467996056038442">"ইনস্টল করা হয়েছে"</string> + <string name="notification_installation_success_status" msgid="3172502643504323321">"\"<xliff:g id="APPNAME">%1$s</xliff:g>\" ইনস্টল করা হয়েছে"</string> </resources> diff --git a/packages/PackageInstaller/res/values-bs/strings.xml b/packages/PackageInstaller/res/values-bs/strings.xml index 1cb8b372d517..97ba693322e1 100644 --- a/packages/PackageInstaller/res/values-bs/strings.xml +++ b/packages/PackageInstaller/res/values-bs/strings.xml @@ -88,4 +88,7 @@ <string name="anonymous_source_continue" msgid="4375745439457209366">"Nastavi"</string> <string name="external_sources_settings" msgid="4046964413071713807">"Postavke"</string> <string name="wear_app_channel" msgid="1960809674709107850">"Instaliranje/deinstaliranje Wear aplik."</string> + <string name="app_installed_notification_channel_description" msgid="2695385797601574123">"Obavještenje o instaliranoj aplikaciji"</string> + <string name="notification_installation_success_message" msgid="6450467996056038442">"Uspješno instalirano"</string> + <string name="notification_installation_success_status" msgid="3172502643504323321">"Aplikacija \"<xliff:g id="APPNAME">%1$s</xliff:g>\" je uspješno instalirana"</string> </resources> diff --git a/packages/PackageInstaller/res/values-ca/strings.xml b/packages/PackageInstaller/res/values-ca/strings.xml index f9ca13936ef5..a0908081e3cf 100644 --- a/packages/PackageInstaller/res/values-ca/strings.xml +++ b/packages/PackageInstaller/res/values-ca/strings.xml @@ -88,4 +88,7 @@ <string name="anonymous_source_continue" msgid="4375745439457209366">"Continua"</string> <string name="external_sources_settings" msgid="4046964413071713807">"Configuració"</string> <string name="wear_app_channel" msgid="1960809674709107850">"Instal·lant o desinstal·lant apps de Wear"</string> + <string name="app_installed_notification_channel_description" msgid="2695385797601574123">"Notificació d\'aplicació instal·lada"</string> + <string name="notification_installation_success_message" msgid="6450467996056038442">"S\'ha instal·lat correctament"</string> + <string name="notification_installation_success_status" msgid="3172502643504323321">"S\'ha instal·lat <xliff:g id="APPNAME">%1$s</xliff:g> correctament"</string> </resources> diff --git a/packages/PackageInstaller/res/values-cs/strings.xml b/packages/PackageInstaller/res/values-cs/strings.xml index 6f681330dd4e..1e46214b4b0c 100644 --- a/packages/PackageInstaller/res/values-cs/strings.xml +++ b/packages/PackageInstaller/res/values-cs/strings.xml @@ -88,4 +88,7 @@ <string name="anonymous_source_continue" msgid="4375745439457209366">"Pokračovat"</string> <string name="external_sources_settings" msgid="4046964413071713807">"Nastavení"</string> <string name="wear_app_channel" msgid="1960809674709107850">"Instalace/odinstalace aplikací pro Wear"</string> + <string name="app_installed_notification_channel_description" msgid="2695385797601574123">"Oznámení o nainstalované aplikaci"</string> + <string name="notification_installation_success_message" msgid="6450467996056038442">"Úspěšně nainstalováno"</string> + <string name="notification_installation_success_status" msgid="3172502643504323321">"Aplikace <xliff:g id="APPNAME">%1$s</xliff:g> byla úspěšně nainstalována"</string> </resources> diff --git a/packages/PackageInstaller/res/values-da/strings.xml b/packages/PackageInstaller/res/values-da/strings.xml index 4b4034190dcf..d35fb9caa6f1 100644 --- a/packages/PackageInstaller/res/values-da/strings.xml +++ b/packages/PackageInstaller/res/values-da/strings.xml @@ -88,4 +88,7 @@ <string name="anonymous_source_continue" msgid="4375745439457209366">"Fortsæt"</string> <string name="external_sources_settings" msgid="4046964413071713807">"Indstillinger"</string> <string name="wear_app_channel" msgid="1960809674709107850">"Installerer/afinstallerer Wear-apps"</string> + <string name="app_installed_notification_channel_description" msgid="2695385797601574123">"Underretning om appinstallation"</string> + <string name="notification_installation_success_message" msgid="6450467996056038442">"Appen er installeret"</string> + <string name="notification_installation_success_status" msgid="3172502643504323321">"\"<xliff:g id="APPNAME">%1$s</xliff:g>\" er installeret"</string> </resources> diff --git a/packages/PackageInstaller/res/values-de/strings.xml b/packages/PackageInstaller/res/values-de/strings.xml index 27c19b88c1fd..367eb243e1e9 100644 --- a/packages/PackageInstaller/res/values-de/strings.xml +++ b/packages/PackageInstaller/res/values-de/strings.xml @@ -88,4 +88,7 @@ <string name="anonymous_source_continue" msgid="4375745439457209366">"Weiter"</string> <string name="external_sources_settings" msgid="4046964413071713807">"Einstellungen"</string> <string name="wear_app_channel" msgid="1960809674709107850">"Wear-Apps installieren/deinstallieren"</string> + <string name="app_installed_notification_channel_description" msgid="2695385797601574123">"Installationsbenachrichtigung für App"</string> + <string name="notification_installation_success_message" msgid="6450467996056038442">"Erfolgreich installiert"</string> + <string name="notification_installation_success_status" msgid="3172502643504323321">"\"<xliff:g id="APPNAME">%1$s</xliff:g>\" wurde erfolgreich installiert"</string> </resources> diff --git a/packages/PackageInstaller/res/values-el/strings.xml b/packages/PackageInstaller/res/values-el/strings.xml index fea35564d0c6..d179acb0dbb9 100644 --- a/packages/PackageInstaller/res/values-el/strings.xml +++ b/packages/PackageInstaller/res/values-el/strings.xml @@ -88,4 +88,7 @@ <string name="anonymous_source_continue" msgid="4375745439457209366">"Συνέχεια"</string> <string name="external_sources_settings" msgid="4046964413071713807">"Ρυθμίσεις"</string> <string name="wear_app_channel" msgid="1960809674709107850">"Εγκατάσταση/απεγκατάσταση εφαρμογών Wear"</string> + <string name="app_installed_notification_channel_description" msgid="2695385797601574123">"Ειδοποίηση εγκατάστασης εφαρμογής"</string> + <string name="notification_installation_success_message" msgid="6450467996056038442">"Εγκαταστάθηκε επιτυχώς"</string> + <string name="notification_installation_success_status" msgid="3172502643504323321">"Η εφαρμογή \"<xliff:g id="APPNAME">%1$s</xliff:g>\" εγκαταστάθηκε επιτυχώς"</string> </resources> diff --git a/packages/PackageInstaller/res/values-en-rAU/strings.xml b/packages/PackageInstaller/res/values-en-rAU/strings.xml index ff926ac70c0b..4016288a420d 100644 --- a/packages/PackageInstaller/res/values-en-rAU/strings.xml +++ b/packages/PackageInstaller/res/values-en-rAU/strings.xml @@ -88,4 +88,7 @@ <string name="anonymous_source_continue" msgid="4375745439457209366">"Continue"</string> <string name="external_sources_settings" msgid="4046964413071713807">"Settings"</string> <string name="wear_app_channel" msgid="1960809674709107850">"Installing/uninstalling Wear apps"</string> + <string name="app_installed_notification_channel_description" msgid="2695385797601574123">"App installed notification"</string> + <string name="notification_installation_success_message" msgid="6450467996056038442">"Successfully installed"</string> + <string name="notification_installation_success_status" msgid="3172502643504323321">"Successfully installed \'<xliff:g id="APPNAME">%1$s</xliff:g>\'"</string> </resources> diff --git a/packages/PackageInstaller/res/values-en-rCA/strings.xml b/packages/PackageInstaller/res/values-en-rCA/strings.xml index ff926ac70c0b..4016288a420d 100644 --- a/packages/PackageInstaller/res/values-en-rCA/strings.xml +++ b/packages/PackageInstaller/res/values-en-rCA/strings.xml @@ -88,4 +88,7 @@ <string name="anonymous_source_continue" msgid="4375745439457209366">"Continue"</string> <string name="external_sources_settings" msgid="4046964413071713807">"Settings"</string> <string name="wear_app_channel" msgid="1960809674709107850">"Installing/uninstalling Wear apps"</string> + <string name="app_installed_notification_channel_description" msgid="2695385797601574123">"App installed notification"</string> + <string name="notification_installation_success_message" msgid="6450467996056038442">"Successfully installed"</string> + <string name="notification_installation_success_status" msgid="3172502643504323321">"Successfully installed \'<xliff:g id="APPNAME">%1$s</xliff:g>\'"</string> </resources> diff --git a/packages/PackageInstaller/res/values-en-rGB/strings.xml b/packages/PackageInstaller/res/values-en-rGB/strings.xml index ff926ac70c0b..4016288a420d 100644 --- a/packages/PackageInstaller/res/values-en-rGB/strings.xml +++ b/packages/PackageInstaller/res/values-en-rGB/strings.xml @@ -88,4 +88,7 @@ <string name="anonymous_source_continue" msgid="4375745439457209366">"Continue"</string> <string name="external_sources_settings" msgid="4046964413071713807">"Settings"</string> <string name="wear_app_channel" msgid="1960809674709107850">"Installing/uninstalling Wear apps"</string> + <string name="app_installed_notification_channel_description" msgid="2695385797601574123">"App installed notification"</string> + <string name="notification_installation_success_message" msgid="6450467996056038442">"Successfully installed"</string> + <string name="notification_installation_success_status" msgid="3172502643504323321">"Successfully installed \'<xliff:g id="APPNAME">%1$s</xliff:g>\'"</string> </resources> diff --git a/packages/PackageInstaller/res/values-en-rIN/strings.xml b/packages/PackageInstaller/res/values-en-rIN/strings.xml index ff926ac70c0b..4016288a420d 100644 --- a/packages/PackageInstaller/res/values-en-rIN/strings.xml +++ b/packages/PackageInstaller/res/values-en-rIN/strings.xml @@ -88,4 +88,7 @@ <string name="anonymous_source_continue" msgid="4375745439457209366">"Continue"</string> <string name="external_sources_settings" msgid="4046964413071713807">"Settings"</string> <string name="wear_app_channel" msgid="1960809674709107850">"Installing/uninstalling Wear apps"</string> + <string name="app_installed_notification_channel_description" msgid="2695385797601574123">"App installed notification"</string> + <string name="notification_installation_success_message" msgid="6450467996056038442">"Successfully installed"</string> + <string name="notification_installation_success_status" msgid="3172502643504323321">"Successfully installed \'<xliff:g id="APPNAME">%1$s</xliff:g>\'"</string> </resources> diff --git a/packages/PackageInstaller/res/values-en-rXC/strings.xml b/packages/PackageInstaller/res/values-en-rXC/strings.xml index 1dc8cee3815a..68332630acff 100644 --- a/packages/PackageInstaller/res/values-en-rXC/strings.xml +++ b/packages/PackageInstaller/res/values-en-rXC/strings.xml @@ -88,4 +88,7 @@ <string name="anonymous_source_continue" msgid="4375745439457209366">"Continue"</string> <string name="external_sources_settings" msgid="4046964413071713807">"Settings"</string> <string name="wear_app_channel" msgid="1960809674709107850">"Installing/uninstalling wear apps"</string> + <string name="app_installed_notification_channel_description" msgid="2695385797601574123">"App installed notification"</string> + <string name="notification_installation_success_message" msgid="6450467996056038442">"Successfully installed"</string> + <string name="notification_installation_success_status" msgid="3172502643504323321">"Successfully installed “<xliff:g id="APPNAME">%1$s</xliff:g>”"</string> </resources> diff --git a/packages/PackageInstaller/res/values-es-rUS/strings.xml b/packages/PackageInstaller/res/values-es-rUS/strings.xml index 887f380bfe7e..dd45a791040c 100644 --- a/packages/PackageInstaller/res/values-es-rUS/strings.xml +++ b/packages/PackageInstaller/res/values-es-rUS/strings.xml @@ -88,4 +88,7 @@ <string name="anonymous_source_continue" msgid="4375745439457209366">"Continuar"</string> <string name="external_sources_settings" msgid="4046964413071713807">"Configuración"</string> <string name="wear_app_channel" msgid="1960809674709107850">"Instalando/desinstalando apps para Wear"</string> + <string name="app_installed_notification_channel_description" msgid="2695385797601574123">"Notificación de app instalada"</string> + <string name="notification_installation_success_message" msgid="6450467996056038442">"Se instaló correctamente"</string> + <string name="notification_installation_success_status" msgid="3172502643504323321">"Se instaló correctamente \"<xliff:g id="APPNAME">%1$s</xliff:g>\""</string> </resources> diff --git a/packages/PackageInstaller/res/values-es/strings.xml b/packages/PackageInstaller/res/values-es/strings.xml index 26203b039b74..7aef3333c13a 100644 --- a/packages/PackageInstaller/res/values-es/strings.xml +++ b/packages/PackageInstaller/res/values-es/strings.xml @@ -88,4 +88,7 @@ <string name="anonymous_source_continue" msgid="4375745439457209366">"Continuar"</string> <string name="external_sources_settings" msgid="4046964413071713807">"Ajustes"</string> <string name="wear_app_channel" msgid="1960809674709107850">"Instalando/desinstalando apps para Wear"</string> + <string name="app_installed_notification_channel_description" msgid="2695385797601574123">"Notificación de aplicación instalada"</string> + <string name="notification_installation_success_message" msgid="6450467996056038442">"Se ha instalado correctamente"</string> + <string name="notification_installation_success_status" msgid="3172502643504323321">"<xliff:g id="APPNAME">%1$s</xliff:g> se ha instalado correctamente"</string> </resources> diff --git a/packages/PackageInstaller/res/values-et/strings.xml b/packages/PackageInstaller/res/values-et/strings.xml index cf9dd568e693..873ac48343ed 100644 --- a/packages/PackageInstaller/res/values-et/strings.xml +++ b/packages/PackageInstaller/res/values-et/strings.xml @@ -88,4 +88,7 @@ <string name="anonymous_source_continue" msgid="4375745439457209366">"Jätka"</string> <string name="external_sources_settings" msgid="4046964413071713807">"Seaded"</string> <string name="wear_app_channel" msgid="1960809674709107850">"Weari rak. installimine/desinstallimine"</string> + <string name="app_installed_notification_channel_description" msgid="2695385797601574123">"Rakenduse installimise märguanne"</string> + <string name="notification_installation_success_message" msgid="6450467996056038442">"Installimine õnnestus"</string> + <string name="notification_installation_success_status" msgid="3172502643504323321">"Rakenduse „<xliff:g id="APPNAME">%1$s</xliff:g>” installimine õnnestus"</string> </resources> diff --git a/packages/PackageInstaller/res/values-eu/strings.xml b/packages/PackageInstaller/res/values-eu/strings.xml index a73a99c1bbdb..6e79ac86a957 100644 --- a/packages/PackageInstaller/res/values-eu/strings.xml +++ b/packages/PackageInstaller/res/values-eu/strings.xml @@ -54,10 +54,10 @@ <string name="uninstall_update_title" msgid="824411791011583031">"Desinstalatu eguneratzea"</string> <string name="uninstall_activity_text" msgid="1928194674397770771">"<xliff:g id="ACTIVITY_NAME">%1$s</xliff:g> aplikazio honen zati da:"</string> <string name="uninstall_application_text" msgid="3816830743706143980">"Aplikazioa desinstalatu nahi duzu?"</string> - <string name="uninstall_application_text_all_users" msgid="575491774380227119">"Erabiltzaile "<b>"guztiei"</b>" desinstalatu nahi diezu aplikazioa? Aplikazioa eta bere datu guztiak ezabatuko zaizkie gailuko erabiltzaile "<b>"guztiei"</b>"."</string> + <string name="uninstall_application_text_all_users" msgid="575491774380227119">"Erabiltzaile "<b>"guztiei"</b>" desinstalatu nahi diezu aplikazioa? Aplikazioa eta haren datu guztiak ezabatuko zaizkie gailuko erabiltzaile "<b>"guztiei"</b>"."</string> <string name="uninstall_application_text_user" msgid="498072714173920526">"<xliff:g id="USERNAME">%1$s</xliff:g> erabiltzaileari desinstalatu nahi diozu aplikazioa?"</string> <string name="uninstall_update_text" msgid="863648314632448705">"Aplikazio hau jatorrizko bertsioarekin ordeztu nahi duzu? Datu guztiak ezabatuko dira."</string> - <string name="uninstall_update_text_multiuser" msgid="8992883151333057227">"Aplikazio hau jatorrizko bertsioarekin ordeztu nahi duzu? Datu guztiak ezabatuko dira. Gailuaren erabiltzaile guztiengan izango du eragina, laneko profilak dituztenengan barne."</string> + <string name="uninstall_update_text_multiuser" msgid="8992883151333057227">"Aplikazio hau jatorrizko bertsioarekin ordeztu nahi duzu? Datu guztiak ezabatuko dira. Gailuaren erabiltzaile guztiengan izango du eragina, laneko profilak dituztenak barne."</string> <string name="uninstalling_notification_channel" msgid="840153394325714653">"Abian diren desinstalatze-eragiketak"</string> <string name="uninstall_failure_notification_channel" msgid="1136405866767576588">"Desinstalatu ezin izan direnak"</string> <string name="uninstalling" msgid="8709566347688966845">"Desinstalatzen…"</string> @@ -88,4 +88,7 @@ <string name="anonymous_source_continue" msgid="4375745439457209366">"Egin aurrera"</string> <string name="external_sources_settings" msgid="4046964413071713807">"Ezarpenak"</string> <string name="wear_app_channel" msgid="1960809674709107850">"Wear aplikazioak instalatzea/desinstalatzea"</string> + <string name="app_installed_notification_channel_description" msgid="2695385797601574123">"Aplikazioa instalatu izanaren jakinarazpena"</string> + <string name="notification_installation_success_message" msgid="6450467996056038442">"Instalatu da"</string> + <string name="notification_installation_success_status" msgid="3172502643504323321">"Instalatu da \"<xliff:g id="APPNAME">%1$s</xliff:g>\""</string> </resources> diff --git a/packages/PackageInstaller/res/values-fa/strings.xml b/packages/PackageInstaller/res/values-fa/strings.xml index be685a95a8bd..7a36387c968c 100644 --- a/packages/PackageInstaller/res/values-fa/strings.xml +++ b/packages/PackageInstaller/res/values-fa/strings.xml @@ -88,4 +88,7 @@ <string name="anonymous_source_continue" msgid="4375745439457209366">"ادامه"</string> <string name="external_sources_settings" msgid="4046964413071713807">"تنظیمات"</string> <string name="wear_app_channel" msgid="1960809674709107850">"نصب/حذف نصب برنامههای پوشیدنی"</string> + <string name="app_installed_notification_channel_description" msgid="2695385797601574123">"اعلان نصب برنامه"</string> + <string name="notification_installation_success_message" msgid="6450467996056038442">"نصب موفقیتآمیز بود"</string> + <string name="notification_installation_success_status" msgid="3172502643504323321">"<xliff:g id="APPNAME">%1$s</xliff:g> باموفقیت نصب شد"</string> </resources> diff --git a/packages/PackageInstaller/res/values-fi/strings.xml b/packages/PackageInstaller/res/values-fi/strings.xml index df73ed77f08d..b73c50b8f370 100644 --- a/packages/PackageInstaller/res/values-fi/strings.xml +++ b/packages/PackageInstaller/res/values-fi/strings.xml @@ -88,4 +88,7 @@ <string name="anonymous_source_continue" msgid="4375745439457209366">"Jatka"</string> <string name="external_sources_settings" msgid="4046964413071713807">"Asetukset"</string> <string name="wear_app_channel" msgid="1960809674709107850">"Wear-sovellusten asennus/poistaminen"</string> + <string name="app_installed_notification_channel_description" msgid="2695385797601574123">"Sovellus asennettu ‑ilmoitus"</string> + <string name="notification_installation_success_message" msgid="6450467996056038442">"Asennus onnistui"</string> + <string name="notification_installation_success_status" msgid="3172502643504323321">"<xliff:g id="APPNAME">%1$s</xliff:g>: asennus onnistui"</string> </resources> diff --git a/packages/PackageInstaller/res/values-fr-rCA/strings.xml b/packages/PackageInstaller/res/values-fr-rCA/strings.xml index 809d20c38fab..a6cd289223ee 100644 --- a/packages/PackageInstaller/res/values-fr-rCA/strings.xml +++ b/packages/PackageInstaller/res/values-fr-rCA/strings.xml @@ -88,4 +88,7 @@ <string name="anonymous_source_continue" msgid="4375745439457209366">"Continuer"</string> <string name="external_sources_settings" msgid="4046964413071713807">"Paramètres"</string> <string name="wear_app_channel" msgid="1960809674709107850">"Installer/désinstaller applis Google Wear"</string> + <string name="app_installed_notification_channel_description" msgid="2695385797601574123">"Notification d\'application installée"</string> + <string name="notification_installation_success_message" msgid="6450467996056038442">"Installation réussie"</string> + <string name="notification_installation_success_status" msgid="3172502643504323321">"Installation de « <xliff:g id="APPNAME">%1$s</xliff:g> » réussie"</string> </resources> diff --git a/packages/PackageInstaller/res/values-fr/strings.xml b/packages/PackageInstaller/res/values-fr/strings.xml index 6bbd7e88a665..d36322ad065c 100644 --- a/packages/PackageInstaller/res/values-fr/strings.xml +++ b/packages/PackageInstaller/res/values-fr/strings.xml @@ -88,4 +88,7 @@ <string name="anonymous_source_continue" msgid="4375745439457209366">"Continuer"</string> <string name="external_sources_settings" msgid="4046964413071713807">"Paramètres"</string> <string name="wear_app_channel" msgid="1960809674709107850">"Installer/Désinstaller les applis Wear"</string> + <string name="app_installed_notification_channel_description" msgid="2695385797601574123">"Notification d\'application installée"</string> + <string name="notification_installation_success_message" msgid="6450467996056038442">"L\'application a été installée"</string> + <string name="notification_installation_success_status" msgid="3172502643504323321">"L\'application <xliff:g id="APPNAME">%1$s</xliff:g> a bien été installée"</string> </resources> diff --git a/packages/PackageInstaller/res/values-gl/strings.xml b/packages/PackageInstaller/res/values-gl/strings.xml index 4f2841103678..a7b42789cf3a 100644 --- a/packages/PackageInstaller/res/values-gl/strings.xml +++ b/packages/PackageInstaller/res/values-gl/strings.xml @@ -88,4 +88,7 @@ <string name="anonymous_source_continue" msgid="4375745439457209366">"Continuar"</string> <string name="external_sources_settings" msgid="4046964413071713807">"Configuración"</string> <string name="wear_app_channel" msgid="1960809674709107850">"Instalando/desinstalando apps para Wear"</string> + <string name="app_installed_notification_channel_description" msgid="2695385797601574123">"Notificación da aplicación instalada"</string> + <string name="notification_installation_success_message" msgid="6450467996056038442">"Instalouse correctamente"</string> + <string name="notification_installation_success_status" msgid="3172502643504323321">"Instalouse correctamente a aplicación <xliff:g id="APPNAME">%1$s</xliff:g>"</string> </resources> diff --git a/packages/PackageInstaller/res/values-gu/strings.xml b/packages/PackageInstaller/res/values-gu/strings.xml index 4aa04d3a21db..66e6fd166a8c 100644 --- a/packages/PackageInstaller/res/values-gu/strings.xml +++ b/packages/PackageInstaller/res/values-gu/strings.xml @@ -88,4 +88,7 @@ <string name="anonymous_source_continue" msgid="4375745439457209366">"આગળ વધો"</string> <string name="external_sources_settings" msgid="4046964413071713807">"સેટિંગ"</string> <string name="wear_app_channel" msgid="1960809674709107850">"એમ્બેડ ઍપ્લિકેશનો ઇન્સ્ટૉલ/અનઇન્સ્ટૉલ"</string> + <string name="app_installed_notification_channel_description" msgid="2695385797601574123">"ઍપ ઇન્સ્ટૉલ થવાનું નોટિફિકેશન"</string> + <string name="notification_installation_success_message" msgid="6450467996056038442">"સફળતાપૂર્વક ઇન્સ્ટૉલ કરેલ"</string> + <string name="notification_installation_success_status" msgid="3172502643504323321">"“<xliff:g id="APPNAME">%1$s</xliff:g>” સફળતાપૂર્વક ઇન્સ્ટૉલ કરેલ"</string> </resources> diff --git a/packages/PackageInstaller/res/values-hi/strings.xml b/packages/PackageInstaller/res/values-hi/strings.xml index 0a51a0011171..3c6031c92ad8 100644 --- a/packages/PackageInstaller/res/values-hi/strings.xml +++ b/packages/PackageInstaller/res/values-hi/strings.xml @@ -88,4 +88,7 @@ <string name="anonymous_source_continue" msgid="4375745439457209366">"जारी रखें"</string> <string name="external_sources_settings" msgid="4046964413071713807">"सेटिंग"</string> <string name="wear_app_channel" msgid="1960809674709107850">"Wear ऐप्लिकेशन इंस्टॉल/अनइंस्टॉल हो रहे हैं"</string> + <string name="app_installed_notification_channel_description" msgid="2695385797601574123">"ऐप्लिकेशन इंस्टॉल होने की सूचना"</string> + <string name="notification_installation_success_message" msgid="6450467996056038442">"पूरी तरह से इंस्टॉल हो गया है"</string> + <string name="notification_installation_success_status" msgid="3172502643504323321">"“<xliff:g id="APPNAME">%1$s</xliff:g>” पूरी तरह से इंस्टॉल हो गया है"</string> </resources> diff --git a/packages/PackageInstaller/res/values-hr/strings.xml b/packages/PackageInstaller/res/values-hr/strings.xml index 3169f3a42fbf..380df4a67327 100644 --- a/packages/PackageInstaller/res/values-hr/strings.xml +++ b/packages/PackageInstaller/res/values-hr/strings.xml @@ -88,4 +88,7 @@ <string name="anonymous_source_continue" msgid="4375745439457209366">"Nastavi"</string> <string name="external_sources_settings" msgid="4046964413071713807">"Postavke"</string> <string name="wear_app_channel" msgid="1960809674709107850">"Instaliranje/deinstaliranje Wear apl."</string> + <string name="app_installed_notification_channel_description" msgid="2695385797601574123">"Obavijest o instaliranoj aplikaciji"</string> + <string name="notification_installation_success_message" msgid="6450467996056038442">"Uspješno instalirano"</string> + <string name="notification_installation_success_status" msgid="3172502643504323321">"Aplikacija \"<xliff:g id="APPNAME">%1$s</xliff:g>\" uspješno je instalirana"</string> </resources> diff --git a/packages/PackageInstaller/res/values-hu/strings.xml b/packages/PackageInstaller/res/values-hu/strings.xml index 1971ea76369b..9cbff8b831c6 100644 --- a/packages/PackageInstaller/res/values-hu/strings.xml +++ b/packages/PackageInstaller/res/values-hu/strings.xml @@ -88,4 +88,7 @@ <string name="anonymous_source_continue" msgid="4375745439457209366">"Tovább"</string> <string name="external_sources_settings" msgid="4046964413071713807">"Beállítások"</string> <string name="wear_app_channel" msgid="1960809674709107850">"Wear-alkalmazások telepítése/törlése"</string> + <string name="app_installed_notification_channel_description" msgid="2695385797601574123">"Alkalmazás telepítve értesítés"</string> + <string name="notification_installation_success_message" msgid="6450467996056038442">"Sikeresen telepítve"</string> + <string name="notification_installation_success_status" msgid="3172502643504323321">"A(z) <xliff:g id="APPNAME">%1$s</xliff:g> alkalmazás sikeresen telepítve"</string> </resources> diff --git a/packages/PackageInstaller/res/values-hy/strings.xml b/packages/PackageInstaller/res/values-hy/strings.xml index 4892ddd0bc71..2e2c8559bff8 100644 --- a/packages/PackageInstaller/res/values-hy/strings.xml +++ b/packages/PackageInstaller/res/values-hy/strings.xml @@ -51,7 +51,7 @@ <string name="generic_error_dlg_title" msgid="5863195085927067752">"Սխալ"</string> <string name="generic_error_dlg_text" msgid="5287861443265795232">"Չհաջողվեց ապատեղադրել հավելվածը:"</string> <string name="uninstall_application_title" msgid="4045420072401428123">"Հավելվածի ապատեղադրում"</string> - <string name="uninstall_update_title" msgid="824411791011583031">"Թարմացման ապատեղադրում"</string> + <string name="uninstall_update_title" msgid="824411791011583031">"Ապատեղադրել թարմացումը"</string> <string name="uninstall_activity_text" msgid="1928194674397770771">"<xliff:g id="ACTIVITY_NAME">%1$s</xliff:g> գործողությունը հետևյալ հավելվածի մասն է`"</string> <string name="uninstall_application_text" msgid="3816830743706143980">"Ուզո՞ւմ եք ապատեղադրել այս հավելվածը։"</string> <string name="uninstall_application_text_all_users" msgid="575491774380227119">"Ապատեղադրե՞լ այս հավելվածը "<b>"բոլոր"</b>" օգտատերերի համար: Հավելվածը և դրա տվյալները կհեռացվեն սարքի "<b>"բոլոր"</b>" օգտատերերից:"</string> @@ -88,4 +88,7 @@ <string name="anonymous_source_continue" msgid="4375745439457209366">"Շարունակել"</string> <string name="external_sources_settings" msgid="4046964413071713807">"Կարգավորումներ"</string> <string name="wear_app_channel" msgid="1960809674709107850">"Wear հավելվածների տեղադրում/ապատեղադրում"</string> + <string name="app_installed_notification_channel_description" msgid="2695385797601574123">"Ծանուցում հավելվածի տեղադրման մասին"</string> + <string name="notification_installation_success_message" msgid="6450467996056038442">"Տեղադրվեց"</string> + <string name="notification_installation_success_status" msgid="3172502643504323321">"<xliff:g id="APPNAME">%1$s</xliff:g> հավելվածը տեղադրվեց"</string> </resources> diff --git a/packages/PackageInstaller/res/values-in/strings.xml b/packages/PackageInstaller/res/values-in/strings.xml index 212eeb13b223..fadff6148bc5 100644 --- a/packages/PackageInstaller/res/values-in/strings.xml +++ b/packages/PackageInstaller/res/values-in/strings.xml @@ -88,4 +88,7 @@ <string name="anonymous_source_continue" msgid="4375745439457209366">"Lanjutkan"</string> <string name="external_sources_settings" msgid="4046964413071713807">"Setelan"</string> <string name="wear_app_channel" msgid="1960809674709107850">"Melakukan instal/uninstal aplikasi Wear"</string> + <string name="app_installed_notification_channel_description" msgid="2695385797601574123">"Notifikasi penginstalan aplikasi"</string> + <string name="notification_installation_success_message" msgid="6450467996056038442">"Berhasil diinstal"</string> + <string name="notification_installation_success_status" msgid="3172502643504323321">"Berhasil menginstal “<xliff:g id="APPNAME">%1$s</xliff:g>”"</string> </resources> diff --git a/packages/PackageInstaller/res/values-is/strings.xml b/packages/PackageInstaller/res/values-is/strings.xml index 6921b7b38df8..a50714fe3be6 100644 --- a/packages/PackageInstaller/res/values-is/strings.xml +++ b/packages/PackageInstaller/res/values-is/strings.xml @@ -88,4 +88,7 @@ <string name="anonymous_source_continue" msgid="4375745439457209366">"Áfram"</string> <string name="external_sources_settings" msgid="4046964413071713807">"Stillingar"</string> <string name="wear_app_channel" msgid="1960809674709107850">"Uppsetning/fjarlæging Wear forrita"</string> + <string name="app_installed_notification_channel_description" msgid="2695385797601574123">"Tilkynning um uppsett forrit"</string> + <string name="notification_installation_success_message" msgid="6450467996056038442">"Uppsetning tókst"</string> + <string name="notification_installation_success_status" msgid="3172502643504323321">"„<xliff:g id="APPNAME">%1$s</xliff:g>“ sett upp"</string> </resources> diff --git a/packages/PackageInstaller/res/values-it/strings.xml b/packages/PackageInstaller/res/values-it/strings.xml index b44d6ea24d72..5ca7edafb30d 100644 --- a/packages/PackageInstaller/res/values-it/strings.xml +++ b/packages/PackageInstaller/res/values-it/strings.xml @@ -88,4 +88,7 @@ <string name="anonymous_source_continue" msgid="4375745439457209366">"Continua"</string> <string name="external_sources_settings" msgid="4046964413071713807">"Impostazioni"</string> <string name="wear_app_channel" msgid="1960809674709107850">"Installazione/disinstallazione app Wear"</string> + <string name="app_installed_notification_channel_description" msgid="2695385797601574123">"Notifica relativa alle app installate"</string> + <string name="notification_installation_success_message" msgid="6450467996056038442">"Installata"</string> + <string name="notification_installation_success_status" msgid="3172502643504323321">"App \"<xliff:g id="APPNAME">%1$s</xliff:g>\" installata"</string> </resources> diff --git a/packages/PackageInstaller/res/values-iw/strings.xml b/packages/PackageInstaller/res/values-iw/strings.xml index 573f12a3d7e4..cd94cdaeae9b 100644 --- a/packages/PackageInstaller/res/values-iw/strings.xml +++ b/packages/PackageInstaller/res/values-iw/strings.xml @@ -88,4 +88,7 @@ <string name="anonymous_source_continue" msgid="4375745439457209366">"המשך"</string> <string name="external_sources_settings" msgid="4046964413071713807">"הגדרות"</string> <string name="wear_app_channel" msgid="1960809674709107850">"מתקין/מסיר התקנה של אפליקציות Wear"</string> + <string name="app_installed_notification_channel_description" msgid="2695385797601574123">"התראה על התקנת האפליקציה"</string> + <string name="notification_installation_success_message" msgid="6450467996056038442">"הותקנה בהצלחה"</string> + <string name="notification_installation_success_status" msgid="3172502643504323321">"האפליקציה \"<xliff:g id="APPNAME">%1$s</xliff:g>\" הותקנה בהצלחה"</string> </resources> diff --git a/packages/PackageInstaller/res/values-ja/strings.xml b/packages/PackageInstaller/res/values-ja/strings.xml index 42f1b3f66133..7496016e41ea 100644 --- a/packages/PackageInstaller/res/values-ja/strings.xml +++ b/packages/PackageInstaller/res/values-ja/strings.xml @@ -88,4 +88,7 @@ <string name="anonymous_source_continue" msgid="4375745439457209366">"次へ"</string> <string name="external_sources_settings" msgid="4046964413071713807">"設定"</string> <string name="wear_app_channel" msgid="1960809674709107850">"Wearアプリ インストール/アンインストール"</string> + <string name="app_installed_notification_channel_description" msgid="2695385797601574123">"アプリのインストールに関する通知"</string> + <string name="notification_installation_success_message" msgid="6450467996056038442">"インストールしました"</string> + <string name="notification_installation_success_status" msgid="3172502643504323321">"「<xliff:g id="APPNAME">%1$s</xliff:g>」をインストールしました"</string> </resources> diff --git a/packages/PackageInstaller/res/values-ka/strings.xml b/packages/PackageInstaller/res/values-ka/strings.xml index 4b2ae75067d1..aac14dca4d16 100644 --- a/packages/PackageInstaller/res/values-ka/strings.xml +++ b/packages/PackageInstaller/res/values-ka/strings.xml @@ -88,4 +88,7 @@ <string name="anonymous_source_continue" msgid="4375745439457209366">"გაგრძელება"</string> <string name="external_sources_settings" msgid="4046964413071713807">"პარამეტრები"</string> <string name="wear_app_channel" msgid="1960809674709107850">"Wear აპების ინსტალაცია/დეინსტალაცია"</string> + <string name="app_installed_notification_channel_description" msgid="2695385797601574123">"შეტყობინება აპის ინსტალაციის შესახებ"</string> + <string name="notification_installation_success_message" msgid="6450467996056038442">"წარმატებით დაინსტალირდა"</string> + <string name="notification_installation_success_status" msgid="3172502643504323321">"„<xliff:g id="APPNAME">%1$s</xliff:g>“ წარმატებით დაინსტალირდა"</string> </resources> diff --git a/packages/PackageInstaller/res/values-kk/strings.xml b/packages/PackageInstaller/res/values-kk/strings.xml index 708411c4a575..a105a005f00f 100644 --- a/packages/PackageInstaller/res/values-kk/strings.xml +++ b/packages/PackageInstaller/res/values-kk/strings.xml @@ -88,4 +88,7 @@ <string name="anonymous_source_continue" msgid="4375745439457209366">"Жалғастыру"</string> <string name="external_sources_settings" msgid="4046964413071713807">"Параметрлер"</string> <string name="wear_app_channel" msgid="1960809674709107850">"Wear қолданбасын орнату/жою"</string> + <string name="app_installed_notification_channel_description" msgid="2695385797601574123">"Қолданба орнатылғаны туралы хабарландыру"</string> + <string name="notification_installation_success_message" msgid="6450467996056038442">"Орнатылды"</string> + <string name="notification_installation_success_status" msgid="3172502643504323321">"\"<xliff:g id="APPNAME">%1$s</xliff:g>\" орнатылды"</string> </resources> diff --git a/packages/PackageInstaller/res/values-km/strings.xml b/packages/PackageInstaller/res/values-km/strings.xml index 78b04a01c0aa..ce0db9108813 100644 --- a/packages/PackageInstaller/res/values-km/strings.xml +++ b/packages/PackageInstaller/res/values-km/strings.xml @@ -88,4 +88,7 @@ <string name="anonymous_source_continue" msgid="4375745439457209366">"បន្ត"</string> <string name="external_sources_settings" msgid="4046964413071713807">"ការកំណត់"</string> <string name="wear_app_channel" msgid="1960809674709107850">"ការដំឡើង/ការលុបកម្មវិធីឧបករណ៍ពាក់"</string> + <string name="app_installed_notification_channel_description" msgid="2695385797601574123">"ការជូនដំណឹងអំពីកម្មវិធីដែលបានដំឡើង"</string> + <string name="notification_installation_success_message" msgid="6450467996056038442">"បានដំឡើងដោយជោគជ័យហើយ"</string> + <string name="notification_installation_success_status" msgid="3172502643504323321">"បានដំឡើង \"<xliff:g id="APPNAME">%1$s</xliff:g>\" ដោយជោគជ័យហើយ"</string> </resources> diff --git a/packages/PackageInstaller/res/values-kn/strings.xml b/packages/PackageInstaller/res/values-kn/strings.xml index 5b9698ad1266..4c5373eb010b 100644 --- a/packages/PackageInstaller/res/values-kn/strings.xml +++ b/packages/PackageInstaller/res/values-kn/strings.xml @@ -88,4 +88,7 @@ <string name="anonymous_source_continue" msgid="4375745439457209366">"ಮುಂದುವರಿಸಿ"</string> <string name="external_sources_settings" msgid="4046964413071713807">"ಸೆಟ್ಟಿಂಗ್ಗಳು"</string> <string name="wear_app_channel" msgid="1960809674709107850">"wear ಆ್ಯಪ್ಗಳನ್ನು ಇನ್ಸ್ಟಾಲ್/ಅನ್ಇನ್ಸ್ಟಾಲ್ ಮಾಡಲಾಗುತ್ತಿದೆ"</string> + <string name="app_installed_notification_channel_description" msgid="2695385797601574123">"ಆ್ಯಪ್ ಇನ್ಸ್ಟಾಲ್ ಮಾಡಿರುವ ಕುರಿತು ಅಧಿಸೂಚನೆ"</string> + <string name="notification_installation_success_message" msgid="6450467996056038442">"ಯಶಸ್ವಿಯಾಗಿ ಇನ್ಸ್ಟಾಲ್ ಆಗಿದೆ"</string> + <string name="notification_installation_success_status" msgid="3172502643504323321">"\"<xliff:g id="APPNAME">%1$s</xliff:g>\" ಆ್ಯಪ್ ಅನ್ನು ಯಶಸ್ವಿಯಾಗಿ ಇನ್ಸ್ಟಾಲ್ ಮಾಡಲಾಗಿದೆ"</string> </resources> diff --git a/packages/PackageInstaller/res/values-ko/strings.xml b/packages/PackageInstaller/res/values-ko/strings.xml index 50f81578b8b4..1741b6e813e9 100644 --- a/packages/PackageInstaller/res/values-ko/strings.xml +++ b/packages/PackageInstaller/res/values-ko/strings.xml @@ -88,4 +88,7 @@ <string name="anonymous_source_continue" msgid="4375745439457209366">"계속"</string> <string name="external_sources_settings" msgid="4046964413071713807">"설정"</string> <string name="wear_app_channel" msgid="1960809674709107850">"Wear 앱 설치/제거"</string> + <string name="app_installed_notification_channel_description" msgid="2695385797601574123">"앱 설치 완료 알림"</string> + <string name="notification_installation_success_message" msgid="6450467996056038442">"설치 완료"</string> + <string name="notification_installation_success_status" msgid="3172502643504323321">"\'<xliff:g id="APPNAME">%1$s</xliff:g>\' 설치 완료"</string> </resources> diff --git a/packages/PackageInstaller/res/values-ky/strings.xml b/packages/PackageInstaller/res/values-ky/strings.xml index 94da72f365a3..6b4abb9b898e 100644 --- a/packages/PackageInstaller/res/values-ky/strings.xml +++ b/packages/PackageInstaller/res/values-ky/strings.xml @@ -88,4 +88,7 @@ <string name="anonymous_source_continue" msgid="4375745439457209366">"Улантуу"</string> <string name="external_sources_settings" msgid="4046964413071713807">"Жөндөөлөр"</string> <string name="wear_app_channel" msgid="1960809674709107850">"Тагынма колдонмолорду орнотуу/чыгаруу"</string> + <string name="app_installed_notification_channel_description" msgid="2695385797601574123">"Колдонмолорду орноткучтун билдирмелери"</string> + <string name="notification_installation_success_message" msgid="6450467996056038442">"Ийгиликтүү орнотулду"</string> + <string name="notification_installation_success_status" msgid="3172502643504323321">"\"<xliff:g id="APPNAME">%1$s</xliff:g>\" орнотулду"</string> </resources> diff --git a/packages/PackageInstaller/res/values-lo/strings.xml b/packages/PackageInstaller/res/values-lo/strings.xml index dba0dcc83cde..aa55e3cb3200 100644 --- a/packages/PackageInstaller/res/values-lo/strings.xml +++ b/packages/PackageInstaller/res/values-lo/strings.xml @@ -88,4 +88,7 @@ <string name="anonymous_source_continue" msgid="4375745439457209366">"ສືບຕໍ່"</string> <string name="external_sources_settings" msgid="4046964413071713807">"ການຕັ້ງຄ່າ"</string> <string name="wear_app_channel" msgid="1960809674709107850">"ການຕິດຕັ້ງ/ຖອນການຕິດຕັ້ງແອັບ Wear"</string> + <string name="app_installed_notification_channel_description" msgid="2695385797601574123">"ການແຈ້ງເຕືອນແອັບທີ່ຕິດຕັ້ງແລ້ວ"</string> + <string name="notification_installation_success_message" msgid="6450467996056038442">"ຕິດຕັ້ງສຳເລັດແລ້ວ"</string> + <string name="notification_installation_success_status" msgid="3172502643504323321">"ຕິດຕັ້ງ \"<xliff:g id="APPNAME">%1$s</xliff:g>\" ສຳເລັດແລ້ວ"</string> </resources> diff --git a/packages/PackageInstaller/res/values-lt/strings.xml b/packages/PackageInstaller/res/values-lt/strings.xml index 73b0bff0d46f..aec94c49825f 100644 --- a/packages/PackageInstaller/res/values-lt/strings.xml +++ b/packages/PackageInstaller/res/values-lt/strings.xml @@ -88,4 +88,7 @@ <string name="anonymous_source_continue" msgid="4375745439457209366">"Tęsti"</string> <string name="external_sources_settings" msgid="4046964413071713807">"Nustatymai"</string> <string name="wear_app_channel" msgid="1960809674709107850">"Įdiegiamos / pašalinamos „Wear“ program."</string> + <string name="app_installed_notification_channel_description" msgid="2695385797601574123">"Pranešimas apie įdiegtą programą"</string> + <string name="notification_installation_success_message" msgid="6450467996056038442">"Sėkmingai įdiegta"</string> + <string name="notification_installation_success_status" msgid="3172502643504323321">"„<xliff:g id="APPNAME">%1$s</xliff:g>“ sėkmingai įdiegta"</string> </resources> diff --git a/packages/PackageInstaller/res/values-lv/strings.xml b/packages/PackageInstaller/res/values-lv/strings.xml index d51a0aa07e21..76840cb35ea4 100644 --- a/packages/PackageInstaller/res/values-lv/strings.xml +++ b/packages/PackageInstaller/res/values-lv/strings.xml @@ -88,4 +88,7 @@ <string name="anonymous_source_continue" msgid="4375745439457209366">"Tālāk"</string> <string name="external_sources_settings" msgid="4046964413071713807">"Iestatījumi"</string> <string name="wear_app_channel" msgid="1960809674709107850">"Wear lietotņu instalēšana/atinstalēšana"</string> + <string name="app_installed_notification_channel_description" msgid="2695385797601574123">"Paziņojums par instalētu lietotni"</string> + <string name="notification_installation_success_message" msgid="6450467996056038442">"Sekmīgi instalēta"</string> + <string name="notification_installation_success_status" msgid="3172502643504323321">"Lietotne “<xliff:g id="APPNAME">%1$s</xliff:g>” sekmīgi instalēta"</string> </resources> diff --git a/packages/PackageInstaller/res/values-mk/strings.xml b/packages/PackageInstaller/res/values-mk/strings.xml index 4dd948bd2da0..998850eb76ac 100644 --- a/packages/PackageInstaller/res/values-mk/strings.xml +++ b/packages/PackageInstaller/res/values-mk/strings.xml @@ -88,4 +88,7 @@ <string name="anonymous_source_continue" msgid="4375745439457209366">"Продолжи"</string> <string name="external_sources_settings" msgid="4046964413071713807">"Поставки"</string> <string name="wear_app_channel" msgid="1960809674709107850">"Се инсталираат/деинсталираат аплик. Wear"</string> + <string name="app_installed_notification_channel_description" msgid="2695385797601574123">"Известување за инсталирана апликација"</string> + <string name="notification_installation_success_message" msgid="6450467996056038442">"Успешно инсталирана"</string> + <string name="notification_installation_success_status" msgid="3172502643504323321">"<xliff:g id="APPNAME">%1$s</xliff:g> е успешно инсталирана"</string> </resources> diff --git a/packages/PackageInstaller/res/values-ml/strings.xml b/packages/PackageInstaller/res/values-ml/strings.xml index bb0d39cfb343..d2dbdcfef36f 100644 --- a/packages/PackageInstaller/res/values-ml/strings.xml +++ b/packages/PackageInstaller/res/values-ml/strings.xml @@ -88,4 +88,7 @@ <string name="anonymous_source_continue" msgid="4375745439457209366">"തുടരുക"</string> <string name="external_sources_settings" msgid="4046964413071713807">"ക്രമീകരണം"</string> <string name="wear_app_channel" msgid="1960809674709107850">"Wear ആപ്പ് ഇൻസ്റ്റാൾ/അൺ ഇൻസ്റ്റാൾ ചെയ്യുന്നു"</string> + <string name="app_installed_notification_channel_description" msgid="2695385797601574123">"ആപ്പ് ഇൻസ്റ്റാൾ ചെയ്ത അറിയിപ്പ്"</string> + <string name="notification_installation_success_message" msgid="6450467996056038442">"ഇൻസ്റ്റാൾ ചെയ്തു"</string> + <string name="notification_installation_success_status" msgid="3172502643504323321">"\"<xliff:g id="APPNAME">%1$s</xliff:g>\" ഇൻസ്റ്റാൾ ചെയ്തു"</string> </resources> diff --git a/packages/PackageInstaller/res/values-mn/strings.xml b/packages/PackageInstaller/res/values-mn/strings.xml index 5b9373625092..56a2128499f2 100644 --- a/packages/PackageInstaller/res/values-mn/strings.xml +++ b/packages/PackageInstaller/res/values-mn/strings.xml @@ -88,4 +88,7 @@ <string name="anonymous_source_continue" msgid="4375745439457209366">"Үргэлжлүүлэх"</string> <string name="external_sources_settings" msgid="4046964413071713807">"Тохиргоо"</string> <string name="wear_app_channel" msgid="1960809674709107850">"Wear аппуудыг суулгаж/устгаж байна"</string> + <string name="app_installed_notification_channel_description" msgid="2695385797601574123">"Апп суулгасны мэдэгдэл"</string> + <string name="notification_installation_success_message" msgid="6450467996056038442">"Амжилттай суулгасан"</string> + <string name="notification_installation_success_status" msgid="3172502643504323321">"“<xliff:g id="APPNAME">%1$s</xliff:g>”-г амжилттай суулгасан"</string> </resources> diff --git a/packages/PackageInstaller/res/values-mr/strings.xml b/packages/PackageInstaller/res/values-mr/strings.xml index 5bbf7b9c5cfe..d751c42a2df7 100644 --- a/packages/PackageInstaller/res/values-mr/strings.xml +++ b/packages/PackageInstaller/res/values-mr/strings.xml @@ -88,4 +88,7 @@ <string name="anonymous_source_continue" msgid="4375745439457209366">"सुरू ठेवा"</string> <string name="external_sources_settings" msgid="4046964413071713807">"सेटिंग्ज"</string> <string name="wear_app_channel" msgid="1960809674709107850">"wear अॅप्स इंस्टॉल/अनइंस्टॉल करत आहे"</string> + <string name="app_installed_notification_channel_description" msgid="2695385797601574123">"अॅप इंस्टॉल सूचना"</string> + <string name="notification_installation_success_message" msgid="6450467996056038442">"यशस्वीरित्या इंस्टॉल केले"</string> + <string name="notification_installation_success_status" msgid="3172502643504323321">"\"<xliff:g id="APPNAME">%1$s</xliff:g>\" यशस्वीरित्या इंस्टॉल झाले"</string> </resources> diff --git a/packages/PackageInstaller/res/values-ms/strings.xml b/packages/PackageInstaller/res/values-ms/strings.xml index 620dc3ff3258..674f55b86b38 100644 --- a/packages/PackageInstaller/res/values-ms/strings.xml +++ b/packages/PackageInstaller/res/values-ms/strings.xml @@ -88,4 +88,7 @@ <string name="anonymous_source_continue" msgid="4375745439457209366">"Teruskan"</string> <string name="external_sources_settings" msgid="4046964413071713807">"Tetapan"</string> <string name="wear_app_channel" msgid="1960809674709107850">"Memasang/menyahpasang apl wear"</string> + <string name="app_installed_notification_channel_description" msgid="2695385797601574123">"Pemberitahuan apl dipasang"</string> + <string name="notification_installation_success_message" msgid="6450467996056038442">"Berjaya dipasang"</string> + <string name="notification_installation_success_status" msgid="3172502643504323321">"Berjaya memasang \"<xliff:g id="APPNAME">%1$s</xliff:g>\""</string> </resources> diff --git a/packages/PackageInstaller/res/values-my/strings.xml b/packages/PackageInstaller/res/values-my/strings.xml index c115cad39444..9c9295712b25 100644 --- a/packages/PackageInstaller/res/values-my/strings.xml +++ b/packages/PackageInstaller/res/values-my/strings.xml @@ -88,4 +88,7 @@ <string name="anonymous_source_continue" msgid="4375745439457209366">"ရှေ့ဆက်ရန်"</string> <string name="external_sources_settings" msgid="4046964413071713807">"ဆက်တင်များ"</string> <string name="wear_app_channel" msgid="1960809674709107850">"wear အက်ပ်ကိုထည့်သွင်းခြင်း/ဖယ်ရှားခြင်း"</string> + <string name="app_installed_notification_channel_description" msgid="2695385797601574123">"အက်ပ်ထည့်သွင်းခြင်း အကြောင်းကြားချက်"</string> + <string name="notification_installation_success_message" msgid="6450467996056038442">"ထည့်သွင်းပြီးပြီ"</string> + <string name="notification_installation_success_status" msgid="3172502643504323321">"\"<xliff:g id="APPNAME">%1$s</xliff:g>\" ကို ထည့်သွင်းပြီးပြီ"</string> </resources> diff --git a/packages/PackageInstaller/res/values-nb/strings.xml b/packages/PackageInstaller/res/values-nb/strings.xml index f39bf2da56c1..0d3384eac5a0 100644 --- a/packages/PackageInstaller/res/values-nb/strings.xml +++ b/packages/PackageInstaller/res/values-nb/strings.xml @@ -88,4 +88,7 @@ <string name="anonymous_source_continue" msgid="4375745439457209366">"Fortsett"</string> <string name="external_sources_settings" msgid="4046964413071713807">"Innstillinger"</string> <string name="wear_app_channel" msgid="1960809674709107850">"Installerer/avinstallerer Wear-apper"</string> + <string name="app_installed_notification_channel_description" msgid="2695385797601574123">"Varsel om at appen er installert"</string> + <string name="notification_installation_success_message" msgid="6450467996056038442">"Installert"</string> + <string name="notification_installation_success_status" msgid="3172502643504323321">"«<xliff:g id="APPNAME">%1$s</xliff:g>» er installert"</string> </resources> diff --git a/packages/PackageInstaller/res/values-ne/strings.xml b/packages/PackageInstaller/res/values-ne/strings.xml index 4d17a3e18d52..a7abc17f538d 100644 --- a/packages/PackageInstaller/res/values-ne/strings.xml +++ b/packages/PackageInstaller/res/values-ne/strings.xml @@ -88,4 +88,7 @@ <string name="anonymous_source_continue" msgid="4375745439457209366">"जारी राख्नुहोस्"</string> <string name="external_sources_settings" msgid="4046964413071713807">"सेटिङहरू"</string> <string name="wear_app_channel" msgid="1960809674709107850">"वेयर एपहरूको स्थापना/स्थापना रद्द गर्दै"</string> + <string name="app_installed_notification_channel_description" msgid="2695385797601574123">"अनुप्रयोगको स्थापना गरिएको सूचना"</string> + <string name="notification_installation_success_message" msgid="6450467996056038442">"सफलतापूर्वक स्थापना गरियो"</string> + <string name="notification_installation_success_status" msgid="3172502643504323321">"“<xliff:g id="APPNAME">%1$s</xliff:g>” सफलतापूर्वक स्थापना गरियो"</string> </resources> diff --git a/packages/PackageInstaller/res/values-nl/strings.xml b/packages/PackageInstaller/res/values-nl/strings.xml index 39fb8f0ed95c..a7a5a34b2e3a 100644 --- a/packages/PackageInstaller/res/values-nl/strings.xml +++ b/packages/PackageInstaller/res/values-nl/strings.xml @@ -88,4 +88,7 @@ <string name="anonymous_source_continue" msgid="4375745439457209366">"Doorgaan"</string> <string name="external_sources_settings" msgid="4046964413071713807">"Instellingen"</string> <string name="wear_app_channel" msgid="1960809674709107850">"Wear-apps installeren/verwijderen"</string> + <string name="app_installed_notification_channel_description" msgid="2695385797601574123">"Melding dat app is geïnstalleerd"</string> + <string name="notification_installation_success_message" msgid="6450467996056038442">"Installatie voltooid"</string> + <string name="notification_installation_success_status" msgid="3172502643504323321">"\'<xliff:g id="APPNAME">%1$s</xliff:g>\' is geïnstalleerd"</string> </resources> diff --git a/packages/PackageInstaller/res/values-or/strings.xml b/packages/PackageInstaller/res/values-or/strings.xml new file mode 100644 index 000000000000..348cc6109fa4 --- /dev/null +++ b/packages/PackageInstaller/res/values-or/strings.xml @@ -0,0 +1,94 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- Copyright (C) 2007 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="app_name" msgid="7488448184431507488">"ପ୍ୟାକେଜ୍ ଇନଷ୍ଟଲର୍"</string> + <string name="install" msgid="711829760615509273">"ଇନଷ୍ଟଲ୍ କରନ୍ତୁ"</string> + <string name="done" msgid="6632441120016885253">"ହୋଇଗଲା"</string> + <string name="cancel" msgid="1018267193425558088">"କ୍ୟାନ୍ସଲ୍ କରନ୍ତୁ"</string> + <string name="installing" msgid="4921993079741206516">"ଇନଷ୍ଟଲ୍ କରାଯାଉଛି…"</string> + <string name="installing_app" msgid="1165095864863849422">"<xliff:g id="PACKAGE_LABEL">%1$s</xliff:g> ଇନଷ୍ଟଲ୍ କରାଯାଉଛି…"</string> + <string name="install_done" msgid="5987363587661783896">"ଆପ୍ ଇନଷ୍ଟଲ୍ ହୋଇଗଲା।"</string> + <string name="install_confirm_question" msgid="8176284075816604590">"ଆପଣ ଏହି ଆପ୍ଲିକେଶନ୍ ଇନ୍ଷ୍ଟଲ୍ କରିବାକୁ ଚାହୁଁଛନ୍ତି?"</string> + <string name="install_confirm_question_update" msgid="7942235418781274635">"ପୂର୍ବରୁ ରହିଥିବା ଏହି ଆପ୍ଲିକେଶନ୍ରେ ଆପଣ ଅପ୍ଡେଟ୍ ଇନ୍ଷ୍ଟଲ୍ କରିବାକୁ ଚାହୁଁଛନ୍ତି? ନିଜର ବିଦ୍ୟମାନ ଡାଟାକୁ ଆପଣ ହରାଇବେ ନାହିଁ।"</string> + <string name="install_confirm_question_update_system" msgid="4713001702777910263">"ଏହି ବିଲ୍ଟ-ଇନ୍ ଆପ୍ଲିକେଶନ୍ରେ ଆପଣ ଏକ ଅପ୍ଡେଟ୍ ଇନ୍ଷ୍ଟଲ୍ କରିବାକୁ ଚାହୁଁଛନ୍ତି? ନିଜର ବିଦ୍ୟମାନ ଡାଟାକୁ ଆପଣ ହରାଇବେ ନାହିଁ।"</string> + <string name="install_failed" msgid="5777824004474125469">"ଆପ୍ ଇନଷ୍ଟଲ୍ ହୋଇନାହିଁ।"</string> + <string name="install_failed_blocked" msgid="8512284352994752094">"ଏହି ପ୍ୟାକେଜ୍କୁ ଇନଷ୍ଟଲ୍ କରାଯିବାରୁ ଅବରୋଧ କରାଯାଇଥିଲା।"</string> + <string name="install_failed_conflict" msgid="3493184212162521426">"ପୂର୍ବରୁ ଥିବା ପ୍ୟାକେଜ୍ ସହ ଏହି ପ୍ୟାକେଜ୍ର ସମସ୍ୟା ଉପୁଯିବାରୁ ଆପ୍ ଇନଷ୍ଟଲ୍ ହୋଇପାରିଲା ନାହିଁ।"</string> + <string name="install_failed_incompatible" product="tablet" msgid="6019021440094927928">"ଆପ୍ ଆପଣଙ୍କ ଟାବଲେଟ୍ ସହ କମ୍ପାଟିବଲ୍ ନଥିବା ହେତୁ ଆପ୍ ଇନଷ୍ଟଲ୍ ହୋଇପାରିଲା ନାହିଁ।"</string> + <string name="install_failed_incompatible" product="tv" msgid="2890001324362291683">"ଏହି ଆପ୍ ଆପଣଙ୍କ ଟିଭି ସହ କମ୍ପାଟିବଲ୍ ନୁହେଁ।"</string> + <string name="install_failed_incompatible" product="default" msgid="7254630419511645826">"ଆପ୍ ଆପଣଙ୍କ ଫୋନ୍ ସହ କମ୍ପାଟିବଲ୍ ନଥିବା ହେତୁ ଆପ୍ ଇନଷ୍ଟଲ୍ ହୋଇପାରିଲା ନାହିଁ।"</string> + <string name="install_failed_invalid_apk" msgid="8581007676422623930">"ପ୍ୟାକେଜ୍ ଅମାନ୍ୟ ଥିବା ପରି ଜଣାପଡ଼ିବାରୁ ଆପ୍ ଇନ୍ଷ୍ଟଲ୍ ହୋଇପାରିଲା ନାହିଁ।"</string> + <string name="install_failed_msg" product="tablet" msgid="6298387264270562442">"<xliff:g id="APP_NAME">%1$s</xliff:g>କୁ ଆପଣଙ୍କ ଟାବଲେଟ୍ରେ ଇନ୍ଷ୍ଟଲ୍ କରିହେଲା ନାହିଁ।"</string> + <string name="install_failed_msg" product="tv" msgid="1920009940048975221">"<xliff:g id="APP_NAME">%1$s</xliff:g>କୁ ଆପଣଙ୍କ ଟିଭିରେ ଇନ୍ଷ୍ଟଲ୍ କରିହେଲା ନାହିଁ।"</string> + <string name="install_failed_msg" product="default" msgid="6484461562647915707">"<xliff:g id="APP_NAME">%1$s</xliff:g>କୁ ଆପଣଙ୍କ ଫୋନ୍ରେ ଇନ୍ଷ୍ଟଲ୍ କରିହେଲା ନାହିଁ।"</string> + <string name="launch" msgid="3952550563999890101">"ଖୋଲନ୍ତୁ"</string> + <string name="unknown_apps_admin_dlg_text" msgid="4456572224020176095">"ଅଜଣା ସୋର୍ସରୁ ଆସିଥିବା ଆପଗୁଡ଼ିକ ଇନଷ୍ଟଲ୍ କରିବାକୁ ଆପଣଙ୍କ ଆଡମିନ୍ ଅନୁମତି ଦିଅନ୍ତି ନାହିଁ"</string> + <string name="unknown_apps_user_restriction_dlg_text" msgid="151020786933988344">"ଏହି ୟୁଜରଙ୍କ ଦ୍ୱାରା ଅଜଣା ଆପ୍ ଇନଷ୍ଟଲ୍ କରାଯାଇପାରିବ ନାହିଁ"</string> + <string name="install_apps_user_restriction_dlg_text" msgid="2154119597001074022">"ଏହି ୟୁଜର୍ ଆପ୍ ଇନଷ୍ଟଲ୍ କରିପାରିବେ ନାହିଁ"</string> + <string name="ok" msgid="7871959885003339302">"ଠିକ୍ ଅଛି"</string> + <string name="manage_applications" msgid="5400164782453975580">"ଆପ୍ଗୁଡ଼ିକର ପରିଚାଳନା କରନ୍ତୁ"</string> + <string name="out_of_space_dlg_title" msgid="4156690013884649502">"ଆଉ ସ୍ଥାନ ନାହିଁ"</string> + <string name="out_of_space_dlg_text" msgid="8727714096031856231">"<xliff:g id="APP_NAME">%1$s</xliff:g> ଇନଷ୍ଟଲ୍ କରାଯାଇପାରିଲା ନାହିଁ। କିଛି ସ୍ଥାନ ଖାଲିକରି ପୁଣିଥରେ ଚେଷ୍ଟା କରନ୍ତୁ।"</string> + <string name="app_not_found_dlg_title" msgid="5107924008597470285">"ଆପ୍ ମିଳିଲା ନାହିଁ"</string> + <string name="app_not_found_dlg_text" msgid="5219983779377811611">"ଇନଷ୍ଟଲ୍ କରାଯାଇଥିବା ଆପ୍ ତାଲିକାରେ ଆପ୍ଟି ମିଳିଲା ନାହିଁ।"</string> + <string name="user_is_not_allowed_dlg_title" msgid="6915293433252210232">"ଅନୁମତି ନାହିଁ"</string> + <string name="user_is_not_allowed_dlg_text" msgid="3468447791330611681">"ଏହି ଅନଇନଷ୍ଟଲେଶନ୍ କରିବାକୁ ବର୍ତ୍ତମାନର ୟୁଜରଙ୍କୁ ଅନୁମତି ଦିଆଯାଇନାହିଁ।"</string> + <string name="generic_error_dlg_title" msgid="5863195085927067752">"ତ୍ରୁଟି"</string> + <string name="generic_error_dlg_text" msgid="5287861443265795232">"ଆପ୍କୁ ଅନଇନଷ୍ଟଲ୍ କରାହେବ ନାହିଁ"</string> + <string name="uninstall_application_title" msgid="4045420072401428123">"ଆପ୍କୁ ଅନଇନଷ୍ଟଲ୍ କରନ୍ତୁ"</string> + <string name="uninstall_update_title" msgid="824411791011583031">"ଅପଡେଟ୍ ଅନଇନଷ୍ଟଲ୍ କରନ୍ତୁ"</string> + <string name="uninstall_activity_text" msgid="1928194674397770771">"<xliff:g id="ACTIVITY_NAME">%1$s</xliff:g> ହେଉଛି ନିମ୍ନ ଆପ୍ର ଏକ ଅଂଶ।"</string> + <string name="uninstall_application_text" msgid="3816830743706143980">"ଆପଣ ଏହି ଆପ୍ ଅନଇନଷ୍ଟଲ୍ କରିବାକୁ ଚାହାଁନ୍ତି କି?"</string> + <string name="uninstall_application_text_all_users" msgid="575491774380227119">"ଆପଣ "<b>"ସମସ୍ତ"</b>" ୟୁଜର୍ଙ୍କ ପାଇଁ ଏହି ଆପ୍କୁ ଅନଷ୍ଟଲ୍ କରିବାକୁ ଚାହୁଁଛନ୍ତି କି? ଡିଭାଇସ୍ରେ ଥିବା "<b>"ସମସ୍ତ"</b>" ୟୁଜର୍ ଆପ୍ଲିକେଶନ୍ ଏବଂ ତାହାର ଡାଟା ବାହାର କରିଦିଆଯିବ।"</string> + <string name="uninstall_application_text_user" msgid="498072714173920526">"ୟୁଜର୍ <xliff:g id="USERNAME">%1$s</xliff:g>ଙ୍କ ପାଇଁ ଆପଣ ଏହି ଆପ୍ ଇନଷ୍ଟଲ୍ କରିବେ କି?"</string> + <string name="uninstall_update_text" msgid="863648314632448705">"ଏହି ଆପ୍ ଫ୍ୟାକ୍ଟୋରୀ ଭର୍ସନ୍ ସହ ବଦଳାଇବେ? ସମସ୍ତ ଡାଟା କାଢ଼ିଦିଆଯିବ।"</string> + <string name="uninstall_update_text_multiuser" msgid="8992883151333057227">"ଏହି ଆପ୍ ଫ୍ୟାକ୍ଟୋରୀ ଭର୍ସନ୍ ସହ ବଦଳାଇବେ? ସମସ୍ତ ଡାଟା ବାହାର କରିଦିଆଯିବ। ୱର୍କ ପ୍ରୋଫାଇଲ୍ ଥିବା ସମେତ, ଏହାଦ୍ୱାରା ଡିଭାଇସରେ ଥିବା ସମସ୍ତ ୟୁଜର୍ ପ୍ରଭାବିତ ହେବେ।"</string> + <string name="uninstalling_notification_channel" msgid="840153394325714653">"ଅନଇନଷ୍ଟଲ୍ ଚାଲୁଛି"</string> + <string name="uninstall_failure_notification_channel" msgid="1136405866767576588">"ବିଫଳ ହୋଇଥିବା ଅନଇନଷ୍ଟଲ୍"</string> + <string name="uninstalling" msgid="8709566347688966845">"ଅନ୍ଇନଷ୍ଟଲ୍ କରାଯାଉଛି…"</string> + <string name="uninstalling_app" msgid="8866082646836981397">"<xliff:g id="PACKAGE_LABEL">%1$s</xliff:g> ଅନଇନଷ୍ଟଲ୍ କରାଯାଉଛି…"</string> + <string name="uninstall_done" msgid="439354138387969269">"ଅନଇନଷ୍ଟଲ୍ ହୋଇଗଲା।"</string> + <string name="uninstall_done_app" msgid="4588850984473605768">"<xliff:g id="PACKAGE_LABEL">%1$s</xliff:g>କୁ ଅନଇନଷ୍ଟଲ୍ କରାଗଲା"</string> + <string name="uninstall_failed" msgid="1847750968168364332">"ଅନଇନଷ୍ଟଲ୍ କରିହେଲା ନାହିଁ।"</string> + <string name="uninstall_failed_app" msgid="5506028705017601412">"<xliff:g id="PACKAGE_LABEL">%1$s</xliff:g> ଅନଇନଷ୍ଟଲ୍ କରିବା ସଫଳ ହେଲାନାହିଁ।"</string> + <string name="uninstall_failed_device_policy_manager" msgid="785293813665540305">"ସକ୍ରିୟ ଡିଭାଇସ୍ ଆପ୍ ଅନଇନଷ୍ଟଲ୍ କରିହେବ ନାହିଁ"</string> + <string name="uninstall_failed_device_policy_manager_of_user" msgid="4813104025494168064">"<xliff:g id="USERNAME">%1$s</xliff:g>ଙ୍କ ପାଇଁ ସକ୍ରିୟ ଡିଭାଇସ୍ ଆଡମିନ୍ ଆପ୍ ଅନଇନଷ୍ଟଲ୍ କରାଯାଇପାରିବ ନାହିଁ"</string> + <string name="uninstall_all_blocked_profile_owner" msgid="2009393666026751501">"କିଛି ୟୁଜର୍ କିମ୍ବା ପ୍ରୋଫାଇଲ୍ ପାଇଁ ଏହି ଆପ୍ ଆବଶ୍ୟକ ଏବଂ ଅନ୍ୟ ସମସ୍ତଙ୍କ ପାଇଁ ଅନଇନଷ୍ଟଲ୍ କରାଯାଇଥିଲା"</string> + <string name="uninstall_blocked_profile_owner" msgid="6373897407002404848">"ଏହି ଆପ୍ ଆପଣଙ୍କ ପ୍ରୋଫାଇଲ୍ ପାଇଁ ଆବଶ୍ୟକ ଅଟେ ଏବଂ ଅନଇନଷ୍ଟଲ୍ କରାଯାଇପାରିବ ନାହିଁ।"</string> + <string name="uninstall_blocked_device_owner" msgid="6724602931761073901">"ଆପଣଙ୍କ ଡିଭାଇସ୍ ଆଡମିନିଷ୍ଟ୍ରେଟର୍ ଦ୍ୱାରା ଏହି ଆପ୍ ଆବଶ୍ୟକ କରାଯାଇଥାଏ ଏବଂ ଏହାକୁ ଅନ୍ଇନଷ୍ଟଲ୍ କରିହେବ ନାହିଁ।"</string> + <string name="manage_device_administrators" msgid="3092696419363842816">"ଡିଭାଇସ୍ ଆଡମିନ୍ ଆପ୍ ପରିଚାଳନା କରନ୍ତୁ"</string> + <string name="manage_users" msgid="1243995386982560813">"ୟୁଜର୍ଙ୍କୁ ପରିଚାଳନା କରନ୍ତୁ"</string> + <string name="uninstall_failed_msg" msgid="2176744834786696012">"<xliff:g id="APP_NAME">%1$s</xliff:g> ଅନଇନଷ୍ଟଲ୍ କରିହେଲା ନାହିଁ।"</string> + <string name="Parse_error_dlg_text" msgid="1661404001063076789">"ଏହି ପ୍ୟାକେଜ୍ ପାର୍ସ କରିବାରେ ସମସ୍ୟା ଥିଲା।"</string> + <string name="wear_not_allowed_dlg_title" msgid="8664785993465117517">"Android Wear"</string> + <string name="wear_not_allowed_dlg_text" msgid="704615521550939237">"ୱିଅର୍ରେ ଇନଷ୍ଟଲ୍/ଅନଇନଷ୍ଟଲ୍ କାର୍ଯ୍ୟ ସପୋର୍ଟ କରେନାହିଁ।"</string> + <string name="message_staging" msgid="8032722385658438567">"ଆପ୍ ପର୍ଯ୍ୟାୟଭୁକ୍ତ କରାଯାଉଛି…"</string> + <string name="app_name_unknown" msgid="6881210203354323926">"ଅଜଣା"</string> + <string name="untrusted_external_source_warning" product="tablet" msgid="6539403649459942547">"ଆପଣଙ୍କ ସୁରକ୍ଷା ପାଇଁ, ଆପଣଙ୍କ ଟାବ୍ଲେଟ୍କୁ ଏହି ସୋର୍ସରୁ ଆସିଥିବା ଅଜଣା ଆପ୍ ଇନଷ୍ଟଲ୍ କରିବାକୁ ଅନୁମତି ଦିଆଯାଇନାହିଁ।"</string> + <string name="untrusted_external_source_warning" product="tv" msgid="1206648674551321364">"ଆପଣଙ୍କ ସୁରକ୍ଷା ପାଇଁ, ଆପଣଙ୍କ ଟିଭିକୁ ଏହି ସୋର୍ସରୁ ଆସିଥିବା ଅଜଣା ଆପ୍ ଇନଷ୍ଟଲ୍ କରିବାକୁ ଅନୁମତି ଦିଆଯାଇନାହିଁ।"</string> + <string name="untrusted_external_source_warning" product="default" msgid="7279739265754475165">"ଆପଣଙ୍କ ସୁରକ୍ଷା ପାଇଁ, ଆପଣଙ୍କ ଫୋନ୍କୁ ଏହି ସୋର୍ସରୁ ଆସିଥିବା ଅଜଣା ଆପ୍ ଇନଷ୍ଟଲ୍ କରିବାକୁ ଅନୁମତି ଦିଆଯାଇନାହିଁ।"</string> + <string name="anonymous_source_warning" product="default" msgid="2784902545920822500">"ଅଜଣା ଆପ୍ ଦ୍ୱାରା ଆପଣଙ୍କ ଫୋନ୍ ଏବଂ ବ୍ୟକ୍ତିଗତ ଡାଟାକୁ ନଷ୍ଟ କରାଯାଇପାରିବାର ସମ୍ଭାବନା ବହୁତ ଅଧିକ। ଏହି ଆପ୍କୁ ଇନଷ୍ଟଲ୍ କରିବାର ଅର୍ଥ ହେଉଛି ଆପଣଙ୍କ ଫୋନ୍ରେ ଘଟିବା କୌଣସି ପ୍ରକାର କ୍ଷତି କିମ୍ବା ସେଗୁଡ଼ିକର ବ୍ୟବହାରରୁ ହେବା କୌଣସି ପ୍ରକାର ଡାଟାର ହାନୀ ପାଇଁ ଆପଣ ଦାୟୀ ରହିବାକୁ ରାଜି ହୁଅନ୍ତି।"</string> + <string name="anonymous_source_warning" product="tablet" msgid="3939101621438855516">"ଅଜଣା ଆପ୍ ଦ୍ୱାରା ଆପଣଙ୍କ ଟାବଲେଟ୍ ଏବଂ ବ୍ୟକ୍ତିଗତ ଡାଟାକୁ ନଷ୍ଟ କରାଯାଇପାରିବାର ସମ୍ଭାବନା ବହୁତ ଅଧିକ। ଏହି ଆପ୍କୁ ଇନଷ୍ଟଲ୍ କରିବାର ଅର୍ଥ ହେଉଛି ଆପଣଙ୍କ ଟାବ୍ଲେଟ୍ରେ ଘଟିବା କୌଣସି ପ୍ରକାର କ୍ଷତି କିମ୍ବା ସେଗୁଡ଼ିକର ବ୍ୟବହାରରୁ ହେବା କୌଣସି ପ୍ରକାର ଡାଟାର ହାନୀ ପାଇଁ ଆପଣ ଦାୟୀ ରହିବାକୁ ରାଜି ହୁଅନ୍ତି।"</string> + <string name="anonymous_source_warning" product="tv" msgid="5599483539528168566">"ଅଜଣା ଆପ୍ ଦ୍ୱାରା ଆପଣଙ୍କ ଟିଭି ଏବଂ ବ୍ୟକ୍ତିଗତ ଡାଟାକୁ ନଷ୍ଟ କରାଯାଇପାରିବାର ସମ୍ଭାବନା ବହୁତ ଅଧିକ। ଏହି ଆପ୍କୁ ଇନଷ୍ଟଲ୍ କରିବାର ଅର୍ଥ ହେଉଛି ଆପଣଙ୍କ ଟିଭିରେ ଘଟିବା କୌଣସି ପ୍ରକାର କ୍ଷତି କିମ୍ବା ସେଗୁଡ଼ିକର ବ୍ୟବହାରରୁ ହେବା କୌଣସି ପ୍ରକାର ଡାଟାର ହାନୀ ପାଇଁ ଆପଣ ଦାୟୀ ରହିବାକୁ ରାଜି ହୁଅନ୍ତି।"</string> + <string name="anonymous_source_continue" msgid="4375745439457209366">"ଜାରି ରଖନ୍ତୁ"</string> + <string name="external_sources_settings" msgid="4046964413071713807">"ସେଟିଙ୍ଗ"</string> + <string name="wear_app_channel" msgid="1960809674709107850">"ୱିଅର୍ ଆପ୍ ଇନଷ୍ଟଲ୍/ଅନଇନଷ୍ଟଲ୍ କରାଯାଉଛି"</string> + <string name="app_installed_notification_channel_description" msgid="2695385797601574123">"ଆପ୍ ଇନ୍ଷ୍ଟଲ୍ କରାଯାଇଥିବା ବିଜ୍ଞପ୍ତି"</string> + <string name="notification_installation_success_message" msgid="6450467996056038442">"ସଫଳତାପୂର୍ବକ ଇନ୍ଷ୍ଟଲ୍ କରାଗଲା"</string> + <string name="notification_installation_success_status" msgid="3172502643504323321">"“<xliff:g id="APPNAME">%1$s</xliff:g>” ସଫଳତାପୂର୍ବକ ଇନ୍ଷ୍ଚଲ୍ କରାଗଲା"</string> +</resources> diff --git a/packages/PackageInstaller/res/values-pa/strings.xml b/packages/PackageInstaller/res/values-pa/strings.xml index 6a1d7eb091e6..7c519a036e04 100644 --- a/packages/PackageInstaller/res/values-pa/strings.xml +++ b/packages/PackageInstaller/res/values-pa/strings.xml @@ -88,4 +88,7 @@ <string name="anonymous_source_continue" msgid="4375745439457209366">"ਜਾਰੀ ਰੱਖੋ"</string> <string name="external_sources_settings" msgid="4046964413071713807">"ਸੈਟਿੰਗਾਂ"</string> <string name="wear_app_channel" msgid="1960809674709107850">"ਵੀਅਰ ਐਪਾਂ ਸਥਾਪਤ ਜਾਂ ਅਣਸਥਾਪਤ ਕਰਨਾ"</string> + <string name="app_installed_notification_channel_description" msgid="2695385797601574123">"ਐਪ ਸਥਾਪਨਾ ਦੀ ਸੂਚਨਾ"</string> + <string name="notification_installation_success_message" msgid="6450467996056038442">"ਸਫਲਤਾਪੂਰਵਕ ਸਥਾਪਤ ਕੀਤੀ ਗਈ"</string> + <string name="notification_installation_success_status" msgid="3172502643504323321">"\"<xliff:g id="APPNAME">%1$s</xliff:g>\" ਨੂੰ ਸਫਲਤਾਪੂਰਵਕ ਸਥਾਪਤ ਕੀਤਾ ਗਿਆ"</string> </resources> diff --git a/packages/PackageInstaller/res/values-pl/strings.xml b/packages/PackageInstaller/res/values-pl/strings.xml index 01b2db991356..19b05a928e34 100644 --- a/packages/PackageInstaller/res/values-pl/strings.xml +++ b/packages/PackageInstaller/res/values-pl/strings.xml @@ -88,4 +88,7 @@ <string name="anonymous_source_continue" msgid="4375745439457209366">"Dalej"</string> <string name="external_sources_settings" msgid="4046964413071713807">"Ustawienia"</string> <string name="wear_app_channel" msgid="1960809674709107850">"Instalacja/usuwanie aplikacji na Wear"</string> + <string name="app_installed_notification_channel_description" msgid="2695385797601574123">"Powiadomienie o zainstalowaniu aplikacji"</string> + <string name="notification_installation_success_message" msgid="6450467996056038442">"Zainstalowano"</string> + <string name="notification_installation_success_status" msgid="3172502643504323321">"Zainstalowano aplikację „<xliff:g id="APPNAME">%1$s</xliff:g>”"</string> </resources> diff --git a/packages/PackageInstaller/res/values-pt-rBR/strings.xml b/packages/PackageInstaller/res/values-pt-rBR/strings.xml index 47289f92975d..1a318374e2dc 100644 --- a/packages/PackageInstaller/res/values-pt-rBR/strings.xml +++ b/packages/PackageInstaller/res/values-pt-rBR/strings.xml @@ -88,4 +88,7 @@ <string name="anonymous_source_continue" msgid="4375745439457209366">"Continuar"</string> <string name="external_sources_settings" msgid="4046964413071713807">"Configurações"</string> <string name="wear_app_channel" msgid="1960809674709107850">"Instalando/desinstalando apps do Wear"</string> + <string name="app_installed_notification_channel_description" msgid="2695385797601574123">"Notificação de app instalado"</string> + <string name="notification_installation_success_message" msgid="6450467996056038442">"Instalação concluída"</string> + <string name="notification_installation_success_status" msgid="3172502643504323321">"“<xliff:g id="APPNAME">%1$s</xliff:g>” instalado"</string> </resources> diff --git a/packages/PackageInstaller/res/values-pt-rPT/strings.xml b/packages/PackageInstaller/res/values-pt-rPT/strings.xml index 871b0a17d0a1..d45dc1cfd49b 100644 --- a/packages/PackageInstaller/res/values-pt-rPT/strings.xml +++ b/packages/PackageInstaller/res/values-pt-rPT/strings.xml @@ -88,4 +88,7 @@ <string name="anonymous_source_continue" msgid="4375745439457209366">"Continuar"</string> <string name="external_sources_settings" msgid="4046964413071713807">"Definições"</string> <string name="wear_app_channel" msgid="1960809674709107850">"Instalar/desinstalar aplicações Wear"</string> + <string name="app_installed_notification_channel_description" msgid="2695385797601574123">"Notificação de aplicação instalada"</string> + <string name="notification_installation_success_message" msgid="6450467996056038442">"Aplicação instalada com êxito"</string> + <string name="notification_installation_success_status" msgid="3172502643504323321">"Aplicação \"<xliff:g id="APPNAME">%1$s</xliff:g>\" instalada com êxito"</string> </resources> diff --git a/packages/PackageInstaller/res/values-pt/strings.xml b/packages/PackageInstaller/res/values-pt/strings.xml index 47289f92975d..1a318374e2dc 100644 --- a/packages/PackageInstaller/res/values-pt/strings.xml +++ b/packages/PackageInstaller/res/values-pt/strings.xml @@ -88,4 +88,7 @@ <string name="anonymous_source_continue" msgid="4375745439457209366">"Continuar"</string> <string name="external_sources_settings" msgid="4046964413071713807">"Configurações"</string> <string name="wear_app_channel" msgid="1960809674709107850">"Instalando/desinstalando apps do Wear"</string> + <string name="app_installed_notification_channel_description" msgid="2695385797601574123">"Notificação de app instalado"</string> + <string name="notification_installation_success_message" msgid="6450467996056038442">"Instalação concluída"</string> + <string name="notification_installation_success_status" msgid="3172502643504323321">"“<xliff:g id="APPNAME">%1$s</xliff:g>” instalado"</string> </resources> diff --git a/packages/PackageInstaller/res/values-ro/strings.xml b/packages/PackageInstaller/res/values-ro/strings.xml index 43b0dead3341..8bcc96919580 100644 --- a/packages/PackageInstaller/res/values-ro/strings.xml +++ b/packages/PackageInstaller/res/values-ro/strings.xml @@ -88,4 +88,7 @@ <string name="anonymous_source_continue" msgid="4375745439457209366">"Continuați"</string> <string name="external_sources_settings" msgid="4046964413071713807">"Setări"</string> <string name="wear_app_channel" msgid="1960809674709107850">"Se (dez)instalează aplicațiile Wear"</string> + <string name="app_installed_notification_channel_description" msgid="2695385797601574123">"Notificare de aplicație instalată"</string> + <string name="notification_installation_success_message" msgid="6450467996056038442">"Aplicația a fost instalată"</string> + <string name="notification_installation_success_status" msgid="3172502643504323321">"„<xliff:g id="APPNAME">%1$s</xliff:g>” a fost instalată"</string> </resources> diff --git a/packages/PackageInstaller/res/values-ru/strings.xml b/packages/PackageInstaller/res/values-ru/strings.xml index 63287f446232..4e5d1ac0d72f 100644 --- a/packages/PackageInstaller/res/values-ru/strings.xml +++ b/packages/PackageInstaller/res/values-ru/strings.xml @@ -88,4 +88,7 @@ <string name="anonymous_source_continue" msgid="4375745439457209366">"Продолжить"</string> <string name="external_sources_settings" msgid="4046964413071713807">"Настройки"</string> <string name="wear_app_channel" msgid="1960809674709107850">"Установка/удаление прилож. для Wear OS"</string> + <string name="app_installed_notification_channel_description" msgid="2695385797601574123">"Уведомление об установке приложения"</string> + <string name="notification_installation_success_message" msgid="6450467996056038442">"Установлено"</string> + <string name="notification_installation_success_status" msgid="3172502643504323321">"Приложение \"<xliff:g id="APPNAME">%1$s</xliff:g>\" установлено"</string> </resources> diff --git a/packages/PackageInstaller/res/values-si/strings.xml b/packages/PackageInstaller/res/values-si/strings.xml index 2e926afb26f0..7d854dc41bca 100644 --- a/packages/PackageInstaller/res/values-si/strings.xml +++ b/packages/PackageInstaller/res/values-si/strings.xml @@ -88,4 +88,7 @@ <string name="anonymous_source_continue" msgid="4375745439457209366">"ඉදිරියට යන්න"</string> <string name="external_sources_settings" msgid="4046964413071713807">"සැකසීම්"</string> <string name="wear_app_channel" msgid="1960809674709107850">"Wear යෙදුම් ස්ථාපනය/අස්ථාපනය කරමින්"</string> + <string name="app_installed_notification_channel_description" msgid="2695385797601574123">"යෙදුම් ස්ථාපනය කළ දැනුම්දීම"</string> + <string name="notification_installation_success_message" msgid="6450467996056038442">"සාර්ථකව ස්ථාපනය කරන ලදී"</string> + <string name="notification_installation_success_status" msgid="3172502643504323321">"\"<xliff:g id="APPNAME">%1$s</xliff:g>\" සාර්ථකව ස්ථාපනය කරන ලදි"</string> </resources> diff --git a/packages/PackageInstaller/res/values-sk/strings.xml b/packages/PackageInstaller/res/values-sk/strings.xml index f8e1e019848c..36614bcc41c3 100644 --- a/packages/PackageInstaller/res/values-sk/strings.xml +++ b/packages/PackageInstaller/res/values-sk/strings.xml @@ -88,4 +88,7 @@ <string name="anonymous_source_continue" msgid="4375745439457209366">"Pokračovať"</string> <string name="external_sources_settings" msgid="4046964413071713807">"Nastavenia"</string> <string name="wear_app_channel" msgid="1960809674709107850">"Inštalácia/odinštalovanie aplikácií Wear"</string> + <string name="app_installed_notification_channel_description" msgid="2695385797601574123">"Upozornenie na inštaláciu aplikácie"</string> + <string name="notification_installation_success_message" msgid="6450467996056038442">"Úspešne nainštalovaná"</string> + <string name="notification_installation_success_status" msgid="3172502643504323321">"Aplikácia <xliff:g id="APPNAME">%1$s</xliff:g> bola nainštalovaná"</string> </resources> diff --git a/packages/PackageInstaller/res/values-sl/strings.xml b/packages/PackageInstaller/res/values-sl/strings.xml index d67edd503a6f..a679e089d188 100644 --- a/packages/PackageInstaller/res/values-sl/strings.xml +++ b/packages/PackageInstaller/res/values-sl/strings.xml @@ -88,4 +88,7 @@ <string name="anonymous_source_continue" msgid="4375745439457209366">"Naprej"</string> <string name="external_sources_settings" msgid="4046964413071713807">"Nastavitve"</string> <string name="wear_app_channel" msgid="1960809674709107850">"Nameščanje/odstranjev. aplikacij za Wear"</string> + <string name="app_installed_notification_channel_description" msgid="2695385797601574123">"Obvestilo o namestitvi aplikacije"</string> + <string name="notification_installation_success_message" msgid="6450467996056038442">"Uspešno nameščeno"</string> + <string name="notification_installation_success_status" msgid="3172502643504323321">"Uspešno nameščeno: »<xliff:g id="APPNAME">%1$s</xliff:g>«"</string> </resources> diff --git a/packages/PackageInstaller/res/values-sq/strings.xml b/packages/PackageInstaller/res/values-sq/strings.xml index 7c0665625f6b..dd35024c7b37 100644 --- a/packages/PackageInstaller/res/values-sq/strings.xml +++ b/packages/PackageInstaller/res/values-sq/strings.xml @@ -88,4 +88,7 @@ <string name="anonymous_source_continue" msgid="4375745439457209366">"Vazhdo"</string> <string name="external_sources_settings" msgid="4046964413071713807">"Cilësimet"</string> <string name="wear_app_channel" msgid="1960809674709107850">"Instalimi/çinstalimi i aplikacioneve të Wear"</string> + <string name="app_installed_notification_channel_description" msgid="2695385797601574123">"Njoftimi për aplikacionin e instaluar"</string> + <string name="notification_installation_success_message" msgid="6450467996056038442">"U instalua me sukses"</string> + <string name="notification_installation_success_status" msgid="3172502643504323321">"“<xliff:g id="APPNAME">%1$s</xliff:g>” u instalua me sukses"</string> </resources> diff --git a/packages/PackageInstaller/res/values-sr/strings.xml b/packages/PackageInstaller/res/values-sr/strings.xml index 9ff859a16453..c25b8e0fd422 100644 --- a/packages/PackageInstaller/res/values-sr/strings.xml +++ b/packages/PackageInstaller/res/values-sr/strings.xml @@ -88,4 +88,7 @@ <string name="anonymous_source_continue" msgid="4375745439457209366">"Настави"</string> <string name="external_sources_settings" msgid="4046964413071713807">"Подешавања"</string> <string name="wear_app_channel" msgid="1960809674709107850">"Инсталирање/деинсталирање Wear апликац."</string> + <string name="app_installed_notification_channel_description" msgid="2695385797601574123">"Обавештење о инсталирању апликације"</string> + <string name="notification_installation_success_message" msgid="6450467996056038442">"Инсталирана је"</string> + <string name="notification_installation_success_status" msgid="3172502643504323321">"Апликација „<xliff:g id="APPNAME">%1$s</xliff:g>“ је инсталирана"</string> </resources> diff --git a/packages/PackageInstaller/res/values-sv/strings.xml b/packages/PackageInstaller/res/values-sv/strings.xml index 43c2aadba049..0c66b53cf987 100644 --- a/packages/PackageInstaller/res/values-sv/strings.xml +++ b/packages/PackageInstaller/res/values-sv/strings.xml @@ -88,4 +88,7 @@ <string name="anonymous_source_continue" msgid="4375745439457209366">"Fortsätt"</string> <string name="external_sources_settings" msgid="4046964413071713807">"Inställningar"</string> <string name="wear_app_channel" msgid="1960809674709107850">"Wear-appar installeras/avinstalleras"</string> + <string name="app_installed_notification_channel_description" msgid="2695385797601574123">"Avisering om installerad app"</string> + <string name="notification_installation_success_message" msgid="6450467996056038442">"Appen har installerats"</string> + <string name="notification_installation_success_status" msgid="3172502643504323321">"<xliff:g id="APPNAME">%1$s</xliff:g> har installerats"</string> </resources> diff --git a/packages/PackageInstaller/res/values-sw/strings.xml b/packages/PackageInstaller/res/values-sw/strings.xml index 1c07291a6269..4557f662bec4 100644 --- a/packages/PackageInstaller/res/values-sw/strings.xml +++ b/packages/PackageInstaller/res/values-sw/strings.xml @@ -88,4 +88,7 @@ <string name="anonymous_source_continue" msgid="4375745439457209366">"Endelea"</string> <string name="external_sources_settings" msgid="4046964413071713807">"Mipangilio"</string> <string name="wear_app_channel" msgid="1960809674709107850">"Inasakinisha/inaondoa programu za Android Wear"</string> + <string name="app_installed_notification_channel_description" msgid="2695385797601574123">"Arifa ya kusakinishwa kwa programu"</string> + <string name="notification_installation_success_message" msgid="6450467996056038442">"Imesakinishwa"</string> + <string name="notification_installation_success_status" msgid="3172502643504323321">"<xliff:g id="APPNAME">%1$s</xliff:g> imesakinishwa"</string> </resources> diff --git a/packages/PackageInstaller/res/values-ta/strings.xml b/packages/PackageInstaller/res/values-ta/strings.xml index c067bd29f093..dab459a35606 100644 --- a/packages/PackageInstaller/res/values-ta/strings.xml +++ b/packages/PackageInstaller/res/values-ta/strings.xml @@ -29,9 +29,9 @@ <string name="install_failed" msgid="5777824004474125469">"ஆப்ஸ் நிறுவப்படவில்லை."</string> <string name="install_failed_blocked" msgid="8512284352994752094">"இந்தத் தொகுப்பு நிறுவப்படுவதிலிருந்து தடுக்கப்பட்டது."</string> <string name="install_failed_conflict" msgid="3493184212162521426">"இந்தத் தொகுப்பு ஏற்கனவே உள்ள தொகுப்புடன் முரண்படுவதால் ஆப்ஸ் நிறுவப்படவில்லை."</string> - <string name="install_failed_incompatible" product="tablet" msgid="6019021440094927928">"ஆப்ஸ் உங்கள் டேப்லெட்டுடன் இணக்கமற்றதாக உள்ளதால் நிறுவப்படவில்லை."</string> - <string name="install_failed_incompatible" product="tv" msgid="2890001324362291683">"இந்த ஆப்ஸ் உங்கள் டிவியுடன் இணக்கமற்றதாக உள்ளது."</string> - <string name="install_failed_incompatible" product="default" msgid="7254630419511645826">"ஆப்ஸ் உங்கள் மொபைலுடன் இணக்கமற்றதாக உள்ளதால் நிறுவப்படவில்லை."</string> + <string name="install_failed_incompatible" product="tablet" msgid="6019021440094927928">"ஆப்ஸ் உங்கள் டேப்லெட்டில் இயங்குமாறு இல்லாததால் நிறுவப்படவில்லை."</string> + <string name="install_failed_incompatible" product="tv" msgid="2890001324362291683">"இந்த ஆப்ஸ் உங்கள் டிவியில் இயங்காது."</string> + <string name="install_failed_incompatible" product="default" msgid="7254630419511645826">"ஆப்ஸ் உங்கள் மொபைலில் இயங்குமாறு இல்லாததால் நிறுவப்படவில்லை."</string> <string name="install_failed_invalid_apk" msgid="8581007676422623930">"இந்தத் தொகுப்பு செல்லாததுபோல் இருப்பதால் ஆப்ஸ் நிறுவப்படவில்லை."</string> <string name="install_failed_msg" product="tablet" msgid="6298387264270562442">"<xliff:g id="APP_NAME">%1$s</xliff:g> ஆப்ஸை உங்கள் டேப்லெட்டில் நிறுவ இயலவில்லை."</string> <string name="install_failed_msg" product="tv" msgid="1920009940048975221">"<xliff:g id="APP_NAME">%1$s</xliff:g> ஆப்ஸை உங்கள் டிவியில் நிறுவ இயலவில்லை."</string> @@ -51,7 +51,7 @@ <string name="generic_error_dlg_title" msgid="5863195085927067752">"பிழை"</string> <string name="generic_error_dlg_text" msgid="5287861443265795232">"ஆப்ஸை நிறுவல் நீக்க இயலவில்லை."</string> <string name="uninstall_application_title" msgid="4045420072401428123">"ஆப்ஸை நிறுவல் நீக்குதல்"</string> - <string name="uninstall_update_title" msgid="824411791011583031">"புதுப்பிப்பை நிறுவல் நீக்கு"</string> + <string name="uninstall_update_title" msgid="824411791011583031">"புதுப்பிப்பை நிறுவல் நீக்குதல்"</string> <string name="uninstall_activity_text" msgid="1928194674397770771">"<xliff:g id="ACTIVITY_NAME">%1$s</xliff:g> என்பது பின்வரும் ஆப்ஸின் பகுதியாகும்:"</string> <string name="uninstall_application_text" msgid="3816830743706143980">"இந்த ஆப்ஸை நிறுவல் நீக்க விரும்புகிறீர்களா?"</string> <string name="uninstall_application_text_all_users" msgid="575491774380227119">"இந்த ஆப்ஸை "<b>"அனைத்துப்"</b>" பயனர்களுக்கும் நிறுவல் நீக்க விரும்புகிறீர்களா? ஆப்ஸும் அதன் தரவும் சாதனத்திலுள்ள "<b>"அனைத்துப்"</b>" பயனர்களிடமிருந்தும் அகற்றப்படும்."</string> @@ -68,24 +68,27 @@ <string name="uninstall_failed_app" msgid="5506028705017601412">"<xliff:g id="PACKAGE_LABEL">%1$s</xliff:g>ஐ நிறுவல் நீக்குவதில் தோல்வி."</string> <string name="uninstall_failed_device_policy_manager" msgid="785293813665540305">"செயலிலுள்ள \'சாதன நிர்வாகி ஆப்ஸை\' நிறுவல் நீக்க இயலாது"</string> <string name="uninstall_failed_device_policy_manager_of_user" msgid="4813104025494168064">"<xliff:g id="USERNAME">%1$s</xliff:g> என்பவருக்குச் \'செயலிலுள்ள சாதன நிர்வாகி ஆப்ஸை’ நிறுவல் நீக்க இயலாது"</string> - <string name="uninstall_all_blocked_profile_owner" msgid="2009393666026751501">"இந்த ஆப்ஸ் சில பயனர்களுக்கோ சுயவிவரங்களுக்கோ தேவைப்படுகிறது. மற்றவர்களுக்கு இது நிறுவல் நீக்கப்பட்டது"</string> - <string name="uninstall_blocked_profile_owner" msgid="6373897407002404848">"உங்கள் சுயவிவரத்திற்கு இந்த ஆப்ஸ் தேவைப்படுவதால் இதை நிறுவல் நீக்க இயலாது."</string> + <string name="uninstall_all_blocked_profile_owner" msgid="2009393666026751501">"இந்த ஆப்ஸ் சில பயனர்களுக்கோ கணக்குகளுக்கோ தேவைப்படுகிறது. மற்றவர்களுக்கு இது நிறுவல் நீக்கப்பட்டது"</string> + <string name="uninstall_blocked_profile_owner" msgid="6373897407002404848">"உங்கள் கணக்கிற்கு இந்த ஆப்ஸ் தேவைப்படுவதால் இதை நிறுவல் நீக்க இயலாது."</string> <string name="uninstall_blocked_device_owner" msgid="6724602931761073901">"உங்கள் சாதன நிர்வாகிக்கு இந்த ஆப்ஸ் தேவைப்படுவதால் இதை நிறுவல் நீக்க இயலாது."</string> <string name="manage_device_administrators" msgid="3092696419363842816">"’சாதன நிர்வாகி ஆப்ஸை’ நிர்வகி"</string> <string name="manage_users" msgid="1243995386982560813">"\'பயனர்களை\' நிர்வகி"</string> <string name="uninstall_failed_msg" msgid="2176744834786696012">"<xliff:g id="APP_NAME">%1$s</xliff:g> ஆப்ஸை நிறுவல் நீக்க இயலவில்லை."</string> <string name="Parse_error_dlg_text" msgid="1661404001063076789">"தொகுப்பைப் பாகுபடுத்திப் பார்ப்பதில் சிக்கல் ஏற்பட்டது."</string> <string name="wear_not_allowed_dlg_title" msgid="8664785993465117517">"Android Wear"</string> - <string name="wear_not_allowed_dlg_text" msgid="704615521550939237">"நிறுவல்கள்/நிறுவல் நீக்கங்கள் Wearரில் செய்ய இயலாது"</string> + <string name="wear_not_allowed_dlg_text" msgid="704615521550939237">"Wearரில் நிறுவல்கள்/நிறுவல் நீக்கங்கள் செய்ய இயலாது"</string> <string name="message_staging" msgid="8032722385658438567">"ஆப்ஸ் தயாராகிறது…"</string> <string name="app_name_unknown" msgid="6881210203354323926">"அறியப்படாதது"</string> - <string name="untrusted_external_source_warning" product="tablet" msgid="6539403649459942547">"உங்கள் சாதனத்தின் பாதுகாப்பிற்காக, இந்த மூலத்திலிருந்து பெற்ற அறியப்படாத ஆப்ஸை டேப்லெட்டில் நிறுவ அனுமதியில்லை."</string> - <string name="untrusted_external_source_warning" product="tv" msgid="1206648674551321364">"உங்கள் சாதனத்தின் பாதுகாப்பிற்காக, இந்த மூலத்திலிருந்து பெற்ற அறியப்படாத ஆப்ஸை டிவியில் நிறுவ அனுமதியில்லை."</string> - <string name="untrusted_external_source_warning" product="default" msgid="7279739265754475165">"உங்கள் சாதனத்தின் பாதுகாப்பிற்காக, இந்த மூலத்திலிருந்து பெற்ற அறியப்படாத ஆப்ஸை மொபைலில் நிறுவ அனுமதியில்லை."</string> - <string name="anonymous_source_warning" product="default" msgid="2784902545920822500">"அறியப்படாத ஆப்ஸால் உங்கள் மொபைலும் தனிப்பட்ட தரவும் மிக எளிதாகப் பாதிப்புக்குள்ளாகும். இந்த ஆப்ஸை நிறுவுவதன் மூலம், இதைப் பயன்படுத்தும்போது மொபைலில் ஏதேனும் சிக்கல் ஏற்பட்டாலோ உங்களது தரவை இழந்தாலோ அதற்கு நீங்களே பொறுப்பாவீர்கள் என்பதை ஏற்கிறீர்கள்."</string> - <string name="anonymous_source_warning" product="tablet" msgid="3939101621438855516">"அறியப்படாத ஆப்ஸால் உங்கள் டேப்லெட்டும் தனிப்பட்ட தரவும் மிக எளிதாகப் பாதிப்புக்குள்ளாகும். இந்த ஆப்ஸை நிறுவுவதன் மூலம், இதைப் பயன்படுத்தும்போது டேப்லெட்டில் ஏதேனும் சிக்கல் ஏற்பட்டாலோ உங்களது தரவை இழந்தாலோ அதற்கு நீங்களே பொறுப்பாவீர்கள் என்பதை ஏற்கிறீர்கள்."</string> - <string name="anonymous_source_warning" product="tv" msgid="5599483539528168566">"அறியப்படாத ஆப்ஸால் உங்கள் டிவியும் தனிப்பட்ட தரவும் மிக எளிதாகப் பாதிப்புக்குள்ளாகும். இந்த ஆப்ஸை நிறுவுவதன் மூலம், இதைப் பயன்படுத்தும்போது டிவியில் ஏதேனும் சிக்கல் ஏற்பட்டாலோ உங்களது தரவை இழந்தாலோ அதற்கு நீங்களே பொறுப்பாவீர்கள் என்பதை ஏற்கிறீர்கள்."</string> + <string name="untrusted_external_source_warning" product="tablet" msgid="6539403649459942547">"உங்கள் பாதுகாப்பிற்காக, இந்த மூலத்திலிருந்து பெற்ற அறியப்படாத ஆப்ஸை டேப்லெட்டில் நிறுவ அனுமதியில்லை."</string> + <string name="untrusted_external_source_warning" product="tv" msgid="1206648674551321364">"உங்கள் பாதுகாப்பிற்காக, இந்த மூலத்திலிருந்து பெற்ற அறியப்படாத ஆப்ஸை டிவியில் நிறுவ அனுமதியில்லை."</string> + <string name="untrusted_external_source_warning" product="default" msgid="7279739265754475165">"உங்கள் பாதுகாப்பிற்காக, இந்த மூலத்திலிருந்து பெற்ற அறியப்படாத ஆப்ஸை மொபைலில் நிறுவ அனுமதியில்லை."</string> + <string name="anonymous_source_warning" product="default" msgid="2784902545920822500">"அறியப்படாத ஆப்ஸால் உங்கள் மொபைலும் தனிப்பட்ட தரவும் மிக எளிதாகப் பாதிப்புக்குள்ளாகலாம். இந்த ஆப்ஸை நிறுவுவதன் மூலம், இதைப் பயன்படுத்தும்போது மொபைலில் ஏதேனும் சிக்கல் ஏற்பட்டாலோ உங்களது தரவை இழந்தாலோ அதற்கு நீங்களே பொறுப்பாவீர்கள் என்பதை ஏற்கிறீர்கள்."</string> + <string name="anonymous_source_warning" product="tablet" msgid="3939101621438855516">"அறியப்படாத ஆப்ஸால் உங்கள் டேப்லெட்டும் தனிப்பட்ட தரவும் மிக எளிதாகப் பாதிப்புக்குள்ளாகலாம். இந்த ஆப்ஸை நிறுவுவதன் மூலம், இதைப் பயன்படுத்தும்போது டேப்லெட்டில் ஏதேனும் சிக்கல் ஏற்பட்டாலோ உங்களது தரவை இழந்தாலோ அதற்கு நீங்களே பொறுப்பாவீர்கள் என்பதை ஏற்கிறீர்கள்."</string> + <string name="anonymous_source_warning" product="tv" msgid="5599483539528168566">"அறியப்படாத ஆப்ஸால் உங்கள் டிவியும் தனிப்பட்ட தரவும் மிக எளிதாகப் பாதிப்புக்குள்ளாகலாம். இந்த ஆப்ஸை நிறுவுவதன் மூலம், இதைப் பயன்படுத்தும்போது டிவியில் ஏதேனும் சிக்கல் ஏற்பட்டாலோ உங்களது தரவை இழந்தாலோ அதற்கு நீங்களே பொறுப்பாவீர்கள் என்பதை ஏற்கிறீர்கள்."</string> <string name="anonymous_source_continue" msgid="4375745439457209366">"தொடர்க"</string> <string name="external_sources_settings" msgid="4046964413071713807">"அமைப்புகள்"</string> <string name="wear_app_channel" msgid="1960809674709107850">"Wear ஆப்ஸை நிறுவுதல்/நிறுவல் நீக்குதல்"</string> + <string name="app_installed_notification_channel_description" msgid="2695385797601574123">"ஆப்ஸ் நிறுவப்பட்டது குறித்த அறிவிப்பு"</string> + <string name="notification_installation_success_message" msgid="6450467996056038442">"வெற்றிகரமாக நிறுவப்பட்டது"</string> + <string name="notification_installation_success_status" msgid="3172502643504323321">"\"<xliff:g id="APPNAME">%1$s</xliff:g>\" வெற்றிகரமாக நிறுவப்பட்டது"</string> </resources> diff --git a/packages/PackageInstaller/res/values-te/strings.xml b/packages/PackageInstaller/res/values-te/strings.xml index fd0a63f6befe..64c5da7ef36d 100644 --- a/packages/PackageInstaller/res/values-te/strings.xml +++ b/packages/PackageInstaller/res/values-te/strings.xml @@ -88,4 +88,7 @@ <string name="anonymous_source_continue" msgid="4375745439457209366">"కొనసాగించు"</string> <string name="external_sources_settings" msgid="4046964413071713807">"సెట్టింగ్లు"</string> <string name="wear_app_channel" msgid="1960809674709107850">"Wear యాప్లను ఇన్స్టాల్/అన్ఇన్స్టాల్ చేస్తోంది"</string> + <string name="app_installed_notification_channel_description" msgid="2695385797601574123">"యాప్ ఇన్స్టాల్ చేయబడిందనే నోటిఫికేషన్"</string> + <string name="notification_installation_success_message" msgid="6450467996056038442">"విజయవంతంగా ఇన్స్టాల్ చేయబడింది"</string> + <string name="notification_installation_success_status" msgid="3172502643504323321">"“<xliff:g id="APPNAME">%1$s</xliff:g>” విజయవంతంగా ఇన్స్టాల్ చేయబడింది"</string> </resources> diff --git a/packages/PackageInstaller/res/values-th/strings.xml b/packages/PackageInstaller/res/values-th/strings.xml index a1f537f5bbe2..b6595d0bd0c7 100644 --- a/packages/PackageInstaller/res/values-th/strings.xml +++ b/packages/PackageInstaller/res/values-th/strings.xml @@ -88,4 +88,7 @@ <string name="anonymous_source_continue" msgid="4375745439457209366">"ดำเนินการต่อ"</string> <string name="external_sources_settings" msgid="4046964413071713807">"การตั้งค่า"</string> <string name="wear_app_channel" msgid="1960809674709107850">"กำลังติดตั้ง/ถอนการติดตั้งแอป Wear"</string> + <string name="app_installed_notification_channel_description" msgid="2695385797601574123">"การแจ้งเตือนว่าติดตั้งแอปแล้ว"</string> + <string name="notification_installation_success_message" msgid="6450467996056038442">"ติดตั้งเรียบร้อยแล้ว"</string> + <string name="notification_installation_success_status" msgid="3172502643504323321">"ติดตั้ง “<xliff:g id="APPNAME">%1$s</xliff:g>” สำเร็จแล้ว"</string> </resources> diff --git a/packages/PackageInstaller/res/values-tl/strings.xml b/packages/PackageInstaller/res/values-tl/strings.xml index eace11e4e748..e7b15fa2688f 100644 --- a/packages/PackageInstaller/res/values-tl/strings.xml +++ b/packages/PackageInstaller/res/values-tl/strings.xml @@ -88,4 +88,7 @@ <string name="anonymous_source_continue" msgid="4375745439457209366">"Magpatuloy"</string> <string name="external_sources_settings" msgid="4046964413071713807">"Mga Setting"</string> <string name="wear_app_channel" msgid="1960809674709107850">"Ini-install/ina-uninstall ang wear app"</string> + <string name="app_installed_notification_channel_description" msgid="2695385797601574123">"Notification na na-install ang app"</string> + <string name="notification_installation_success_message" msgid="6450467996056038442">"Matagumpay na na-install"</string> + <string name="notification_installation_success_status" msgid="3172502643504323321">"Matagumpay na na-install ang “<xliff:g id="APPNAME">%1$s</xliff:g>”"</string> </resources> diff --git a/packages/PackageInstaller/res/values-tr/strings.xml b/packages/PackageInstaller/res/values-tr/strings.xml index 99575e0057e1..33dbc3a9161d 100644 --- a/packages/PackageInstaller/res/values-tr/strings.xml +++ b/packages/PackageInstaller/res/values-tr/strings.xml @@ -88,4 +88,7 @@ <string name="anonymous_source_continue" msgid="4375745439457209366">"Devam"</string> <string name="external_sources_settings" msgid="4046964413071713807">"Ayarlar"</string> <string name="wear_app_channel" msgid="1960809674709107850">"Wear uygulamalarını yükleme/kaldırma"</string> + <string name="app_installed_notification_channel_description" msgid="2695385797601574123">"Uygulama yükleme bildirimi"</string> + <string name="notification_installation_success_message" msgid="6450467996056038442">"Başarıyla yüklendi"</string> + <string name="notification_installation_success_status" msgid="3172502643504323321">"\"<xliff:g id="APPNAME">%1$s</xliff:g>\" başarıyla yüklendi"</string> </resources> diff --git a/packages/PackageInstaller/res/values-uk/strings.xml b/packages/PackageInstaller/res/values-uk/strings.xml index b3bebf11d61d..d9cb954786a8 100644 --- a/packages/PackageInstaller/res/values-uk/strings.xml +++ b/packages/PackageInstaller/res/values-uk/strings.xml @@ -88,4 +88,7 @@ <string name="anonymous_source_continue" msgid="4375745439457209366">"Продовжити"</string> <string name="external_sources_settings" msgid="4046964413071713807">"Налаштування"</string> <string name="wear_app_channel" msgid="1960809674709107850">"Встановлення або видалення додатків Wear"</string> + <string name="app_installed_notification_channel_description" msgid="2695385797601574123">"Сповіщення: додаток установлено"</string> + <string name="notification_installation_success_message" msgid="6450467996056038442">"Установлено"</string> + <string name="notification_installation_success_status" msgid="3172502643504323321">"Додаток <xliff:g id="APPNAME">%1$s</xliff:g> установлено"</string> </resources> diff --git a/packages/PackageInstaller/res/values-ur/strings.xml b/packages/PackageInstaller/res/values-ur/strings.xml new file mode 100644 index 000000000000..61b15790c3d9 --- /dev/null +++ b/packages/PackageInstaller/res/values-ur/strings.xml @@ -0,0 +1,94 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- Copyright (C) 2007 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="app_name" msgid="7488448184431507488">"پیکیج انسٹالر"</string> + <string name="install" msgid="711829760615509273">"انسٹال کریں"</string> + <string name="done" msgid="6632441120016885253">"ہو گیا"</string> + <string name="cancel" msgid="1018267193425558088">"منسوخ کریں"</string> + <string name="installing" msgid="4921993079741206516">"انسٹال ہو رہی ہے…"</string> + <string name="installing_app" msgid="1165095864863849422">"<xliff:g id="PACKAGE_LABEL">%1$s</xliff:g> کو انسٹال کیا جا رہا ہے…"</string> + <string name="install_done" msgid="5987363587661783896">"ایپ انسٹال ہو گئی۔"</string> + <string name="install_confirm_question" msgid="8176284075816604590">"کیا آپ یہ ایپلیکیشن انسٹال کرنا چاہتے ہیں؟"</string> + <string name="install_confirm_question_update" msgid="7942235418781274635">"کیا آپ اس موجودہ ایپلیکیشن میں ایک اپ ڈیٹ انسٹال کرنا چاہتے ہیں؟ آپ کا موجودہ ڈیٹا ضائع نہیں ہوگا۔"</string> + <string name="install_confirm_question_update_system" msgid="4713001702777910263">"کیا آپ پہلے سے شامل اس ایپلیکیشن میں ایک اپ ڈیٹ انسٹال کرنا چاہتے ہیں؟ آپ کا موجودہ ڈیٹا ضائع نہیں ہوگا۔"</string> + <string name="install_failed" msgid="5777824004474125469">"ایپ انسٹال نہیں ہوئی۔"</string> + <string name="install_failed_blocked" msgid="8512284352994752094">"پیکج کو انسٹال ہونے سے مسدود کر دیا گیا تھا۔"</string> + <string name="install_failed_conflict" msgid="3493184212162521426">"ایپ انسٹال نہیں ہوئی کیونکہ پیکج ایک موجودہ پیکیج سے متصادم ہے۔"</string> + <string name="install_failed_incompatible" product="tablet" msgid="6019021440094927928">"ایپ انسٹال نہیں ہوئی کیونکہ ایپ آپ کے ٹیبلیٹ کے ساتھ مطابقت پذیر نہیں ہے۔"</string> + <string name="install_failed_incompatible" product="tv" msgid="2890001324362291683">"یہ ایپ آپ کے TV کے ساتھ مطابقت پذیر نہیں ہے۔"</string> + <string name="install_failed_incompatible" product="default" msgid="7254630419511645826">"ایپ انسٹال نہیں ہوئی کیونکہ ایپ آپ کے فون کے ساتھ مطابقت پذیر نہیں ہے۔"</string> + <string name="install_failed_invalid_apk" msgid="8581007676422623930">"ایپ انسٹال نہیں ہوئی کیونکہ پیکیج غلط معلوم ہوتا ہے۔"</string> + <string name="install_failed_msg" product="tablet" msgid="6298387264270562442">"<xliff:g id="APP_NAME">%1$s</xliff:g> کو آپ کے ٹیبلیٹ پر انسٹال نہیں کیا جا سکا۔"</string> + <string name="install_failed_msg" product="tv" msgid="1920009940048975221">"<xliff:g id="APP_NAME">%1$s</xliff:g> کو آپ کے TV پر انسٹال نہیں کیا جا سکا۔"</string> + <string name="install_failed_msg" product="default" msgid="6484461562647915707">"<xliff:g id="APP_NAME">%1$s</xliff:g> کو آپ کے فون پر انسٹال نہیں کیا جا سکا۔"</string> + <string name="launch" msgid="3952550563999890101">"کھولیں"</string> + <string name="unknown_apps_admin_dlg_text" msgid="4456572224020176095">"آپ کا منتظم نامعلوم ذرائع سے حاصل شدہ ایپس کو انسٹال کرنے کی اجازت نہیں دیتا ہے"</string> + <string name="unknown_apps_user_restriction_dlg_text" msgid="151020786933988344">"یہ صارف نامعلوم ایپس کو انسٹال نہیں کر سکتا"</string> + <string name="install_apps_user_restriction_dlg_text" msgid="2154119597001074022">"اس صارف کو ایپس انسٹال کرنے کی اجازت نہیں ہے"</string> + <string name="ok" msgid="7871959885003339302">"ٹھیک ہے"</string> + <string name="manage_applications" msgid="5400164782453975580">"ایپس منظم کریں"</string> + <string name="out_of_space_dlg_title" msgid="4156690013884649502">"جگہ نہیں ہے"</string> + <string name="out_of_space_dlg_text" msgid="8727714096031856231">"<xliff:g id="APP_NAME">%1$s</xliff:g> کو انسٹال نہیں کیا جا سکا۔ کچھ جگہ خالی کریں اور دوبارہ کوشش کریں۔"</string> + <string name="app_not_found_dlg_title" msgid="5107924008597470285">"ایپ نہیں ملی"</string> + <string name="app_not_found_dlg_text" msgid="5219983779377811611">"ایپ انسٹال کردہ ایپس کی فہرست میں نہیں ملی۔"</string> + <string name="user_is_not_allowed_dlg_title" msgid="6915293433252210232">"اجازت نہیں ہے"</string> + <string name="user_is_not_allowed_dlg_text" msgid="3468447791330611681">"موجودہ صارف کو اس اَن انسٹالیشن کو انجام دینے کی اجازت نہیں ہے۔"</string> + <string name="generic_error_dlg_title" msgid="5863195085927067752">"خرابی"</string> + <string name="generic_error_dlg_text" msgid="5287861443265795232">"ایپ اَن انسٹال نہیں ہو سکی۔"</string> + <string name="uninstall_application_title" msgid="4045420072401428123">"ایپ کو اَن انسٹال کریں"</string> + <string name="uninstall_update_title" msgid="824411791011583031">"اپ ڈیٹ اَن انسٹال کریں"</string> + <string name="uninstall_activity_text" msgid="1928194674397770771">"<xliff:g id="ACTIVITY_NAME">%1$s</xliff:g> درج ذیل ایپ کا حصہ ہے:"</string> + <string name="uninstall_application_text" msgid="3816830743706143980">"کیا آپ اس ایپ کو اَن انسٹال کرنا چاہتے ہیں؟"</string> + <string name="uninstall_application_text_all_users" msgid="575491774380227119">"کیا آپ "<b>"سبھی"</b>" صارفین کیلئے اس ایپ کو اَن انسٹال کرنا چاہتے ہیں؟ ایپلیکیشن اور اس کا ڈیٹا آلہ پر موجود "<b>"سبھی"</b>" صارفین سے ہٹا دیا جائے گا۔"</string> + <string name="uninstall_application_text_user" msgid="498072714173920526">"کیا آپ اس ایپ کو صارف <xliff:g id="USERNAME">%1$s</xliff:g> کیلئے اَن انسٹال کرنا چاہتے ہیں؟"</string> + <string name="uninstall_update_text" msgid="863648314632448705">"اس ایپ کو فیکٹری ورژن سے تبدیل کریں؟ تمام ڈیٹا ہٹا دیا جائے گا۔"</string> + <string name="uninstall_update_text_multiuser" msgid="8992883151333057227">"اس ایپ کو فیکٹری ورژن سے تبدیل کریں؟ تمام ڈیٹا ہٹا دیا جائے گا۔ اس سے دفتری پروفائلز کے حاملین سمیت اس آلہ کے تمام صارفین متاثر ہوں گے۔"</string> + <string name="uninstalling_notification_channel" msgid="840153394325714653">"چل رہے اَن انسٹالز"</string> + <string name="uninstall_failure_notification_channel" msgid="1136405866767576588">"ناکام اَن انسٹالز"</string> + <string name="uninstalling" msgid="8709566347688966845">"اَن انسٹال ہو رہا ہے…"</string> + <string name="uninstalling_app" msgid="8866082646836981397">"<xliff:g id="PACKAGE_LABEL">%1$s</xliff:g> ان انسٹال ہو رہا ہے…"</string> + <string name="uninstall_done" msgid="439354138387969269">"اَن انسٹال مکمل ہو گیا۔"</string> + <string name="uninstall_done_app" msgid="4588850984473605768">"<xliff:g id="PACKAGE_LABEL">%1$s</xliff:g> اَن انسٹال ہو گیا"</string> + <string name="uninstall_failed" msgid="1847750968168364332">"اَن انسٹال ناکام ہو گیا۔"</string> + <string name="uninstall_failed_app" msgid="5506028705017601412">"<xliff:g id="PACKAGE_LABEL">%1$s</xliff:g> کو ان انسٹال کرنا ناکام ہو گیا۔"</string> + <string name="uninstall_failed_device_policy_manager" msgid="785293813665540305">"فعال آلہ کے منتظم کی ایپ کو اَن انسٹال نہیں کیا جا سکتا"</string> + <string name="uninstall_failed_device_policy_manager_of_user" msgid="4813104025494168064">"<xliff:g id="USERNAME">%1$s</xliff:g> کی فعال آلہ کے منتظم کی ایپ کو اَن انسٹال نہیں کیا جا سکتا"</string> + <string name="uninstall_all_blocked_profile_owner" msgid="2009393666026751501">"یہ ایپ کچھ صارفین یا پروفائلز کیلئے درکار ہے اور دیگر کیلئے اَن انسٹال کر دی گئی"</string> + <string name="uninstall_blocked_profile_owner" msgid="6373897407002404848">"یہ ایپ آپ کے پروفائل کیلئے درکار ہے اور اسے اَن انسٹال نہیں کیا جا سکتا۔"</string> + <string name="uninstall_blocked_device_owner" msgid="6724602931761073901">"آپ کے آلہ کے منتظم کو اس ایپ کی ضرورت ہے اور اسے اَن انسٹال نہیں کیا جا سکتا۔"</string> + <string name="manage_device_administrators" msgid="3092696419363842816">"آلہ کے منتظم کی ایپس کا نظم کریں"</string> + <string name="manage_users" msgid="1243995386982560813">"صارفین کا نظم کریں"</string> + <string name="uninstall_failed_msg" msgid="2176744834786696012">"<xliff:g id="APP_NAME">%1$s</xliff:g> کو اَن انسٹال نہیں کیا جا سکا۔"</string> + <string name="Parse_error_dlg_text" msgid="1661404001063076789">"پیکیج کو پارس کرنے میں ایک مسئلہ تھا۔"</string> + <string name="wear_not_allowed_dlg_title" msgid="8664785993465117517">"Android Wear"</string> + <string name="wear_not_allowed_dlg_text" msgid="704615521550939237">"\'انسٹال/اَن انسٹال کی کارروائیاں\' Wear پر تعاون یافتہ نہیں ہیں۔"</string> + <string name="message_staging" msgid="8032722385658438567">"ایپ کی ٹیسٹنگ ہو رہی ہے…"</string> + <string name="app_name_unknown" msgid="6881210203354323926">"نامعلوم"</string> + <string name="untrusted_external_source_warning" product="tablet" msgid="6539403649459942547">"آپ کی سیکیورٹی کے مدنظر، آپ کے ٹیبلیٹ کو اس ذریعے سے نامعلوم ایپس انسٹال کرنے کی اجازت نہیں ہے۔"</string> + <string name="untrusted_external_source_warning" product="tv" msgid="1206648674551321364">"آپ کی سیکیورٹی کے مدنظر، آپ کے TV کو اس ذریعے سے نامعلوم ایپس انسٹال کرنے کی اجازت نہیں ہے۔"</string> + <string name="untrusted_external_source_warning" product="default" msgid="7279739265754475165">"آپ کی سیکیورٹی کے مدنظر، آپ کے فون کو اس ذریعے سے نامعلوم ایپس انسٹال کرنے کی اجازت نہیں ہے۔"</string> + <string name="anonymous_source_warning" product="default" msgid="2784902545920822500">"آپ کے فون اور ذاتی ڈیٹا کو نامعلوم ایپس کی جانب سے حملے کا زیادہ خطرہ ہے۔ اس ایپ کو انسٹال کر کے، آپ اس بات سے اتفاق کرتے ہیں کہ آپ اس سے اپنے فون کو ہونے والے کسی بھی نقصان یا ڈیٹا کے نقصان کے لئے خود ذمہ دار ہیں۔"</string> + <string name="anonymous_source_warning" product="tablet" msgid="3939101621438855516">"آپ کے ٹیبلیٹ اور ذاتی ڈیٹا کو نامعلوم ایپس کی جانب سے حملے کا زیادہ خطرہ ہے۔ اس ایپ کو انسٹال کر کے، آپ اس بات سے اتفاق کرتے ہیں کہ آپ اس سے اپنے ٹیبلیٹ کو ہونے والے کسی بھی نقصان یا ڈیٹا کے نقصان کے لئے خود ذمہ دار ہیں۔"</string> + <string name="anonymous_source_warning" product="tv" msgid="5599483539528168566">"آپ کے TV اور ذاتی ڈیٹا کو نامعلوم ایپس کی جانب سے حملے کا زیادہ خطرہ ہے۔ اس ایپ کو انسٹال کر کے، آپ اس بات سے اتفاق کرتے ہیں کہ آپ اس سے اپنے TV کو ہونے والے کسی بھی نقصان یا ڈیٹا کے نقصان کے لئے خود ذمہ دار ہیں۔"</string> + <string name="anonymous_source_continue" msgid="4375745439457209366">"جاری رکھیں"</string> + <string name="external_sources_settings" msgid="4046964413071713807">"ترتیبات"</string> + <string name="wear_app_channel" msgid="1960809674709107850">"wear ایپس کو انسٹال/اَن انسٹال کرنا"</string> + <string name="app_installed_notification_channel_description" msgid="2695385797601574123">"ایپ انسٹال ہونے کی اطلاع"</string> + <string name="notification_installation_success_message" msgid="6450467996056038442">"کامیابی کے ساتھ انسٹال ہو گئی"</string> + <string name="notification_installation_success_status" msgid="3172502643504323321">"\"<xliff:g id="APPNAME">%1$s</xliff:g>\" کامیابی کے ساتھ انسٹال ہو گئی"</string> +</resources> diff --git a/packages/PackageInstaller/res/values-uz/strings.xml b/packages/PackageInstaller/res/values-uz/strings.xml index e692e0d99229..dedf00c86330 100644 --- a/packages/PackageInstaller/res/values-uz/strings.xml +++ b/packages/PackageInstaller/res/values-uz/strings.xml @@ -88,4 +88,7 @@ <string name="anonymous_source_continue" msgid="4375745439457209366">"Davom etish"</string> <string name="external_sources_settings" msgid="4046964413071713807">"Sozlamalar"</string> <string name="wear_app_channel" msgid="1960809674709107850">"Wear ilovalarini o‘rnatish/o‘chirish"</string> + <string name="app_installed_notification_channel_description" msgid="2695385797601574123">"Ilova oʻrnatilgani haqida bildirishnoma"</string> + <string name="notification_installation_success_message" msgid="6450467996056038442">"O‘rnatildi"</string> + <string name="notification_installation_success_status" msgid="3172502643504323321">"“<xliff:g id="APPNAME">%1$s</xliff:g>” o‘rnatildi"</string> </resources> diff --git a/packages/PackageInstaller/res/values-vi/strings.xml b/packages/PackageInstaller/res/values-vi/strings.xml index 91aa71c9390f..7043093c4347 100644 --- a/packages/PackageInstaller/res/values-vi/strings.xml +++ b/packages/PackageInstaller/res/values-vi/strings.xml @@ -88,4 +88,7 @@ <string name="anonymous_source_continue" msgid="4375745439457209366">"Tiếp tục"</string> <string name="external_sources_settings" msgid="4046964413071713807">"Cài đặt"</string> <string name="wear_app_channel" msgid="1960809674709107850">"Cài đặt/gỡ cài đặt ứng dụng Wear"</string> + <string name="app_installed_notification_channel_description" msgid="2695385797601574123">"Thông báo đã cài đặt ứng dụng"</string> + <string name="notification_installation_success_message" msgid="6450467996056038442">"Đã cài đặt thành công"</string> + <string name="notification_installation_success_status" msgid="3172502643504323321">"Đã cài đặt thành công “<xliff:g id="APPNAME">%1$s</xliff:g>”"</string> </resources> diff --git a/packages/PackageInstaller/res/values-zh-rCN/strings.xml b/packages/PackageInstaller/res/values-zh-rCN/strings.xml index d3b5e6849262..a91f00f94f3a 100644 --- a/packages/PackageInstaller/res/values-zh-rCN/strings.xml +++ b/packages/PackageInstaller/res/values-zh-rCN/strings.xml @@ -88,4 +88,7 @@ <string name="anonymous_source_continue" msgid="4375745439457209366">"继续"</string> <string name="external_sources_settings" msgid="4046964413071713807">"设置"</string> <string name="wear_app_channel" msgid="1960809674709107850">"正在安装/卸载 Wear 应用"</string> + <string name="app_installed_notification_channel_description" msgid="2695385797601574123">"“应用已安装”通知"</string> + <string name="notification_installation_success_message" msgid="6450467996056038442">"已成功安装"</string> + <string name="notification_installation_success_status" msgid="3172502643504323321">"“<xliff:g id="APPNAME">%1$s</xliff:g>”安装成功"</string> </resources> diff --git a/packages/PackageInstaller/res/values-zh-rHK/strings.xml b/packages/PackageInstaller/res/values-zh-rHK/strings.xml index bb3605fa6c3e..286e360d601c 100644 --- a/packages/PackageInstaller/res/values-zh-rHK/strings.xml +++ b/packages/PackageInstaller/res/values-zh-rHK/strings.xml @@ -88,4 +88,7 @@ <string name="anonymous_source_continue" msgid="4375745439457209366">"繼續"</string> <string name="external_sources_settings" msgid="4046964413071713807">"設定"</string> <string name="wear_app_channel" msgid="1960809674709107850">"正在安裝/解除安裝 Wear 應用程式"</string> + <string name="app_installed_notification_channel_description" msgid="2695385797601574123">"「應用程式已安裝」通知"</string> + <string name="notification_installation_success_message" msgid="6450467996056038442">"已成功安裝"</string> + <string name="notification_installation_success_status" msgid="3172502643504323321">"「<xliff:g id="APPNAME">%1$s</xliff:g>」已成功安裝"</string> </resources> diff --git a/packages/PackageInstaller/res/values-zh-rTW/strings.xml b/packages/PackageInstaller/res/values-zh-rTW/strings.xml index 513b16aa802d..b0de2c256ce3 100644 --- a/packages/PackageInstaller/res/values-zh-rTW/strings.xml +++ b/packages/PackageInstaller/res/values-zh-rTW/strings.xml @@ -88,4 +88,7 @@ <string name="anonymous_source_continue" msgid="4375745439457209366">"繼續"</string> <string name="external_sources_settings" msgid="4046964413071713807">"設定"</string> <string name="wear_app_channel" msgid="1960809674709107850">"安裝/解除安裝中的 Wear 應用程式"</string> + <string name="app_installed_notification_channel_description" msgid="2695385797601574123">"「應用程式已安裝」通知"</string> + <string name="notification_installation_success_message" msgid="6450467996056038442">"安裝成功"</string> + <string name="notification_installation_success_status" msgid="3172502643504323321">"已成功安裝「<xliff:g id="APPNAME">%1$s</xliff:g>」"</string> </resources> diff --git a/packages/PackageInstaller/res/values-zu/strings.xml b/packages/PackageInstaller/res/values-zu/strings.xml index bc91ed11ae50..5d2847933323 100644 --- a/packages/PackageInstaller/res/values-zu/strings.xml +++ b/packages/PackageInstaller/res/values-zu/strings.xml @@ -88,4 +88,7 @@ <string name="anonymous_source_continue" msgid="4375745439457209366">"Qhubeka"</string> <string name="external_sources_settings" msgid="4046964413071713807">"Izilungiselelo"</string> <string name="wear_app_channel" msgid="1960809674709107850">"Ifaka/ikhipha izinhlelo zokusebenza ze-wear"</string> + <string name="app_installed_notification_channel_description" msgid="2695385797601574123">"Isaziso sokufakwa kohlelo lokusebenza"</string> + <string name="notification_installation_success_message" msgid="6450467996056038442">"Ifakwe ngempumelelo"</string> + <string name="notification_installation_success_status" msgid="3172502643504323321">"Kufakwe ngempumelelo i-\"<xliff:g id="APPNAME">%1$s</xliff:g>\""</string> </resources> diff --git a/packages/PrintSpooler/res/values-mr/strings.xml b/packages/PrintSpooler/res/values-mr/strings.xml index 5593f66fa789..b055d82eea1d 100644 --- a/packages/PrintSpooler/res/values-mr/strings.xml +++ b/packages/PrintSpooler/res/values-mr/strings.xml @@ -88,7 +88,7 @@ <string name="no_connection_to_printer" msgid="2159246915977282728">"प्रिंटरवर कोणतेही कनेक्शन नाही"</string> <string name="reason_unknown" msgid="5507940196503246139">"अज्ञात"</string> <string name="print_service_security_warning_title" msgid="2160752291246775320">"<xliff:g id="SERVICE">%1$s</xliff:g> वापरायची?"</string> - <string name="print_service_security_warning_summary" msgid="1427434625361692006">"आपला दस्तऐवज प्रिंटरपर्यंत पोहचण्यापूर्वी एक किंवा अधिक सर्व्हरद्वारे जाऊ शकतो."</string> + <string name="print_service_security_warning_summary" msgid="1427434625361692006">"तुमचा दस्तऐवज प्रिंटरपर्यंत पोहचण्यापूर्वी एक किंवा अधिक सर्व्हरद्वारे जाऊ शकतो."</string> <string-array name="color_mode_labels"> <item msgid="7602948745415174937">"कृष्ण धवल"</item> <item msgid="2762241247228983754">"रंग"</item> diff --git a/packages/SettingsLib/HelpUtils/res/values-hi/strings.xml b/packages/SettingsLib/HelpUtils/res/values-hi/strings.xml new file mode 100644 index 000000000000..cfc69617404e --- /dev/null +++ b/packages/SettingsLib/HelpUtils/res/values-hi/strings.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + Copyright (C) 2018 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="help_feedback_label" msgid="4550436169116444686">"सहायता और सुझाव"</string> +</resources> diff --git a/packages/SettingsLib/SearchWidget/res/values-or/strings.xml b/packages/SettingsLib/SearchWidget/res/values-or/strings.xml new file mode 100644 index 000000000000..352303748952 --- /dev/null +++ b/packages/SettingsLib/SearchWidget/res/values-or/strings.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + Copyright (C) 2018 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="search_menu" msgid="1604061903696928905">"ସର୍ଚ୍ଚ ସେଟିଙ୍ଗ"</string> +</resources> diff --git a/packages/SettingsLib/SearchWidget/res/values-ur/strings.xml b/packages/SettingsLib/SearchWidget/res/values-ur/strings.xml new file mode 100644 index 000000000000..8b62fd4989a9 --- /dev/null +++ b/packages/SettingsLib/SearchWidget/res/values-ur/strings.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + Copyright (C) 2018 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="search_menu" msgid="1604061903696928905">"ترتیبات تلاش کریں"</string> +</resources> diff --git a/packages/SettingsLib/res/values-ar/strings.xml b/packages/SettingsLib/res/values-ar/strings.xml index 5c11db18d29e..83c4b8ee3b86 100644 --- a/packages/SettingsLib/res/values-ar/strings.xml +++ b/packages/SettingsLib/res/values-ar/strings.xml @@ -240,7 +240,7 @@ <string name="wifi_unmetered_label" msgid="6124098729457992931">"بدون قياس"</string> <string name="select_logd_size_title" msgid="7433137108348553508">"أحجام ذاكرة التخزين المؤقت للتسجيل"</string> <string name="select_logd_size_dialog_title" msgid="1206769310236476760">"حدد أحجامًا أكبر لكل ذاكرة تخزين مؤقت للتسجيل"</string> - <string name="dev_logpersist_clear_warning_title" msgid="684806692440237967">"هل تريد محو سعة التخزين الدائمة للمسجِّل؟"</string> + <string name="dev_logpersist_clear_warning_title" msgid="684806692440237967">"هل تريد محو مساحة التخزين الدائمة للمسجِّل؟"</string> <string name="dev_logpersist_clear_warning_message" msgid="2256582531342994562">"عندما نتوقف عن رصد أي أخطاء باستخدام المسجِّل الدائم مرة أخرى، يتعين علينا محو بيانات المسجِّل الموجودة على جهازك."</string> <string name="select_logpersist_title" msgid="7530031344550073166">"تخزين بيانات المسجِّل باستمرار على الجهاز"</string> <string name="select_logpersist_dialog_title" msgid="4003400579973269060">"تحديد مخازن السجلات المؤقتة المراد تخزينها باستمرار على الجهاز"</string> diff --git a/packages/SettingsLib/res/values-ca/strings.xml b/packages/SettingsLib/res/values-ca/strings.xml index 72dd8cf70dd2..5638947f64b1 100644 --- a/packages/SettingsLib/res/values-ca/strings.xml +++ b/packages/SettingsLib/res/values-ca/strings.xml @@ -446,6 +446,6 @@ <string name="alarm_template_far" msgid="3779172822607461675">"Data: <xliff:g id="WHEN">%1$s</xliff:g>"</string> <string name="zen_mode_duration_settings_title" msgid="229547412251222757">"Durada"</string> <string name="zen_mode_duration_always_prompt_title" msgid="6478923750878945501">"Pregunta sempre"</string> - <string name="zen_mode_forever" msgid="2704305038191592967">"Fins que no ho desactivi"</string> + <string name="zen_mode_forever" msgid="2704305038191592967">"Fins que no ho desactivis"</string> <string name="time_unit_just_now" msgid="6363336622778342422">"Ara mateix"</string> </resources> diff --git a/packages/SettingsLib/res/values-iw/strings.xml b/packages/SettingsLib/res/values-iw/strings.xml index 5a11e721f1b0..5da253c2d4bb 100644 --- a/packages/SettingsLib/res/values-iw/strings.xml +++ b/packages/SettingsLib/res/values-iw/strings.xml @@ -317,7 +317,7 @@ <string name="show_all_anrs" msgid="4924885492787069007">"הצגת מקרי ANR ברקע"</string> <string name="show_all_anrs_summary" msgid="6636514318275139826">"הצגת תיבת דו-שיח של \'אפליקציה לא מגיבה\' עבור אפליקציות שפועלות ברקע"</string> <string name="show_notification_channel_warnings" msgid="1399948193466922683">"אזהרות לגבי ערוץ הודעות"</string> - <string name="show_notification_channel_warnings_summary" msgid="5536803251863694895">"הצגת אזהרה כשאפליקציה שולחת הודעה ללא ערוץ חוקי"</string> + <string name="show_notification_channel_warnings_summary" msgid="5536803251863694895">"הצגת אזהרה כשאפליקציה שולחת התראה ללא ערוץ חוקי"</string> <string name="force_allow_on_external" msgid="3215759785081916381">"אילוץ הרשאת אפליקציות באחסון חיצוני"</string> <string name="force_allow_on_external_summary" msgid="3640752408258034689">"מאפשר כתיבה של כל אפליקציה באחסון חיצוני, ללא התחשבות בערכי המניפסט"</string> <string name="force_resizable_activities" msgid="8615764378147824985">"אלץ יכולת קביעת גודל של הפעילויות"</string> diff --git a/packages/SettingsLib/res/values-sw/strings.xml b/packages/SettingsLib/res/values-sw/strings.xml index df122e283c58..d693077737e2 100644 --- a/packages/SettingsLib/res/values-sw/strings.xml +++ b/packages/SettingsLib/res/values-sw/strings.xml @@ -415,7 +415,7 @@ <string name="screen_zoom_summary_custom" msgid="5611979864124160447">"Kiwango maalum (<xliff:g id="DENSITYDPI">%d</xliff:g>)"</string> <string name="content_description_menu_button" msgid="8182594799812351266">"Menyu"</string> <string name="retail_demo_reset_message" msgid="118771671364131297">"Weka nenosiri ili urejeshe mipangilio ya kiwandani ikiwa katika hali ya onyesho."</string> - <string name="retail_demo_reset_next" msgid="8356731459226304963">"Inayofuata"</string> + <string name="retail_demo_reset_next" msgid="8356731459226304963">"Endelea"</string> <string name="retail_demo_reset_title" msgid="696589204029930100">"Nenosiri linahitajika"</string> <string name="active_input_method_subtypes" msgid="3596398805424733238">"Mbinu zinazotumika"</string> <string name="use_system_language_to_select_input_method_subtypes" msgid="5747329075020379587">"Tumia lugha za mfumo"</string> diff --git a/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java b/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java index 240a19279739..a936df2bf2eb 100644 --- a/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java +++ b/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java @@ -254,7 +254,8 @@ public class ApplicationsState { user.isAdmin() ? mAdminRetrieveFlags : mRetrieveFlags, user.id); mApplications.addAll(list.getList()); - } catch (RemoteException e) { + } catch (Exception e) { + Log.e(TAG, "Error during doResumeIfNeededLocked", e); } } diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothCallback.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothCallback.java index a84222909072..3152e65d5a36 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothCallback.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothCallback.java @@ -127,4 +127,17 @@ public interface BluetoothCallback { default void onProfileConnectionStateChanged(CachedBluetoothDevice cachedDevice, int state, int bluetoothProfile) { } + + /** + * Called when ACL connection state is changed. It listens to + * {@link android.bluetooth.BluetoothDevice#ACTION_ACL_CONNECTED} and {@link + * android.bluetooth.BluetoothDevice#ACTION_ACL_DISCONNECTED} + * + * @param cachedDevice Bluetooth device that changed + * @param state the Bluetooth device connection state, the possible values are: + * {@link android.bluetooth.BluetoothAdapter#STATE_DISCONNECTED}, + * {@link android.bluetooth.BluetoothAdapter#STATE_CONNECTED} + */ + default void onAclConnectionStateChanged(CachedBluetoothDevice cachedDevice, int state) { + } } diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java index 022bf6915886..2b7babd06b47 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java @@ -119,6 +119,10 @@ public class BluetoothEventManager { addHandler(TelephonyManager.ACTION_PHONE_STATE_CHANGED, new AudioModeChangedHandler()); + // ACL connection changed broadcasts + addHandler(BluetoothDevice.ACTION_ACL_CONNECTED, new AclStateChangedHandler()); + addHandler(BluetoothDevice.ACTION_ACL_DISCONNECTED, new AclStateChangedHandler()); + registerAdapterIntentReceiver(); } @@ -236,6 +240,15 @@ public class BluetoothEventManager { } } + private void dispatchAclStateChanged(CachedBluetoothDevice activeDevice, + int state) { + synchronized (mCallbacks) { + for (BluetoothCallback callback : mCallbacks) { + callback.onAclConnectionStateChanged(activeDevice, state); + } + } + } + @VisibleForTesting void addHandler(String action, Handler handler) { mHandlerMap.put(action, handler); @@ -447,6 +460,32 @@ public class BluetoothEventManager { } } + private class AclStateChangedHandler implements Handler { + @Override + public void onReceive(Context context, Intent intent, BluetoothDevice device) { + final String action = intent.getAction(); + if (action == null) { + Log.w(TAG, "AclStateChangedHandler: action is null"); + return; + } + final CachedBluetoothDevice activeDevice = mDeviceManager.findDevice(device); + final int state; + switch (action) { + case BluetoothDevice.ACTION_ACL_CONNECTED: + state = BluetoothAdapter.STATE_CONNECTED; + break; + case BluetoothDevice.ACTION_ACL_DISCONNECTED: + state = BluetoothAdapter.STATE_DISCONNECTED; + break; + default: + Log.w(TAG, "ActiveDeviceChangedHandler: unknown action " + action); + return; + + } + dispatchAclStateChanged(activeDevice, state); + } + } + private class AudioModeChangedHandler implements Handler { @Override diff --git a/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/LogWriter.java b/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/LogWriter.java index 379a820da167..a16838c7fe1f 100644 --- a/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/LogWriter.java +++ b/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/LogWriter.java @@ -55,6 +55,7 @@ public interface LogWriter { /** * Logs an user action. + * * @deprecated use {@link #action(int, int, Pair[])} */ @Deprecated @@ -62,6 +63,7 @@ public interface LogWriter { /** * Logs an user action. + * * @deprecated use {@link #action(int, boolean, Pair[])} */ @Deprecated @@ -76,4 +78,10 @@ public interface LogWriter { * Logs a count. */ void count(Context context, String name, int value); + + /** + * Generically log action into statsd. + */ + default void action(int attribution, int action, int pageId, String key, int value) { + } } diff --git a/packages/SettingsLib/src/com/android/settingslib/license/LicenseHtmlLoader.java b/packages/SettingsLib/src/com/android/settingslib/license/LicenseHtmlLoader.java index 78e807cf1a1c..8c03918ce266 100644 --- a/packages/SettingsLib/src/com/android/settingslib/license/LicenseHtmlLoader.java +++ b/packages/SettingsLib/src/com/android/settingslib/license/LicenseHtmlLoader.java @@ -16,18 +16,11 @@ package com.android.settingslib.license; -import static com.android.settingslib.license.LicenseHtmlLoaderCompat.generateHtmlFile; -import static com.android.settingslib.license.LicenseHtmlLoaderCompat.getCachedHtmlFile; -import static com.android.settingslib.license.LicenseHtmlLoaderCompat.getVaildXmlFiles; -import static com.android.settingslib.license.LicenseHtmlLoaderCompat.isCachedHtmlFileOutdated; - import android.content.Context; -import android.util.Log; import com.android.settingslib.utils.AsyncLoader; import java.io.File; -import java.util.List; /** * LicenseHtmlLoader is a loader which loads a license html file from default license xml files. @@ -44,27 +37,10 @@ public class LicenseHtmlLoader extends AsyncLoader<File> { @Override public File loadInBackground() { - return generateHtmlFromDefaultXmlFiles(); + return new LicenseHtmlLoaderCompat(mContext).loadInBackground(); } @Override protected void onDiscardResult(File f) { } - - private File generateHtmlFromDefaultXmlFiles() { - final List<File> xmlFiles = getVaildXmlFiles(); - if (xmlFiles.isEmpty()) { - Log.e(TAG, "No notice file exists."); - return null; - } - - File cachedHtmlFile = getCachedHtmlFile(mContext); - if (!isCachedHtmlFileOutdated(xmlFiles, cachedHtmlFile) - || generateHtmlFile(mContext, xmlFiles, cachedHtmlFile)) { - return cachedHtmlFile; - } - - return null; - } - } diff --git a/packages/SettingsLib/src/com/android/settingslib/license/LicenseHtmlLoaderCompat.java b/packages/SettingsLib/src/com/android/settingslib/license/LicenseHtmlLoaderCompat.java index ca6248505dc0..0b6996365372 100644 --- a/packages/SettingsLib/src/com/android/settingslib/license/LicenseHtmlLoaderCompat.java +++ b/packages/SettingsLib/src/com/android/settingslib/license/LicenseHtmlLoaderCompat.java @@ -73,7 +73,7 @@ public class LicenseHtmlLoaderCompat extends AsyncLoaderCompat<File> { return null; } - static List<File> getVaildXmlFiles() { + private List<File> getVaildXmlFiles() { final List<File> xmlFiles = new ArrayList(); for (final String xmlPath : DEFAULT_LICENSE_XML_PATHS) { File file = new File(xmlPath); @@ -84,11 +84,11 @@ public class LicenseHtmlLoaderCompat extends AsyncLoaderCompat<File> { return xmlFiles; } - static File getCachedHtmlFile(Context context) { + private File getCachedHtmlFile(Context context) { return new File(context.getCacheDir(), NOTICE_HTML_FILE_NAME); } - static boolean isCachedHtmlFileOutdated(List<File> xmlFiles, File cachedHtmlFile) { + private boolean isCachedHtmlFileOutdated(List<File> xmlFiles, File cachedHtmlFile) { boolean outdated = true; if (cachedHtmlFile.exists() && cachedHtmlFile.length() != 0) { outdated = false; @@ -102,7 +102,7 @@ public class LicenseHtmlLoaderCompat extends AsyncLoaderCompat<File> { return outdated; } - static boolean generateHtmlFile(Context context, List<File> xmlFiles, File htmlFile) { + private boolean generateHtmlFile(Context context, List<File> xmlFiles, File htmlFile) { return LicenseHtmlGeneratorFromXml.generateHtml(xmlFiles, htmlFile, context.getString(R.string.notice_header)); } diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ApplicationsStateRoboTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ApplicationsStateRoboTest.java index 19ce424257ac..b307b4730fa1 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ApplicationsStateRoboTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ApplicationsStateRoboTest.java @@ -86,21 +86,21 @@ public class ApplicationsStateRoboTest { @Mock private StorageStatsManager mStorageStatsManager; - @Implements(value = IconDrawableFactory.class, inheritImplementationMethods = true) + @Implements(value = IconDrawableFactory.class) public static class ShadowIconDrawableFactory { @Implementation - public Drawable getBadgedIcon(ApplicationInfo appInfo) { + protected Drawable getBadgedIcon(ApplicationInfo appInfo) { return new ColorDrawable(0); } } - @Implements(value = ApplicationPackageManager.class, inheritImplementationMethods = true) + @Implements(value = ApplicationPackageManager.class) public static class ShadowPackageManager extends org.robolectric.shadows.ShadowApplicationPackageManager { @Implementation - public ComponentName getHomeActivities(List<ResolveInfo> outActivities) { + protected ComponentName getHomeActivities(List<ResolveInfo> outActivities) { ResolveInfo resolveInfo = new ResolveInfo(); resolveInfo.activityInfo = new ActivityInfo(); resolveInfo.activityInfo.packageName = HOME_PACKAGE_NAME; @@ -139,6 +139,7 @@ public class ApplicationsStateRoboTest { when(mStorageStatsManager.queryStatsForPackage(ArgumentMatchers.any(UUID.class), anyString(), ArgumentMatchers.any(UserHandle.class))).thenReturn(storageStats); + ApplicationsState.sInstance = null; mApplicationsState = ApplicationsState.getInstance(RuntimeEnvironment.application); mApplicationsState.clearEntries(); } @@ -189,8 +190,8 @@ public class ApplicationsStateRoboTest { Session session = mApplicationsState.newSession(mCallbacks); session.onResume(); - addApp(HOME_PACKAGE_NAME,1); - addApp(LAUNCHABLE_PACKAGE_NAME,2); + addApp(HOME_PACKAGE_NAME, 1); + addApp(LAUNCHABLE_PACKAGE_NAME, 2); session.rebuild(ApplicationsState.FILTER_EVERYTHING, ApplicationsState.SIZE_COMPARATOR); processAllMessages(); verify(mCallbacks).onRebuildComplete(mAppEntriesCaptor.capture()); @@ -219,7 +220,7 @@ public class ApplicationsStateRoboTest { session.setSessionFlags(ApplicationsState.FLAG_SESSION_REQUEST_ICONS); session.onResume(); - addApp(LAUNCHABLE_PACKAGE_NAME,1); + addApp(LAUNCHABLE_PACKAGE_NAME, 1); session.rebuild(ApplicationsState.FILTER_EVERYTHING, ApplicationsState.SIZE_COMPARATOR); processAllMessages(); verify(mCallbacks).onRebuildComplete(mAppEntriesCaptor.capture()); @@ -240,7 +241,7 @@ public class ApplicationsStateRoboTest { session.setSessionFlags(ApplicationsState.FLAG_SESSION_REQUEST_SIZES); session.onResume(); - addApp(LAUNCHABLE_PACKAGE_NAME,1); + addApp(LAUNCHABLE_PACKAGE_NAME, 1); session.rebuild(ApplicationsState.FILTER_EVERYTHING, ApplicationsState.SIZE_COMPARATOR); processAllMessages(); verify(mCallbacks).onRebuildComplete(mAppEntriesCaptor.capture()); @@ -261,7 +262,7 @@ public class ApplicationsStateRoboTest { session.setSessionFlags(ApplicationsState.FLAG_SESSION_REQUEST_HOME_APP); session.onResume(); - addApp(HOME_PACKAGE_NAME,1); + addApp(HOME_PACKAGE_NAME, 1); session.rebuild(ApplicationsState.FILTER_EVERYTHING, ApplicationsState.SIZE_COMPARATOR); processAllMessages(); verify(mCallbacks).onRebuildComplete(mAppEntriesCaptor.capture()); @@ -283,7 +284,7 @@ public class ApplicationsStateRoboTest { session.setSessionFlags(ApplicationsState.FLAG_SESSION_REQUEST_LEANBACK_LAUNCHER); session.onResume(); - addApp(LAUNCHABLE_PACKAGE_NAME,1); + addApp(LAUNCHABLE_PACKAGE_NAME, 1); session.rebuild(ApplicationsState.FILTER_EVERYTHING, ApplicationsState.SIZE_COMPARATOR); processAllMessages(); verify(mCallbacks).onRebuildComplete(mAppEntriesCaptor.capture()); diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothEventManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothEventManagerTest.java index 020234c6e12f..c147d5e306c2 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothEventManagerTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothEventManagerTest.java @@ -19,7 +19,10 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothHeadset; import android.bluetooth.BluetoothProfile; import android.content.BroadcastReceiver; @@ -50,6 +53,8 @@ public class BluetoothEventManagerTest { private BluetoothCallback mBluetoothCallback; @Mock private CachedBluetoothDevice mCachedBluetoothDevice; + @Mock + private BluetoothDevice mBluetoothDevice; private Context mContext; private Intent mIntent; @@ -62,6 +67,7 @@ public class BluetoothEventManagerTest { mBluetoothEventManager = new BluetoothEventManager(mLocalAdapter, mCachedDeviceManager, mContext, /* handler= */ null, /* userHandle= */ null); + when(mCachedDeviceManager.findDevice(mBluetoothDevice)).thenReturn(mCachedBluetoothDevice); } @Test @@ -126,4 +132,28 @@ public class BluetoothEventManagerTest { verify(mBluetoothCallback).onProfileConnectionStateChanged(mCachedBluetoothDevice, BluetoothProfile.STATE_CONNECTED, BluetoothProfile.A2DP); } + + @Test + public void dispatchAclConnectionStateChanged_aclDisconnected_shouldDispatchCallback() { + mBluetoothEventManager.registerCallback(mBluetoothCallback); + mIntent = new Intent(BluetoothDevice.ACTION_ACL_DISCONNECTED); + mIntent.putExtra(BluetoothDevice.EXTRA_DEVICE, mBluetoothDevice); + + mContext.sendBroadcast(mIntent); + + verify(mBluetoothCallback).onAclConnectionStateChanged(mCachedBluetoothDevice, + BluetoothAdapter.STATE_DISCONNECTED); + } + + @Test + public void dispatchAclConnectionStateChanged_aclConnected_shouldDispatchCallback() { + mBluetoothEventManager.registerCallback(mBluetoothCallback); + mIntent = new Intent(BluetoothDevice.ACTION_ACL_CONNECTED); + mIntent.putExtra(BluetoothDevice.EXTRA_DEVICE, mBluetoothDevice); + + mContext.sendBroadcast(mIntent); + + verify(mBluetoothCallback).onAclConnectionStateChanged(mCachedBluetoothDevice, + BluetoothAdapter.STATE_CONNECTED); + } } diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/development/DevelopmentSettingsEnablerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/development/DevelopmentSettingsEnablerTest.java index a15f5fce558e..a0fa6b599b45 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/development/DevelopmentSettingsEnablerTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/development/DevelopmentSettingsEnablerTest.java @@ -18,11 +18,14 @@ package com.android.settingslib.development; import static com.google.common.truth.Truth.assertThat; +import android.os.UserManager; import android.content.Context; import android.provider.Settings; import com.android.settingslib.SettingsLibRobolectricTestRunner; -import com.android.settingslib.testutils.shadow.ShadowUserManager; + +import org.robolectric.shadows.ShadowUserManager; +import org.robolectric.shadow.api.Shadow; import org.junit.After; import org.junit.Before; @@ -32,20 +35,16 @@ import org.robolectric.RuntimeEnvironment; import org.robolectric.annotation.Config; @RunWith(SettingsLibRobolectricTestRunner.class) -@Config(shadows = ShadowUserManager.class) public class DevelopmentSettingsEnablerTest { private Context mContext; + private ShadowUserManager mUserManager; @Before public void setUp() { mContext = RuntimeEnvironment.application; - ShadowUserManager.getShadow().setIsAdminUser(true); - } - - @After - public void tearDown() { - ShadowUserManager.getShadow().reset(); + mUserManager = Shadow.extract(mContext.getSystemService(UserManager.class)); + mUserManager.setIsAdminUser(true); } @Test @@ -76,7 +75,7 @@ public class DevelopmentSettingsEnablerTest { public void isEnabled_settingsOn_noRestriction_notAdmin_shouldReturnFalse() { Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.DEVELOPMENT_SETTINGS_ENABLED, 1); - ShadowUserManager.getShadow().setIsAdminUser(false); + mUserManager.setIsAdminUser(false); assertThat(DevelopmentSettingsEnabler.isDevelopmentSettingsEnabled(mContext)).isFalse(); } diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/development/EnableAdbPreferenceControllerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/development/EnableAdbPreferenceControllerTest.java index 2a60839990c4..d7b23b0ef636 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/development/EnableAdbPreferenceControllerTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/development/EnableAdbPreferenceControllerTest.java @@ -39,6 +39,7 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import org.robolectric.RuntimeEnvironment; import org.robolectric.shadows.ShadowApplication; @RunWith(SettingsLibRobolectricTestRunner.class) @@ -59,7 +60,7 @@ public class EnableAdbPreferenceControllerTest { MockitoAnnotations.initMocks(this); ShadowApplication shadowContext = ShadowApplication.getInstance(); shadowContext.setSystemService(Context.USER_SERVICE, mUserManager); - mContext = spy(shadowContext.getApplicationContext()); + mContext = spy(RuntimeEnvironment.application); when(mContext.getPackageManager()).thenReturn(mPackageManager); mController = new ConcreteEnableAdbPreferenceController(mContext); mPreference = new SwitchPreference(mContext); @@ -85,7 +86,6 @@ public class EnableAdbPreferenceControllerTest { assertThat(mPreference.isVisible()).isTrue(); } - @Test public void resetPreference_shouldUncheck() { when(mUserManager.isAdminUser()).thenReturn(true); @@ -100,14 +100,14 @@ public class EnableAdbPreferenceControllerTest { @Test public void handlePreferenceTreeClick_shouldUpdateSettings() { when(mUserManager.isAdminUser()).thenReturn(true); - Settings.Secure.putInt(mContext.getContentResolver(), + Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.ADB_ENABLED, 1); mPreference.setChecked(true); mController.displayPreference(mScreen); mController.handlePreferenceTreeClick(mPreference); - assertThat(Settings.Secure.getInt(mContext.getContentResolver(), + assertThat(Settings.Global.getInt(mContext.getContentResolver(), Settings.Global.ADB_ENABLED, 0)).isEqualTo(0); } @@ -126,7 +126,7 @@ public class EnableAdbPreferenceControllerTest { @Test public void updateState_settingsOn_shouldCheck() { when(mUserManager.isAdminUser()).thenReturn(true); - Settings.Secure.putInt(mContext.getContentResolver(), + Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.ADB_ENABLED, 1); mPreference.setChecked(false); mController.displayPreference(mScreen); @@ -139,7 +139,7 @@ public class EnableAdbPreferenceControllerTest { @Test public void updateState_settingsOff_shouldUncheck() { when(mUserManager.isAdminUser()).thenReturn(true); - Settings.Secure.putInt(mContext.getContentResolver(), + Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.ADB_ENABLED, 0); mPreference.setChecked(true); mController.displayPreference(mScreen); diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/fuelgauge/BatterySaverUtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/fuelgauge/BatterySaverUtilsTest.java index de96af4fa6a1..d0b6dab43281 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/fuelgauge/BatterySaverUtilsTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/fuelgauge/BatterySaverUtilsTest.java @@ -158,16 +158,16 @@ public class BatterySaverUtilsTest { @Test public void testEnsureAutoBatterysaver_setNewPositiveValue_doNotOverwrite() throws Exception { - Global.putString(mMockResolver, Global.LOW_POWER_MODE_TRIGGER_LEVEL, "null"); + Global.putInt(mMockResolver, Global.LOW_POWER_MODE_TRIGGER_LEVEL, 0); BatterySaverUtils.ensureAutoBatterySaver(mMockContext, BATTERY_SAVER_THRESHOLD_1); - assertThat(Secure.getInt(mMockResolver, Global.LOW_POWER_MODE_TRIGGER_LEVEL, -1)) + assertThat(Global.getInt(mMockResolver, Global.LOW_POWER_MODE_TRIGGER_LEVEL, -1)) .isEqualTo(BATTERY_SAVER_THRESHOLD_1); // Once a positive number is set, ensureAutoBatterySaver() won't overwrite it. BatterySaverUtils.ensureAutoBatterySaver(mMockContext, BATTERY_SAVER_THRESHOLD_2); - assertThat(Secure.getInt(mMockResolver, Global.LOW_POWER_MODE_TRIGGER_LEVEL, -1)) + assertThat(Global.getInt(mMockResolver, Global.LOW_POWER_MODE_TRIGGER_LEVEL, -1)) .isEqualTo(BATTERY_SAVER_THRESHOLD_1); } @@ -182,8 +182,8 @@ public class BatterySaverUtilsTest { assertThat(Secure.getInt(mMockResolver, Secure.SUPPRESS_AUTO_BATTERY_SAVER_SUGGESTION, -1)) .isEqualTo(-1); // not set. - BatterySaverUtils.setAutoBatterySaverTriggerLevel(mMockContext, BATTERY_SAVER_THRESHOLD_1 ); - assertThat( Global.getInt(mMockResolver, Global.LOW_POWER_MODE_TRIGGER_LEVEL, -1)) + BatterySaverUtils.setAutoBatterySaverTriggerLevel(mMockContext, BATTERY_SAVER_THRESHOLD_1); + assertThat(Global.getInt(mMockResolver, Global.LOW_POWER_MODE_TRIGGER_LEVEL, -1)) .isEqualTo(BATTERY_SAVER_THRESHOLD_1); assertThat(Secure.getInt(mMockResolver, Secure.SUPPRESS_AUTO_BATTERY_SAVER_SUGGESTION, -1)) .isEqualTo(1); diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/graph/BluetoothDeviceLayerDrawableTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/graph/BluetoothDeviceLayerDrawableTest.java index 37d4d1d845f5..5dbb5caf60eb 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/graph/BluetoothDeviceLayerDrawableTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/graph/BluetoothDeviceLayerDrawableTest.java @@ -89,10 +89,9 @@ public class BluetoothDeviceLayerDrawableTest { BluetoothDeviceLayerDrawable twinDrawable = (BluetoothDeviceLayerDrawable) drawable.getConstantState().newDrawable(); - assertThat(twinDrawable.getDrawable(0)).isEqualTo(drawable.getDrawable(0)); - assertThat(twinDrawable.getDrawable(1)).isEqualTo(drawable.getDrawable(1)); - assertThat(twinDrawable.getLayerInsetTop(1)).isEqualTo( - drawable.getLayerInsetTop(1)); + assertThat(twinDrawable.getDrawable(0)).isNotNull(); + assertThat(twinDrawable.getDrawable(1)).isNotNull(); + assertThat(twinDrawable.getLayerInsetTop(1)).isEqualTo(drawable.getLayerInsetTop(1)); } @Test diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/license/LicenseHtmlLoaderCompatTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/license/LicenseHtmlLoaderCompatTest.java index c32cc99daf96..c90de5fe621e 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/license/LicenseHtmlLoaderCompatTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/license/LicenseHtmlLoaderCompatTest.java @@ -112,7 +112,6 @@ public class LicenseHtmlLoaderCompatTest { @Implements(LicenseHtmlLoaderCompat.class) public static class ShadowLicenseHtmlLoaderCompat { - public static List<File> sValidXmlFiles; public static File sCachedHtmlFile; public static boolean sIsCachedHtmlFileOutdated; @@ -127,22 +126,24 @@ public class LicenseHtmlLoaderCompatTest { } @Implementation - static List<File> getVaildXmlFiles() { + protected List<File> getVaildXmlFiles() { return sValidXmlFiles; } @Implementation - static File getCachedHtmlFile(Context context) { + protected File getCachedHtmlFile(Context context) { return sCachedHtmlFile; } @Implementation - static boolean isCachedHtmlFileOutdated(List<File> xmlFiles, File cachedHtmlFile) { + protected boolean isCachedHtmlFileOutdated(List<File> xmlFiles, + File cachedHtmlFile) { return sIsCachedHtmlFileOutdated; } @Implementation - static boolean generateHtmlFile(Context context, List<File> xmlFiles, File htmlFile) { + protected boolean generateHtmlFile(Context context, List<File> xmlFiles, + File htmlFile) { return sGenerateHtmlFileSucceeded; } } diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/testutils/DrawableTestHelper.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/testutils/DrawableTestHelper.java new file mode 100644 index 000000000000..ad8de770d624 --- /dev/null +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/testutils/DrawableTestHelper.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.settingslib.testutils; + +import static com.google.common.truth.Truth.assertThat; + +import android.graphics.drawable.Drawable; + +import org.robolectric.Shadows; +import org.robolectric.shadows.ShadowDrawable; + +public class DrawableTestHelper { + public static void assertDrawableResId(Drawable drawable, int resId) { + final ShadowDrawable shadow = Shadows.shadowOf(drawable); + assertThat(shadow.getCreatedFromResId()).isEqualTo(resId); + } +} diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/testutils/shadow/ShadowBluetoothAdapter.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/testutils/shadow/ShadowBluetoothAdapter.java index 9b8c230dded4..cae74c888f0a 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/testutils/shadow/ShadowBluetoothAdapter.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/testutils/shadow/ShadowBluetoothAdapter.java @@ -25,7 +25,7 @@ import org.robolectric.annotation.Implements; import java.util.List; -@Implements(value = BluetoothAdapter.class, inheritImplementationMethods = true) +@Implements(value = BluetoothAdapter.class) public class ShadowBluetoothAdapter extends org.robolectric.shadows.ShadowBluetoothAdapter { private List<Integer> mSupportedProfiles; diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/testutils/shadow/ShadowUserManager.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/testutils/shadow/ShadowUserManager.java index bbd3a53751a0..a81e39501a8e 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/testutils/shadow/ShadowUserManager.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/testutils/shadow/ShadowUserManager.java @@ -16,6 +16,7 @@ package com.android.settingslib.testutils.shadow; +import android.annotation.UserIdInt; import android.content.Context; import android.content.pm.UserInfo; import android.os.UserManager; @@ -29,37 +30,21 @@ import org.robolectric.shadow.api.Shadow; import java.util.ArrayList; import java.util.List; -@Implements(value = UserManager.class, inheritImplementationMethods = true) +@Implements(value = UserManager.class) public class ShadowUserManager extends org.robolectric.shadows.ShadowUserManager { - private boolean mAdminUser; - - public void setIsAdminUser(boolean isAdminUser) { - mAdminUser = isAdminUser; - } - - @Resetter - public void reset() { - mAdminUser = false; - } - @Implementation - public boolean isAdminUser() { - return mAdminUser; - } - - @Implementation - public static UserManager get(Context context) { + protected static UserManager get(Context context) { return (UserManager) context.getSystemService(Context.USER_SERVICE); } @Implementation - public int[] getProfileIdsWithDisabled(int userId) { - return new int[] { 0 }; + protected int[] getProfileIdsWithDisabled(int userId) { + return new int[]{0}; } @Implementation - public List<UserInfo> getProfiles() { + protected List<UserInfo> getProfiles() { UserInfo userInfo = new UserInfo(); userInfo.id = 0; List<UserInfo> userInfos = new ArrayList<>(); @@ -67,8 +52,9 @@ public class ShadowUserManager extends org.robolectric.shadows.ShadowUserManager return userInfos; } - public static ShadowUserManager getShadow() { - return (ShadowUserManager) Shadow.extract( - RuntimeEnvironment.application.getSystemService(UserManager.class)); + @Implementation + protected List<UserInfo> getProfiles(@UserIdInt int userHandle) { + return getProfiles(); } -}
\ No newline at end of file + +} diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/FooterPreferenceMixinCompatTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/FooterPreferenceMixinCompatTest.java index f56c111d86ea..e030005e2d09 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/FooterPreferenceMixinCompatTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/FooterPreferenceMixinCompatTest.java @@ -37,7 +37,7 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.MockitoAnnotations; -import org.robolectric.shadows.ShadowApplication; +import org.robolectric.RuntimeEnvironment; @RunWith(SettingsLibRobolectricTestRunner.class) public class FooterPreferenceMixinCompatTest { @@ -58,7 +58,7 @@ public class FooterPreferenceMixinCompatTest { mLifecycle = new Lifecycle(mLifecycleOwner); when(mFragment.getPreferenceManager()).thenReturn(mock(PreferenceManager.class)); when(mFragment.getPreferenceManager().getContext()) - .thenReturn(ShadowApplication.getInstance().getApplicationContext()); + .thenReturn(RuntimeEnvironment.application); mMixin = new FooterPreferenceMixinCompat(mFragment, mLifecycle); } diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/FooterPreferenceMixinTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/FooterPreferenceMixinTest.java index 366b720fc87b..8817ff7f65b3 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/FooterPreferenceMixinTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/FooterPreferenceMixinTest.java @@ -37,6 +37,7 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import org.robolectric.RuntimeEnvironment; import org.robolectric.shadows.ShadowApplication; @RunWith(SettingsLibRobolectricTestRunner.class) @@ -58,7 +59,7 @@ public class FooterPreferenceMixinTest { mLifecycle = new Lifecycle(mLifecycleOwner); when(mFragment.getPreferenceManager()).thenReturn(mock(PreferenceManager.class)); when(mFragment.getPreferenceManager().getContext()) - .thenReturn(ShadowApplication.getInstance().getApplicationContext()); + .thenReturn(RuntimeEnvironment.application); mMixin = new FooterPreferenceMixin(mFragment, mLifecycle); } diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/FooterPreferenceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/FooterPreferenceTest.java index 84a043e15eb5..e0eceb418f27 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/FooterPreferenceTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/FooterPreferenceTest.java @@ -31,7 +31,7 @@ import com.android.settingslib.SettingsLibRobolectricTestRunner; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; -import org.robolectric.shadows.ShadowApplication; +import org.robolectric.RuntimeEnvironment; @RunWith(SettingsLibRobolectricTestRunner.class) public class FooterPreferenceTest { @@ -40,7 +40,7 @@ public class FooterPreferenceTest { @Before public void setUp() { - mContext = ShadowApplication.getInstance().getApplicationContext(); + mContext = RuntimeEnvironment.application; } @Test diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsService.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsService.java index a6fadf967f9a..379cfc70b7ba 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsService.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsService.java @@ -124,7 +124,7 @@ final public class SettingsService extends Binder { @Override public int onCommand(String cmd) { - if (cmd == null) { + if (cmd == null || "help".equals(cmd) || "-h".equals(cmd)) { return handleDefaultCommands(cmd); } diff --git a/packages/Shell/res/values-mr/strings.xml b/packages/Shell/res/values-mr/strings.xml index aae84934346b..dc6a6126bc5d 100644 --- a/packages/Shell/res/values-mr/strings.xml +++ b/packages/Shell/res/values-mr/strings.xml @@ -23,12 +23,12 @@ <string name="bugreport_updating_title" msgid="4423539949559634214">"दोष अहवालामध्ये तपशील जोडत आहे"</string> <string name="bugreport_updating_wait" msgid="3322151947853929470">"कृपया प्रतीक्षा करा..."</string> <string name="bugreport_finished_text" product="watch" msgid="1223616207145252689">"फोनवर बग रीपोर्ट लवकरच दिसेल"</string> - <string name="bugreport_finished_text" product="tv" msgid="5758325479058638893">"आपला बग रीपोर्ट शेअर करण्यासाठी निवडा"</string> - <string name="bugreport_finished_text" product="default" msgid="8353769438382138847">"आपला बग रीपोर्ट शेअर करण्यासाठी टॅप करा"</string> - <string name="bugreport_finished_pending_screenshot_text" product="tv" msgid="2343263822812016950">"आपला बग रीपोर्ट स्क्रीनशॉटशिवाय शेअर करण्यासाठी टॅप करा किंवा स्क्रीनशॉट पूर्ण होण्याची प्रतीक्षा करा"</string> - <string name="bugreport_finished_pending_screenshot_text" product="watch" msgid="1474435374470177193">"स्क्रीनशॉट शिवाय आपला बग रीपोर्ट शेअर करण्यासाठी टॅप करा किंवा समाप्त करण्यासाठी स्क्रीनशॉटची प्रतीक्षा करा"</string> - <string name="bugreport_finished_pending_screenshot_text" product="default" msgid="1474435374470177193">"स्क्रीनशॉट शिवाय आपला बग रीपोर्ट शेअर करण्यासाठी टॅप करा किंवा समाप्त करण्यासाठी स्क्रीनशॉटची प्रतीक्षा करा"</string> - <string name="bugreport_confirm" msgid="5917407234515812495">"बग रीपोर्टांमध्ये तुम्ही संवेदनशील (अॅप-वापर आणि स्थान डेटा यासारखा) डेटा म्हणून विचार करता त्या डेटाच्या समावेशासह सिस्टीमच्या विविध लॉग फायलींमधील डेटा असतो. ज्या लोकांवर आणि अॅपवर आपला विश्वास आहे केवळ त्यांच्यासह हा बग रीपोर्ट शेअर करा."</string> + <string name="bugreport_finished_text" product="tv" msgid="5758325479058638893">"तुमचा बग रीपोर्ट शेअर करण्यासाठी निवडा"</string> + <string name="bugreport_finished_text" product="default" msgid="8353769438382138847">"तुमचा बग रीपोर्ट शेअर करण्यासाठी टॅप करा"</string> + <string name="bugreport_finished_pending_screenshot_text" product="tv" msgid="2343263822812016950">"तुमचा बग रीपोर्ट स्क्रीनशॉटशिवाय शेअर करण्यासाठी टॅप करा किंवा स्क्रीनशॉट पूर्ण होण्याची प्रतीक्षा करा"</string> + <string name="bugreport_finished_pending_screenshot_text" product="watch" msgid="1474435374470177193">"स्क्रीनशॉट शिवाय तुमचा बग रीपोर्ट शेअर करण्यासाठी टॅप करा किंवा समाप्त करण्यासाठी स्क्रीनशॉटची प्रतीक्षा करा"</string> + <string name="bugreport_finished_pending_screenshot_text" product="default" msgid="1474435374470177193">"स्क्रीनशॉट शिवाय तुमचा बग रीपोर्ट शेअर करण्यासाठी टॅप करा किंवा समाप्त करण्यासाठी स्क्रीनशॉटची प्रतीक्षा करा"</string> + <string name="bugreport_confirm" msgid="5917407234515812495">"बग रीपोर्टांमध्ये तुम्ही संवेदनशील (अॅप-वापर आणि स्थान डेटा यासारखा) डेटा म्हणून विचार करता त्या डेटाच्या समावेशासह सिस्टीमच्या विविध लॉग फायलींमधील डेटा असतो. ज्या लोकांवर आणि अॅपवर तुमचा विश्वास आहे केवळ त्यांच्यासह हा बग रीपोर्ट शेअर करा."</string> <string name="bugreport_confirm_dont_repeat" msgid="6179945398364357318">"पुन्हा दर्शवू नका"</string> <string name="bugreport_storage_title" msgid="5332488144740527109">"बग रीपोर्ट"</string> <string name="bugreport_unreadable_text" msgid="586517851044535486">"बग रीपोर्ट फाईल वाचणे शक्य झाले नाही"</string> diff --git a/packages/SystemUI/OWNERS b/packages/SystemUI/OWNERS index 47f2cdcf80c6..2f6e32b2c6d2 100644 --- a/packages/SystemUI/OWNERS +++ b/packages/SystemUI/OWNERS @@ -6,20 +6,36 @@ adamcohen@google.com asc@google.com ashaikh@google.com beverlyt@google.com +brockman@google.com cinek@google.com cwren@google.com dupin@google.com +ethibodeau@google.com evanlaird@google.com jmonk@google.com jaggies@google.com jjaggi@google.com +joshmcgrath@google.com juliacr@google.com +juliatuttle@google.com kchyn@google.com +kozynski@google.com +kprevas@google.com madym@google.com +mankoff@google.com +nbenbernou@google.com +nesciosquid@google.com ngmatthew@google.com +ogunwale@google.com +pixel@google.com roosa@google.com shahrk@google.com +snoeberger@google.com +steell@google.com +stwu@google.com sunnygoyal@google.com +susikp@google.com +tsuji@google.com twickham@google.com winsonc@google.com diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java index bf4374acf6e6..bca353050c46 100644 --- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java +++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java @@ -116,6 +116,7 @@ public interface QSTile { public boolean isTransient = false; public String expandedAccessibilityClassName; public SlashState slash; + public boolean handlesLongClick = true; public boolean copyTo(State other) { if (other == null) throw new IllegalArgumentException(); @@ -133,7 +134,8 @@ public interface QSTile { || !Objects.equals(other.state, state) || !Objects.equals(other.isTransient, isTransient) || !Objects.equals(other.dualTarget, dualTarget) - || !Objects.equals(other.slash, slash); + || !Objects.equals(other.slash, slash) + || !Objects.equals(other.handlesLongClick, handlesLongClick); other.icon = icon; other.iconSupplier = iconSupplier; other.label = label; @@ -146,6 +148,7 @@ public interface QSTile { other.dualTarget = dualTarget; other.isTransient = isTransient; other.slash = slash != null ? slash.copy() : null; + other.handlesLongClick = handlesLongClick; return changed; } diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_status_area.xml b/packages/SystemUI/res-keyguard/layout/keyguard_status_area.xml index 00f8f869008e..b51ad1cb4922 100644 --- a/packages/SystemUI/res-keyguard/layout/keyguard_status_area.xml +++ b/packages/SystemUI/res-keyguard/layout/keyguard_status_area.xml @@ -28,17 +28,6 @@ android:clipToPadding="false" android:orientation="vertical" android:layout_centerHorizontal="true"> - <view class="com.android.keyguard.KeyguardSliceView$TitleView" - android:id="@+id/title" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_marginBottom="@dimen/widget_title_bottom_margin" - android:paddingStart="64dp" - android:paddingEnd="64dp" - android:visibility="gone" - android:textColor="?attr/wallpaperTextColor" - android:theme="@style/TextAppearance.Keyguard" - /> <view class="com.android.keyguard.KeyguardSliceView$Row" android:id="@+id/row" android:layout_width="match_parent" diff --git a/packages/SystemUI/res/drawable/biometric_dialog_bg.xml b/packages/SystemUI/res/drawable/biometric_dialog_bg.xml index 335448d82373..d4dfdee82653 100644 --- a/packages/SystemUI/res/drawable/biometric_dialog_bg.xml +++ b/packages/SystemUI/res/drawable/biometric_dialog_bg.xml @@ -17,10 +17,10 @@ --> <shape xmlns:android="http://schemas.android.com/apk/res/android"> - <solid android:color="@color/biometric_dialog_bg_color" /> + <solid android:color="?android:attr/colorBackgroundFloating" /> <corners android:radius="1dp" android:topLeftRadius="@dimen/biometric_dialog_corner_size" android:topRightRadius="@dimen/biometric_dialog_corner_size" android:bottomLeftRadius="0dp" android:bottomRightRadius="0dp"/> -</shape>
\ No newline at end of file +</shape> diff --git a/packages/SystemUI/res/drawable/fingerprint_dialog_error_to_fp.xml b/packages/SystemUI/res/drawable/fingerprint_dialog_error_to_fp.xml index 05fd467b749e..af25e44bd746 100644 --- a/packages/SystemUI/res/drawable/fingerprint_dialog_error_to_fp.xml +++ b/packages/SystemUI/res/drawable/fingerprint_dialog_error_to_fp.xml @@ -36,7 +36,7 @@ android:name="_R_G_L_2_G_D_0_P_0" android:pathData=" M-25.36 -24.41 C-25.93,-24.31 -26.49,-24.27 -26.81,-24.27 C-28.11,-24.27 -29.35,-24.62 -30.43,-25.4 C-32.11,-26.6 -33.2,-28.57 -33.2,-30.79 " android:strokeAlpha="1" - android:strokeColor="@color/biometric_dialog_biometric_color" + android:strokeColor="?android:attr/colorAccent" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="1.45" @@ -47,7 +47,7 @@ android:name="_R_G_L_2_G_D_1_P_0" android:pathData=" M-36.14 -21.78 C-37.15,-22.98 -37.72,-23.7 -38.51,-25.29 C-39.33,-26.94 -39.82,-28.78 -39.82,-30.77 C-39.82,-34.43 -36.85,-37.4 -33.19,-37.4 C-29.52,-37.4 -26.55,-34.43 -26.55,-30.77 " android:strokeAlpha="1" - android:strokeColor="@color/biometric_dialog_biometric_color" + android:strokeColor="?android:attr/colorAccent" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="1.45" @@ -58,7 +58,7 @@ android:name="_R_G_L_2_G_D_2_P_0" android:pathData=" M-42.19 -25.68 C-42.95,-27.82 -43.09,-29.54 -43.09,-30.8 C-43.09,-32.27 -42.84,-33.65 -42.27,-34.9 C-40.71,-38.35 -37.24,-40.75 -33.2,-40.75 C-27.71,-40.75 -23.26,-36.3 -23.26,-30.8 C-23.26,-28.97 -24.74,-27.49 -26.57,-27.49 C-28.4,-27.49 -29.89,-28.97 -29.89,-30.8 C-29.89,-32.64 -31.37,-34.12 -33.2,-34.12 C-35.04,-34.12 -36.52,-32.64 -36.52,-30.8 C-36.52,-28.23 -35.53,-25.92 -33.92,-24.22 C-32.69,-22.93 -31.48,-22.12 -29.44,-21.53 " android:strokeAlpha="1" - android:strokeColor="@color/biometric_dialog_biometric_color" + android:strokeColor="?android:attr/colorAccent" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="1.45" @@ -69,7 +69,7 @@ android:name="_R_G_L_2_G_D_3_P_0" android:pathData=" M-44.06 -38.17 C-42.87,-39.94 -41.39,-41.41 -39.51,-42.44 C-37.62,-43.47 -35.46,-44.05 -33.16,-44.05 C-30.88,-44.05 -28.72,-43.47 -26.85,-42.45 C-24.97,-41.43 -23.48,-39.97 -22.29,-38.21 " android:strokeAlpha="1" - android:strokeColor="@color/biometric_dialog_biometric_color" + android:strokeColor="?android:attr/colorAccent" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="1.45" @@ -80,7 +80,7 @@ android:name="_R_G_L_2_G_D_4_P_0" android:pathData=" M-25.72 -45.45 C-27.99,-46.76 -30.43,-47.52 -33.28,-47.52 C-36.13,-47.52 -38.51,-46.74 -40.62,-45.45 " android:strokeAlpha="1" - android:strokeColor="@color/biometric_dialog_biometric_color" + android:strokeColor="?android:attr/colorAccent" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="1.45" @@ -97,7 +97,7 @@ android:name="_R_G_L_1_G_D_0_P_0" android:pathData=" M0 -9 C4.97,-9 9,-4.97 9,0 C9,4.97 4.97,9 0,9 C-4.97,9 -9,4.97 -9,0 C-9,-4.97 -4.97,-9 0,-9c " android:strokeAlpha="1" - android:strokeColor="@color/biometric_dialog_error_color" + android:strokeColor="?android:attr/colorError" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" @@ -118,7 +118,7 @@ <path android:name="_R_G_L_0_G_D_0_P_0" android:fillAlpha="1" - android:fillColor="@color/biometric_dialog_error_color" + android:fillColor="?android:attr/colorError" android:fillType="nonZero" android:pathData=" M1.1 3.94 C1.1,4.55 0.61,5.04 0,5.04 C-0.61,5.04 -1.1,4.55 -1.1,3.94 C-1.1,3.33 -0.61,2.84 0,2.84 C0.61,2.84 1.1,3.33 1.1,3.94c " /> </group> @@ -131,7 +131,7 @@ <path android:name="_R_G_L_0_G_D_0_P_1" android:fillAlpha="1" - android:fillColor="@color/biometric_dialog_error_color" + android:fillColor="?android:attr/colorError" android:fillType="nonZero" android:pathData=" M1 -4.06 C1,-4.06 1,-0.06 1,-0.06 C1,0.49 0.55,0.94 0,0.94 C-0.55,0.94 -1,0.49 -1,-0.06 C-1,-0.06 -1,-4.06 -1,-4.06 C-1,-4.61 -0.55,-5.06 0,-5.06 C0.55,-5.06 1,-4.61 1,-4.06c " /> </group> @@ -522,4 +522,4 @@ </set> </aapt:attr> </target> -</animated-vector>
\ No newline at end of file +</animated-vector> diff --git a/packages/SystemUI/res/drawable/fingerprint_dialog_fp_to_error.xml b/packages/SystemUI/res/drawable/fingerprint_dialog_fp_to_error.xml index fd0ab22b2f98..1a7a846b140e 100644 --- a/packages/SystemUI/res/drawable/fingerprint_dialog_fp_to_error.xml +++ b/packages/SystemUI/res/drawable/fingerprint_dialog_fp_to_error.xml @@ -36,7 +36,7 @@ android:name="_R_G_L_3_G_D_0_P_0" android:pathData=" M-25.36 -24.41 C-25.93,-24.31 -26.49,-24.27 -26.81,-24.27 C-28.11,-24.27 -29.35,-24.62 -30.43,-25.4 C-32.11,-26.6 -33.2,-28.57 -33.2,-30.79 " android:strokeAlpha="1" - android:strokeColor="@color/biometric_dialog_biometric_color" + android:strokeColor="?android:attr/colorAccent" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="1.45" @@ -47,7 +47,7 @@ android:name="_R_G_L_3_G_D_1_P_0" android:pathData=" M-36.14 -21.78 C-37.15,-22.98 -37.72,-23.7 -38.51,-25.29 C-39.33,-26.94 -39.82,-28.78 -39.82,-30.77 C-39.82,-34.43 -36.85,-37.4 -33.19,-37.4 C-29.52,-37.4 -26.55,-34.43 -26.55,-30.77 " android:strokeAlpha="1" - android:strokeColor="@color/biometric_dialog_biometric_color" + android:strokeColor="?android:attr/colorAccent" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="1.45" @@ -58,7 +58,7 @@ android:name="_R_G_L_3_G_D_2_P_0" android:pathData=" M-42.19 -25.68 C-42.95,-27.82 -43.09,-29.54 -43.09,-30.8 C-43.09,-32.27 -42.84,-33.65 -42.27,-34.9 C-40.71,-38.35 -37.24,-40.75 -33.2,-40.75 C-27.71,-40.75 -23.26,-36.3 -23.26,-30.8 C-23.26,-28.97 -24.74,-27.49 -26.57,-27.49 C-28.4,-27.49 -29.89,-28.97 -29.89,-30.8 C-29.89,-32.64 -31.37,-34.12 -33.2,-34.12 C-35.04,-34.12 -36.52,-32.64 -36.52,-30.8 C-36.52,-28.23 -35.53,-25.92 -33.92,-24.22 C-32.69,-22.93 -31.48,-22.12 -29.44,-21.53 " android:strokeAlpha="1" - android:strokeColor="@color/biometric_dialog_biometric_color" + android:strokeColor="?android:attr/colorAccent" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="1.45" @@ -69,7 +69,7 @@ android:name="_R_G_L_3_G_D_3_P_0" android:pathData=" M-44.06 -38.17 C-42.87,-39.94 -41.39,-41.41 -39.51,-42.44 C-37.62,-43.47 -35.46,-44.05 -33.16,-44.05 C-30.88,-44.05 -28.72,-43.47 -26.85,-42.45 C-24.97,-41.43 -23.48,-39.97 -22.29,-38.21 " android:strokeAlpha="1" - android:strokeColor="@color/biometric_dialog_biometric_color" + android:strokeColor="?android:attr/colorAccent" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="1.45" @@ -80,7 +80,7 @@ android:name="_R_G_L_3_G_D_4_P_0" android:pathData=" M-25.72 -45.45 C-27.99,-46.76 -30.43,-47.52 -33.28,-47.52 C-36.13,-47.52 -38.51,-46.74 -40.62,-45.45 " android:strokeAlpha="1" - android:strokeColor="@color/biometric_dialog_biometric_color" + android:strokeColor="?android:attr/colorAccent" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="1.45" @@ -101,7 +101,7 @@ android:name="_R_G_L_2_G_D_0_P_0" android:pathData=" M-25.36 -24.41 C-25.93,-24.31 -26.49,-24.27 -26.81,-24.27 C-28.11,-24.27 -29.35,-24.62 -30.43,-25.4 C-32.11,-26.6 -33.2,-28.57 -33.2,-30.79 " android:strokeAlpha="1" - android:strokeColor="@color/biometric_dialog_error_color" + android:strokeColor="?android:attr/colorError" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="1.45" @@ -112,7 +112,7 @@ android:name="_R_G_L_2_G_D_1_P_0" android:pathData=" M-36.14 -21.78 C-37.15,-22.98 -37.72,-23.7 -38.51,-25.29 C-39.33,-26.94 -39.82,-28.78 -39.82,-30.77 C-39.82,-34.43 -36.85,-37.4 -33.19,-37.4 C-29.52,-37.4 -26.55,-34.43 -26.55,-30.77 " android:strokeAlpha="1" - android:strokeColor="@color/biometric_dialog_error_color" + android:strokeColor="?android:attr/colorError" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="1.45" @@ -123,7 +123,7 @@ android:name="_R_G_L_2_G_D_2_P_0" android:pathData=" M-42.19 -25.68 C-42.95,-27.82 -43.09,-29.54 -43.09,-30.8 C-43.09,-32.27 -42.84,-33.65 -42.27,-34.9 C-40.71,-38.35 -37.24,-40.75 -33.2,-40.75 C-27.71,-40.75 -23.26,-36.3 -23.26,-30.8 C-23.26,-28.97 -24.74,-27.49 -26.57,-27.49 C-28.4,-27.49 -29.89,-28.97 -29.89,-30.8 C-29.89,-32.64 -31.37,-34.12 -33.2,-34.12 C-35.04,-34.12 -36.52,-32.64 -36.52,-30.8 C-36.52,-28.23 -35.53,-25.92 -33.92,-24.22 C-32.69,-22.93 -31.48,-22.12 -29.44,-21.53 " android:strokeAlpha="1" - android:strokeColor="@color/biometric_dialog_error_color" + android:strokeColor="?android:attr/colorError" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="1.45" @@ -134,7 +134,7 @@ android:name="_R_G_L_2_G_D_3_P_0" android:pathData=" M-44.06 -38.17 C-42.87,-39.94 -41.39,-41.41 -39.51,-42.44 C-37.62,-43.47 -35.46,-44.05 -33.16,-44.05 C-30.88,-44.05 -28.72,-43.47 -26.85,-42.45 C-24.97,-41.43 -23.48,-39.97 -22.29,-38.21 " android:strokeAlpha="1" - android:strokeColor="@color/biometric_dialog_error_color" + android:strokeColor="?android:attr/colorError" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="1.45" @@ -145,7 +145,7 @@ android:name="_R_G_L_2_G_D_4_P_0" android:pathData=" M-25.72 -45.45 C-27.99,-46.76 -30.43,-47.52 -33.28,-47.52 C-36.13,-47.52 -38.51,-46.74 -40.62,-45.45 " android:strokeAlpha="1" - android:strokeColor="@color/biometric_dialog_error_color" + android:strokeColor="?android:attr/colorError" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="1.45" @@ -162,7 +162,7 @@ android:name="_R_G_L_1_G_D_0_P_0" android:pathData=" M0 -9 C4.97,-9 9,-4.97 9,0 C9,4.97 4.97,9 0,9 C-4.97,9 -9,4.97 -9,0 C-9,-4.97 -4.97,-9 0,-9c " android:strokeAlpha="1" - android:strokeColor="@color/biometric_dialog_error_color" + android:strokeColor="?android:attr/colorError" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" @@ -183,7 +183,7 @@ <path android:name="_R_G_L_0_G_D_0_P_0" android:fillAlpha="1" - android:fillColor="@color/biometric_dialog_error_color" + android:fillColor="?android:attr/colorError" android:fillType="nonZero" android:pathData=" M1.1 3.94 C1.1,4.55 0.61,5.04 0,5.04 C-0.61,5.04 -1.1,4.55 -1.1,3.94 C-1.1,3.33 -0.61,2.84 0,2.84 C0.61,2.84 1.1,3.33 1.1,3.94c " /> </group> @@ -196,7 +196,7 @@ <path android:name="_R_G_L_0_G_D_0_P_1" android:fillAlpha="1" - android:fillColor="@color/biometric_dialog_error_color" + android:fillColor="?android:attr/colorError" android:fillType="nonZero" android:pathData=" M1 -4.06 C1,-4.06 1,-0.06 1,-0.06 C1,0.49 0.55,0.94 0,0.94 C-0.55,0.94 -1,0.49 -1,-0.06 C-1,-0.06 -1,-4.06 -1,-4.06 C-1,-4.61 -0.55,-5.06 0,-5.06 C0.55,-5.06 1,-4.61 1,-4.06c " /> </group> @@ -851,4 +851,4 @@ </set> </aapt:attr> </target> -</animated-vector>
\ No newline at end of file +</animated-vector> diff --git a/packages/SystemUI/res/layout/biometric_dialog.xml b/packages/SystemUI/res/layout/biometric_dialog.xml index 0417e2ede2d3..ec25e311806e 100644 --- a/packages/SystemUI/res/layout/biometric_dialog.xml +++ b/packages/SystemUI/res/layout/biometric_dialog.xml @@ -63,7 +63,7 @@ android:singleLine="true" android:ellipsize="marquee" android:marqueeRepeatLimit="marquee_forever" - android:textColor="@color/biometric_dialog_text_dark_color"/> + android:textColor="?android:attr/textColorPrimary"/> <TextView android:id="@+id/subtitle" @@ -78,7 +78,7 @@ android:singleLine="true" android:ellipsize="marquee" android:marqueeRepeatLimit="marquee_forever" - android:textColor="@color/biometric_dialog_text_dark_color"/> + android:textColor="?android:attr/textColorPrimary"/> <TextView android:id="@+id/description" @@ -90,7 +90,7 @@ android:paddingTop="8dp" android:textSize="16sp" android:maxLines="4" - android:textColor="@color/biometric_dialog_text_dark_color"/> + android:textColor="?android:attr/textColorPrimary"/> <ImageView android:id="@+id/biometric_icon" @@ -112,7 +112,7 @@ android:gravity="center_horizontal" android:accessibilityLiveRegion="polite" android:contentDescription="@string/accessibility_biometric_dialog_help_area" - android:textColor="@color/biometric_dialog_text_light_color"/> + android:textColor="?android:attr/textColorSecondary"/> <LinearLayout android:layout_width="match_parent" @@ -164,4 +164,4 @@ </LinearLayout> -</LinearLayout>
\ No newline at end of file +</LinearLayout> diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml index 0027cc606bad..b3567f892c44 100644 --- a/packages/SystemUI/res/values/colors.xml +++ b/packages/SystemUI/res/values/colors.xml @@ -129,12 +129,7 @@ <color name="smart_reply_button_stroke">#ffdadce0</color> <!-- Biometric dialog colors --> - <color name="biometric_dialog_bg_color">#ffffffff</color> <!-- 100% white --> - <color name="biometric_dialog_text_dark_color">#dd000000</color> <!-- 87% black --> - <color name="biometric_dialog_text_light_color">#89000000</color> <!-- 54% black --> <color name="biometric_dialog_dim_color">#80000000</color> <!-- 50% black --> - <color name="biometric_dialog_error_color">#fff44336</color> <!-- red --> - <color name="biometric_dialog_biometric_color">#ff008577</color> <!-- teal --> <!-- Logout button --> <color name="logout_button_bg_color">#ccffffff</color> diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index 9fe771868568..f77d9234463e 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -844,7 +844,7 @@ <dimen name="burn_in_prevention_offset_y">50dp</dimen> <!-- The maximum offset in either direction that icons move to prevent burn-in on AOD. --> - <dimen name="default_burn_in_prevention_offset">5dp</dimen> + <dimen name="default_burn_in_prevention_offset">15dp</dimen> <dimen name="corner_size">8dp</dimen> <dimen name="top_padding">0dp</dimen> diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index b92fcc6c1d31..3e928a4227cd 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -1851,6 +1851,9 @@ <item>Don\'t show this icon</item> </string-array> + <!-- SysUI Tuner: Switch for showing low-priority notification icons in status bar [CHAR LIMIT=NONE] --> + <string name="tuner_low_priority">Show low-priority notification icons</string> + <!-- SysUI Tuner: Other section --> <string name="other">Other</string> diff --git a/packages/SystemUI/res/xml/tuner_prefs.xml b/packages/SystemUI/res/xml/tuner_prefs.xml index 46ea4949512b..6eec5dc9e1c1 100644 --- a/packages/SystemUI/res/xml/tuner_prefs.xml +++ b/packages/SystemUI/res/xml/tuner_prefs.xml @@ -98,6 +98,11 @@ android:summary="%s" android:entries="@array/clock_options" /> + <com.android.systemui.tuner.TunerSwitch + android:key="low_priority" + android:title="@string/tuner_low_priority" + sysui:defValue="false" /> + </PreferenceScreen> <PreferenceScreen diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/StatsLogCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/StatsLogCompat.java new file mode 100644 index 000000000000..5bc1f2511411 --- /dev/null +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/StatsLogCompat.java @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.shared.system; + +import android.util.StatsLog; + +/** + * Wrapper class to make StatsLog hidden API accessible. + */ +public class StatsLogCompat { + + /** + * StatsLog.write(StatsLog.LAUNCHER_EVENT, int action, int src_state, int dst_state, + * byte[] extension, boolean is_swipe_up_enabled); + */ + public static void write(int action, int srcState, int dstState, byte [] extension, + boolean swipeUpEnabled) { + StatsLog.write(19, action, srcState, dstState, extension, + swipeUpEnabled); + } +} diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java index 7479152f5da0..50b98a10b7f0 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java @@ -23,15 +23,12 @@ import android.animation.ObjectAnimator; import android.animation.PropertyValuesHolder; import android.annotation.ColorInt; import android.app.PendingIntent; -import androidx.lifecycle.LiveData; -import androidx.lifecycle.Observer; import android.content.Context; import android.graphics.Color; import android.graphics.drawable.Drawable; import android.net.Uri; import android.os.Trace; import android.provider.Settings; -import android.text.Layout; import android.text.TextUtils; import android.text.TextUtils.TruncateAt; import android.util.AttributeSet; @@ -41,16 +38,24 @@ import android.view.ViewGroup; import android.view.animation.Animation; import android.widget.Button; import android.widget.LinearLayout; -import android.widget.TextView; + +import androidx.lifecycle.LiveData; +import androidx.lifecycle.Observer; +import androidx.slice.Slice; +import androidx.slice.SliceItem; +import androidx.slice.SliceViewManager; +import androidx.slice.core.SliceQuery; +import androidx.slice.widget.ListContent; +import androidx.slice.widget.RowContent; +import androidx.slice.widget.SliceContent; +import androidx.slice.widget.SliceLiveData; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.graphics.ColorUtils; import com.android.settingslib.Utils; import com.android.systemui.Dependency; import com.android.systemui.Interpolators; -import com.android.systemui.R; import com.android.systemui.keyguard.KeyguardSliceProvider; -import com.android.systemui.statusbar.AlphaOptimizedTextView; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.tuner.TunerService; import com.android.systemui.util.wakelock.KeepAwakeAnimationListener; @@ -58,16 +63,6 @@ import com.android.systemui.util.wakelock.KeepAwakeAnimationListener; import java.util.ArrayList; import java.util.HashMap; import java.util.List; -import java.util.function.Consumer; - -import androidx.slice.Slice; -import androidx.slice.SliceItem; -import androidx.slice.SliceViewManager; -import androidx.slice.core.SliceQuery; -import androidx.slice.widget.ListContent; -import androidx.slice.widget.RowContent; -import androidx.slice.widget.SliceContent; -import androidx.slice.widget.SliceLiveData; /** * View visible under the clock on the lock screen and AoD. @@ -80,8 +75,6 @@ public class KeyguardSliceView extends LinearLayout implements View.OnClickListe private final HashMap<View, PendingIntent> mClickActions; private Uri mKeyguardSliceUri; - @VisibleForTesting - TextView mTitle; private Row mRow; private int mTextColor; private float mDarkAmount = 0; @@ -92,7 +85,6 @@ public class KeyguardSliceView extends LinearLayout implements View.OnClickListe * Runnable called whenever the view contents change. */ private Runnable mContentChangeListener; - private boolean mHasHeader; private Slice mSlice; private boolean mPulsing; @@ -128,7 +120,6 @@ public class KeyguardSliceView extends LinearLayout implements View.OnClickListe @Override protected void onFinishInflate() { super.onFinishInflate(); - mTitle = findViewById(R.id.title); mRow = findViewById(R.id.row); mTextColor = Utils.getColorAttrDefaultColor(mContext, R.attr.wallpaperTextColor); mIconSize = (int) mContext.getResources().getDimension(R.dimen.widget_icon_size); @@ -154,7 +145,6 @@ public class KeyguardSliceView extends LinearLayout implements View.OnClickListe private void showSlice() { Trace.beginSection("KeyguardSliceView#showSlice"); if (mPulsing || mSlice == null) { - mTitle.setVisibility(GONE); mRow.setVisibility(GONE); if (mContentChangeListener != null) { mContentChangeListener.run(); @@ -164,8 +154,9 @@ public class KeyguardSliceView extends LinearLayout implements View.OnClickListe ListContent lc = new ListContent(getContext(), mSlice); SliceContent headerContent = lc.getHeader(); - mHasHeader = headerContent != null && !headerContent.getSliceItem().hasHint(HINT_LIST_ITEM); - List<SliceContent> subItems = new ArrayList<SliceContent>(); + boolean hasHeader = headerContent != null + && !headerContent.getSliceItem().hasHint(HINT_LIST_ITEM); + List<SliceContent> subItems = new ArrayList<>(); for (int i = 0; i < lc.getRowItems().size(); i++) { SliceContent subItem = lc.getRowItems().get(i); String itemUri = subItem.getSliceItem().getSlice().getUri().toString(); @@ -174,21 +165,11 @@ public class KeyguardSliceView extends LinearLayout implements View.OnClickListe subItems.add(subItem); } } - if (!mHasHeader) { - mTitle.setVisibility(GONE); - } else { - mTitle.setVisibility(VISIBLE); - - RowContent header = lc.getHeader(); - SliceItem mainTitle = header.getTitleItem(); - CharSequence title = mainTitle != null ? mainTitle.getText() : null; - mTitle.setText(title); - } mClickActions.clear(); final int subItemsCount = subItems.size(); final int blendedColor = getTextColor(); - final int startIndex = mHasHeader ? 1 : 0; // First item is header; skip it + final int startIndex = hasHeader ? 1 : 0; // First item is header; skip it mRow.setVisibility(subItemsCount > 0 ? VISIBLE : GONE); for (int i = startIndex; i < subItemsCount; i++) { RowContent rc = (RowContent) subItems.get(i); @@ -200,7 +181,7 @@ public class KeyguardSliceView extends LinearLayout implements View.OnClickListe button = new KeyguardSliceButton(mContext); button.setTextColor(blendedColor); button.setTag(itemTag); - final int viewIndex = i - (mHasHeader ? 1 : 0); + final int viewIndex = i - (hasHeader ? 1 : 0); mRow.addView(button, viewIndex); } @@ -303,7 +284,6 @@ public class KeyguardSliceView extends LinearLayout implements View.OnClickListe private void updateTextColors() { final int blendedColor = getTextColor(); - mTitle.setTextColor(blendedColor); int childCount = mRow.getChildCount(); for (int i = 0; i < childCount; i++) { View v = mRow.getChildAt(i); @@ -333,10 +313,6 @@ public class KeyguardSliceView extends LinearLayout implements View.OnClickListe mContentChangeListener = contentChangeListener; } - public boolean hasHeader() { - return mHasHeader; - } - /** * LiveData observer lifecycle. * @param slice the new slice content. @@ -352,7 +328,7 @@ public class KeyguardSliceView extends LinearLayout implements View.OnClickListe setupUri(newValue); } - public void setupUri(String uriString) { + private void setupUri(String uriString) { if (uriString == null) { uriString = KeyguardSliceProvider.KEYGUARD_SLICE_URI; } @@ -564,46 +540,6 @@ public class KeyguardSliceView extends LinearLayout implements View.OnClickListe } } - /** - * A text view that will split its contents in 2 lines when possible. - */ - static class TitleView extends AlphaOptimizedTextView { - - public TitleView(Context context) { - super(context); - } - - public TitleView(Context context, AttributeSet attrs) { - super(context, attrs); - } - - public TitleView(Context context, AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); - } - - public TitleView(Context context, AttributeSet attrs, int defStyleAttr, - int defStyleRes) { - super(context, attrs, defStyleAttr, defStyleRes); - } - - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - super.onMeasure(widthMeasureSpec, heightMeasureSpec); - - Layout layout = getLayout(); - int lineCount = layout.getLineCount(); - boolean ellipsizing = layout.getEllipsisCount(lineCount - 1) != 0; - if (lineCount > 0 && !ellipsizing) { - CharSequence title = getText(); - CharSequence bestLineBreak = findBestLineBreak(title); - if (!TextUtils.equals(title, bestLineBreak)) { - setText(bestLineBreak); - super.onMeasure(widthMeasureSpec, heightMeasureSpec); - } - } - } - } - private class SliceViewTransitionListener implements LayoutTransition.TransitionListener { @Override public void startTransition(LayoutTransition transition, ViewGroup container, View view, @@ -619,15 +555,6 @@ public class KeyguardSliceView extends LinearLayout implements View.OnClickListe .setInterpolator(Interpolators.ALPHA_IN) .start(); break; - case LayoutTransition.DISAPPEARING: - if (view == mTitle) { - // Translate the view to the inverse of its height, so the layout event - // won't misposition it. - LayoutParams params = (LayoutParams) mTitle.getLayoutParams(); - int margin = params.topMargin + params.bottomMargin; - mTitle.setTranslationY(-mTitle.getHeight() - margin); - } - break; } } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java index db786677f0b3..a403b751b548 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java @@ -200,10 +200,9 @@ public class KeyguardStatusView extends GridLayout implements * Moves clock, adjusting margins when slice content changes. */ private void onSliceContentChanged() { - boolean smallClock = mKeyguardSlice.hasHeader() || mPulsing; RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams) mClockView.getLayoutParams(); - layoutParams.bottomMargin = smallClock ? mSmallClockPadding : 0; + layoutParams.bottomMargin = mPulsing ? mSmallClockPadding : 0; mClockView.setLayoutParams(layoutParams); } @@ -214,17 +213,15 @@ public class KeyguardStatusView extends GridLayout implements public void onLayoutChange(View view, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) { int heightOffset = mPulsing || mWasPulsing ? 0 : getHeight() - mLastLayoutHeight; - boolean hasHeader = mKeyguardSlice.hasHeader(); - boolean smallClock = hasHeader || mPulsing; long duration = KeyguardSliceView.DEFAULT_ANIM_DURATION; - long delay = smallClock || mWasPulsing ? 0 : duration / 4; + long delay = mPulsing || mWasPulsing ? 0 : duration / 4; mWasPulsing = false; boolean shouldAnimate = mKeyguardSlice.getLayoutTransition() != null && mKeyguardSlice.getLayoutTransition().isRunning(); if (view == mClockView) { - float clockScale = smallClock ? mSmallClockScale : 1; - Paint.Style style = smallClock ? Paint.Style.FILL_AND_STROKE : Paint.Style.FILL; + float clockScale = mPulsing ? mSmallClockScale : 1; + Paint.Style style = mPulsing ? Paint.Style.FILL_AND_STROKE : Paint.Style.FILL; mClockView.animate().cancel(); if (shouldAnimate) { mClockView.setY(oldTop + heightOffset); @@ -431,11 +428,6 @@ public class KeyguardStatusView extends GridLayout implements mWasPulsing = true; } mPulsing = pulsing; - // Animation can look really weird when the slice has a header, let's hide the views - // immediately instead of fading them away. - if (mKeyguardSlice.hasHeader()) { - animate = false; - } mKeyguardSlice.setPulsing(pulsing, animate); updateDozeVisibleViews(); } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDialogImpl.java b/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDialogImpl.java index 8fc46891c3a1..903e178b9107 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDialogImpl.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDialogImpl.java @@ -18,6 +18,7 @@ package com.android.systemui.biometrics; import android.content.Context; import android.content.pm.PackageManager; +import android.content.res.Configuration; import android.hardware.biometrics.BiometricAuthenticator; import android.hardware.biometrics.BiometricPrompt; import android.hardware.biometrics.IBiometricPromptReceiver; @@ -53,6 +54,7 @@ public class BiometricDialogImpl extends SystemUI implements CommandQueue.Callba private static final int MSG_BUTTON_POSITIVE = 8; private Map<Integer, BiometricDialogView> mDialogs; // BiometricAuthenticator type, view + private SomeArgs mCurrentDialogArgs; private BiometricDialogView mCurrentDialog; private WindowManager mWindowManager; private IBiometricPromptReceiver mReceiver; @@ -116,6 +118,15 @@ public class BiometricDialogImpl extends SystemUI implements CommandQueue.Callba @Override public void start() { + createDialogs(); + + if (!mDialogs.isEmpty()) { + getComponent(CommandQueue.class).addCallbacks(this); + mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE); + } + } + + private void createDialogs() { final PackageManager pm = mContext.getPackageManager(); mDialogs = new HashMap<>(); if (pm.hasSystemFeature(PackageManager.FEATURE_FACE)) { @@ -125,11 +136,6 @@ public class BiometricDialogImpl extends SystemUI implements CommandQueue.Callba mDialogs.put(BiometricAuthenticator.TYPE_FINGERPRINT, new FingerprintDialogView(mContext, mCallback)); } - - if (!mDialogs.isEmpty()) { - getComponent(CommandQueue.class).addCallbacks(this); - mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE); - } } @Override @@ -173,6 +179,7 @@ public class BiometricDialogImpl extends SystemUI implements CommandQueue.Callba } private void handleShowDialog(SomeArgs args) { + mCurrentDialogArgs = args; final int type = args.argi1; mCurrentDialog = mDialogs.get(type); @@ -268,4 +275,18 @@ public class BiometricDialogImpl extends SystemUI implements CommandQueue.Callba private void handleUserCanceled() { handleHideDialog(true /* userCanceled */); } + + @Override + protected void onConfigurationChanged(Configuration newConfig) { + if (mDialogShowing) { + mCurrentDialog.forceRemove(); + } + createDialogs(); + if (mDialogShowing) { + mCurrentDialog = mDialogs.get(mCurrentDialogArgs.argi1); + mCurrentDialog.forceRemove(); // Prevents intro animation when reattaching. + mDialogShowing = false; + handleShowDialog(mCurrentDialogArgs); + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDialogView.java b/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDialogView.java index 7d77929556a5..1d836ecb1c10 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDialogView.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDialogView.java @@ -17,7 +17,7 @@ package com.android.systemui.biometrics; import android.content.Context; -import android.graphics.Color; +import android.content.res.TypedArray; import android.graphics.PixelFormat; import android.hardware.biometrics.BiometricPrompt; import android.os.Binder; @@ -122,10 +122,12 @@ public abstract class BiometricDialogView extends LinearLayout { mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE); mAnimationTranslationOffset = getResources() .getDimension(R.dimen.biometric_dialog_animation_translation_offset); - mErrorColor = Color.parseColor( - getResources().getString(R.color.biometric_dialog_error_color)); - mTextColor = Color.parseColor( - getResources().getString(R.color.biometric_dialog_text_light_color)); + + TypedArray array = getContext().obtainStyledAttributes( + new int[]{android.R.attr.colorError, android.R.attr.textColorSecondary}); + mErrorColor = array.getColor(0, 0); + mTextColor = array.getColor(1, 0); + array.recycle(); DisplayMetrics metrics = new DisplayMetrics(); mWindowManager.getDefaultDisplay().getMetrics(metrics); @@ -167,7 +169,6 @@ public abstract class BiometricDialogView extends LinearLayout { final ImageView icon = mLayout.findViewById(R.id.biometric_icon); icon.setContentDescription(getResources().getString(getIconDescriptionResourceId())); - mErrorText.setText(getResources().getString(getHintStringResourceId())); setDismissesDialog(space); setDismissesDialog(leftSpace); @@ -189,6 +190,8 @@ public abstract class BiometricDialogView extends LinearLayout { public void onAttachedToWindow() { super.onAttachedToWindow(); + mErrorText.setText(getHintStringResourceId()); + final TextView title = mLayout.findViewById(R.id.title); final TextView subtitle = mLayout.findViewById(R.id.subtitle); final TextView description = mLayout.findViewById(R.id.description); diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeReceiver.java b/packages/SystemUI/src/com/android/systemui/doze/DozeReceiver.java index dcb3882be85e..30dfd3622198 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeReceiver.java +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeReceiver.java @@ -20,5 +20,19 @@ package com.android.systemui.doze; * Interface for class that cares about doze states. */ public interface DozeReceiver { + + /** + * If device enters or leaves doze mode + */ void setDozing(boolean dozing); + + /** + * Invoked every time a minute is elapsed in doze mode + */ + void dozeTimeTick(); + + /** + * When view is double tapped in doze mode. + */ + void onDozeDoubleTap(); } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileBaseView.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileBaseView.java index d42127e74944..0638998d8e67 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileBaseView.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileBaseView.java @@ -194,6 +194,7 @@ public class QSTileBaseView extends com.android.systemui.plugins.qs.QSTileView { } setClickable(state.state != Tile.STATE_UNAVAILABLE); + setLongClickable(state.handlesLongClick); mIcon.setIcon(state, allowAnimations); setContentDescription(state.contentDescription); @@ -287,10 +288,14 @@ public class QSTileBaseView extends com.android.systemui.plugins.qs.QSTileView { info.setText(label); info.setChecked(b); info.setCheckable(true); - info.addAction( - new AccessibilityNodeInfo.AccessibilityAction( - AccessibilityNodeInfo.AccessibilityAction.ACTION_LONG_CLICK.getId(), - getResources().getString(R.string.accessibility_long_click_tile))); + if (isLongClickable()) { + info.addAction( + new AccessibilityNodeInfo.AccessibilityAction( + AccessibilityNodeInfo.AccessibilityAction + .ACTION_LONG_CLICK.getId(), + getResources().getString( + R.string.accessibility_long_click_tile))); + } } } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/FlashlightTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/FlashlightTile.java index f2ead1cbca94..d7ac2532982d 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/FlashlightTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/FlashlightTile.java @@ -18,7 +18,6 @@ package com.android.systemui.qs.tiles; import android.app.ActivityManager; import android.content.Intent; -import android.graphics.drawable.Drawable; import android.provider.MediaStore; import android.service.quicksettings.Tile; import android.widget.Switch; @@ -50,7 +49,9 @@ public class FlashlightTile extends QSTileImpl<BooleanState> implements @Override public BooleanState newTileState() { - return new BooleanState(); + BooleanState state = new BooleanState(); + state.handlesLongClick = false; + return state; } @Override diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java index 38011d9c1e87..427d169d6b90 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java @@ -15,6 +15,7 @@ */ package com.android.systemui.statusbar; +import static android.app.NotificationManager.IMPORTANCE_DEFAULT; import static android.app.admin.DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED; import android.app.ActivityManager; @@ -292,7 +293,8 @@ public class NotificationLockscreenUserManagerImpl implements return false; } return mShowLockscreenNotifications - && !getEntryManager().getNotificationData().isAmbient(sbn.getKey()); + && getEntryManager().getNotificationData().getImportance(sbn.getKey()) + >= IMPORTANCE_DEFAULT; } private void setShowLockscreenNotifications(boolean show) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java index cd3da123ed32..39b8a9fd217c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java @@ -43,13 +43,13 @@ import com.android.systemui.statusbar.notification.NotificationUtils; import com.android.systemui.statusbar.notification.row.ActivatableNotificationView; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.notification.row.ExpandableView; -import com.android.systemui.statusbar.phone.NotificationIconContainer; import com.android.systemui.statusbar.notification.stack.AmbientState; import com.android.systemui.statusbar.notification.stack.AnimationProperties; import com.android.systemui.statusbar.notification.stack.ExpandableViewState; import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout; import com.android.systemui.statusbar.notification.stack.StackScrollState; import com.android.systemui.statusbar.notification.stack.ViewState; +import com.android.systemui.statusbar.phone.NotificationIconContainer; /** * A notification shelf view that is placed inside the notification scroller. It manages the @@ -58,7 +58,6 @@ import com.android.systemui.statusbar.notification.stack.ViewState; public class NotificationShelf extends ActivatableNotificationView implements View.OnLayoutChangeListener { - public static final boolean SHOW_AMBIENT_ICONS = true; private static final boolean USE_ANIMATIONS_WHEN_OPENING = SystemProperties.getBoolean("debug.icon_opening_animations", true); private static final boolean ICON_ANMATIONS_WHILE_SCROLLING @@ -176,21 +175,6 @@ public class NotificationShelf extends ActivatableNotificationView implements updateInteractiveness(); } - public void fadeInTranslating() { - mShelfIcons.setTranslationY(-mShelfAppearTranslation); - mShelfIcons.setAlpha(0); - mShelfIcons.animate() - .setInterpolator(Interpolators.DECELERATE_QUINT) - .translationY(0) - .setDuration(SHELF_IN_TRANSLATION_DURATION) - .start(); - mShelfIcons.animate() - .alpha(1) - .setInterpolator(Interpolators.LINEAR) - .setDuration(SHELF_IN_TRANSLATION_DURATION) - .start(); - } - @Override protected View getContentView() { return mShelfIcons; @@ -417,18 +401,16 @@ public class NotificationShelf extends ActivatableNotificationView implements float maxTop = row.getTranslationY(); StatusBarIconView icon = row.getEntry().expandedIcon; float shelfIconPosition = getTranslationY() + icon.getTop() + icon.getTranslationY(); - if (shelfIconPosition < maxTop && !mAmbientState.isDark()) { + if (shelfIconPosition < maxTop) { int top = (int) (maxTop - shelfIconPosition); Rect clipRect = new Rect(0, top, icon.getWidth(), Math.max(top, icon.getHeight())); icon.setClipBounds(clipRect); - } else { - icon.setClipBounds(null); } } private void updateContinuousClipping(final ExpandableNotificationRow row) { StatusBarIconView icon = row.getEntry().expandedIcon; - boolean needsContinuousClipping = ViewState.isAnimatingY(icon) && !mAmbientState.isDark(); + boolean needsContinuousClipping = ViewState.isAnimatingY(icon); boolean isContinuousClipping = icon.getTag(TAG_CONTINUOUS_CLIPPING) != null; if (needsContinuousClipping && !isContinuousClipping) { final ViewTreeObserver observer = icon.getViewTreeObserver(); @@ -619,9 +601,7 @@ public class NotificationShelf extends ActivatableNotificationView implements iconState.translateContent = false; } float transitionAmount; - if (mAmbientState.getDarkAmount() > 0 && !row.isInShelf()) { - transitionAmount = mAmbientState.isFullyDark() ? 1 : 0; - } else if (isLastChild || !USE_ANIMATIONS_WHEN_OPENING || iconState.useFullTransitionAmount + if (isLastChild || !USE_ANIMATIONS_WHEN_OPENING || iconState.useFullTransitionAmount || iconState.useLinearTransitionAmount) { transitionAmount = iconTransitionAmount; } else { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java index 1e04377e25b2..8b61a5bccc3c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java @@ -206,10 +206,6 @@ public class StatusBarIconView extends AnimatedImageView implements StatusIconDi mIconScale = SYSTEM_ICON_SCALE; } - public float getIconScaleFullyDark() { - return (float) mStatusBarIconDrawingSizeDark / mStatusBarIconDrawingSize; - } - public float getIconScale() { return mIconScale; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java index 22c37fdb11cc..6193de59d1d0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java @@ -82,7 +82,6 @@ import com.android.systemui.plugins.PluginListener; import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin; import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin.MenuItem; import com.android.systemui.shared.plugins.PluginManager; -import com.android.systemui.statusbar.NotificationShelf; import com.android.systemui.statusbar.RemoteInputController; import com.android.systemui.statusbar.StatusBarIconView; import com.android.systemui.statusbar.notification.AboveShelfChangedListener; @@ -1501,7 +1500,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView private void updateIconVisibilities() { boolean visible = isChildInGroup() - || (isBelowSpeedBump() && !NotificationShelf.SHOW_AMBIENT_ICONS) + || isBelowSpeedBump() || mIconsVisible; for (NotificationContentView l : mLayouts) { l.setIconsVisible(visible); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java index 30d17017ca1c..003f158d7822 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java @@ -18,6 +18,7 @@ package com.android.systemui.statusbar.notification.stack; import static com.android.systemui.statusbar.notification.ActivityLaunchAnimator .ExpandAnimationParameters; +import static com.android.systemui.statusbar.phone.NotificationIconAreaController.LOW_PRIORITY; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; @@ -27,6 +28,7 @@ import android.animation.TimeAnimator; import android.animation.ValueAnimator; import android.annotation.NonNull; import android.annotation.Nullable; +import android.app.NotificationManager; import android.app.WallpaperManager; import android.content.Context; import android.content.Intent; @@ -111,7 +113,6 @@ import com.android.systemui.statusbar.notification.row.NotificationGuts; import com.android.systemui.statusbar.notification.row.NotificationGutsManager; import com.android.systemui.statusbar.notification.row.NotificationSnooze; import com.android.systemui.statusbar.notification.row.StackScrollerDecorView; -import com.android.systemui.statusbar.phone.DozeParameters; import com.android.systemui.statusbar.phone.HeadsUpAppearanceController; import com.android.systemui.statusbar.phone.HeadsUpManagerPhone; import com.android.systemui.statusbar.phone.HeadsUpTouchHelper; @@ -127,6 +128,7 @@ import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener; import com.android.systemui.statusbar.policy.HeadsUpUtil; import com.android.systemui.statusbar.policy.ScrollAdapter; +import com.android.systemui.tuner.TunerService; import java.io.FileDescriptor; import java.io.PrintWriter; @@ -159,6 +161,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd private int mCurrentStackHeight = Integer.MAX_VALUE; private final Paint mBackgroundPaint = new Paint(); private final boolean mShouldDrawNotificationBackground; + private boolean mLowPriorityBeforeSpeedBump; private float mExpandedHeight; private int mOwnScrollY; @@ -515,6 +518,13 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd mDebugPaint.setStyle(Paint.Style.STROKE); } mClearAllEnabled = res.getBoolean(R.bool.config_enableNotificationsClearAll); + + TunerService tunerService = Dependency.get(TunerService.class); + tunerService.addTunable((key, newValue) -> { + if (key.equals(LOW_PRIORITY)) { + mLowPriorityBeforeSpeedBump = "1".equals(newValue); + } + }, LOW_PRIORITY); } @Override @@ -662,37 +672,29 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd @ShadeViewRefactor(RefactorComponent.DECORATOR) private void drawBackground(Canvas canvas) { - final int lockScreenLeft = mSidePaddings; - final int lockScreenRight = getWidth() - mSidePaddings; - final int lockScreenTop = mCurrentBounds.top; - final int lockScreenBottom = mCurrentBounds.bottom; - final int darkLeft = getWidth() / 2; - final int darkTop = mRegularTopPadding; - - if (mAmbientState.hasPulsingNotifications()) { - // No divider, we have a notification icon instead - } else if (mAmbientState.isFullyDark()) { - // Only draw divider on AOD if we actually have notifications - if (mFirstVisibleBackgroundChild != null) { - canvas.drawRect(darkLeft, darkTop, darkLeft, darkTop, mBackgroundPaint); - } - } else { - float yProgress = 1 - mInterpolatedDarkAmount; - float xProgress = mDarkXInterpolator.getInterpolation( - (1 - mLinearDarkAmount) * mBackgroundXFactor); + int lockScreenLeft = mSidePaddings; + int lockScreenRight = getWidth() - mSidePaddings; + int lockScreenTop = mCurrentBounds.top; + int lockScreenBottom = mCurrentBounds.bottom; + int darkLeft = getWidth() / 2; + int darkTop = mRegularTopPadding; - mBackgroundAnimationRect.set( - (int) MathUtils.lerp(darkLeft, lockScreenLeft, xProgress), - (int) MathUtils.lerp(darkTop, lockScreenTop, yProgress), - (int) MathUtils.lerp(darkLeft, lockScreenRight, xProgress), - (int) MathUtils.lerp(darkTop, lockScreenBottom, yProgress)); + float yProgress = 1 - mInterpolatedDarkAmount; + float xProgress = mDarkXInterpolator.getInterpolation( + (1 - mLinearDarkAmount) * mBackgroundXFactor); - if (!mAmbientState.isDark() || mFirstVisibleBackgroundChild != null) { - canvas.drawRoundRect(mBackgroundAnimationRect.left, mBackgroundAnimationRect.top, - mBackgroundAnimationRect.right, mBackgroundAnimationRect.bottom, - mCornerRadius, mCornerRadius, mBackgroundPaint); - } + mBackgroundAnimationRect.set( + (int) MathUtils.lerp(darkLeft, lockScreenLeft, xProgress), + (int) MathUtils.lerp(darkTop, lockScreenTop, yProgress), + (int) MathUtils.lerp(darkLeft, lockScreenRight, xProgress), + (int) MathUtils.lerp(darkTop, lockScreenBottom, yProgress)); + + if (!mAmbientState.isDark() || mFirstVisibleBackgroundChild != null) { + canvas.drawRoundRect(mBackgroundAnimationRect.left, mBackgroundAnimationRect.top, + mBackgroundAnimationRect.right, mBackgroundAnimationRect.bottom, + mCornerRadius, mCornerRadius, mBackgroundPaint); } + updateClipping(); } @@ -1099,14 +1101,13 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) public void updateClipping() { - boolean animatingClipping = mInterpolatedDarkAmount > 0 && mInterpolatedDarkAmount < 1; boolean clipped = mRequestedClipBounds != null && !mInHeadsUpPinnedMode && !mHeadsUpAnimatingAway; if (mIsClipped != clipped) { mIsClipped = clipped; } - if (animatingClipping) { + if (mAmbientState.isDarkAtAll()) { setClipBounds(mBackgroundAnimationRect); } else if (clipped) { setClipBounds(mRequestedClipBounds); @@ -4246,13 +4247,9 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd boolean nowDarkAtAll = mAmbientState.isDarkAtAll(); if (nowFullyDark != wasFullyDark) { updateContentHeight(); - DozeParameters dozeParameters = DozeParameters.getInstance(mContext); - if (nowFullyDark && dozeParameters.shouldControlScreenOff()) { - mShelf.fadeInTranslating(); - } - if (mIconAreaController != null) { - mIconAreaController.setFullyDark(nowFullyDark); - } + } + if (mIconAreaController != null) { + mIconAreaController.setDarkAmount(interpolatedDarkAmount); } if (!wasDarkAtAll && nowDarkAtAll) { resetExposedMenuView(true /* animate */, true /* animate */); @@ -5087,8 +5084,16 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd } ExpandableNotificationRow row = (ExpandableNotificationRow) view; currentIndex++; - if (!mEntryManager.getNotificationData().isAmbient( - row.getStatusBarNotification().getKey())) { + boolean beforeSpeedBump; + if (mLowPriorityBeforeSpeedBump) { + beforeSpeedBump = !mEntryManager.getNotificationData().isAmbient( + row.getStatusBarNotification().getKey()); + } else { + beforeSpeedBump = mEntryManager.getNotificationData().getImportance( + row.getStatusBarNotification().getKey()) + >= NotificationManager.IMPORTANCE_DEFAULT; + } + if (beforeSpeedBump) { speedBumpIndex = currentIndex; } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java index fa63831b5e1b..d4de8fce3750 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java @@ -20,7 +20,6 @@ import static android.app.StatusBarManager.DISABLE_SYSTEM_INFO; import android.annotation.Nullable; import android.app.Fragment; -import android.app.StatusBarManager; import android.os.Bundle; import android.os.Parcelable; import android.util.SparseArray; @@ -35,8 +34,8 @@ import com.android.systemui.Interpolators; import com.android.systemui.R; import com.android.systemui.SysUiServiceProvider; import com.android.systemui.statusbar.CommandQueue; +import com.android.systemui.statusbar.StatusBarStateController; import com.android.systemui.statusbar.phone.StatusBarIconController.DarkIconManager; -import com.android.systemui.statusbar.policy.DarkIconDispatcher; import com.android.systemui.statusbar.policy.EncryptionHelper; import com.android.systemui.statusbar.policy.KeyguardMonitor; import com.android.systemui.statusbar.policy.NetworkController; @@ -55,6 +54,7 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue public static final int FADE_IN_DURATION = 320; public static final int FADE_IN_DELAY = 50; private PhoneStatusBarView mStatusBar; + private StatusBarStateController mStatusBarStateController; private KeyguardMonitor mKeyguardMonitor; private NetworkController mNetworkController; private LinearLayout mSystemIconArea; @@ -78,6 +78,7 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue super.onCreate(savedInstanceState); mKeyguardMonitor = Dependency.get(KeyguardMonitor.class); mNetworkController = Dependency.get(NetworkController.class); + mStatusBarStateController = Dependency.get(StatusBarStateController.class); mStatusBarComponent = SysUiServiceProvider.getComponent(getContext(), StatusBar.class); mCommandQueue = SysUiServiceProvider.getComponent(getContext(), CommandQueue.class); } @@ -207,6 +208,12 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue state |= DISABLE_SYSTEM_INFO; } } + + if (mStatusBarStateController.isDozing()) { + state |= DISABLE_CLOCK | DISABLE_SYSTEM_INFO; + state &= ~DISABLE_NOTIFICATION_ICONS; + } + return state; } @@ -241,7 +248,8 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue * don't set the clock GONE otherwise it'll mess up the animation. */ private int clockHiddenMode() { - if (!mStatusBar.isClosed() && !mKeyguardMonitor.isShowing()) { + if (!mStatusBar.isClosed() && !mKeyguardMonitor.isShowing() + && !mStatusBarStateController.isDozing()) { return View.INVISIBLE; } return View.GONE; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeScrimController.java index 25db4f20a450..a0597dc66d14 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeScrimController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeScrimController.java @@ -17,7 +17,6 @@ package com.android.systemui.statusbar.phone; import android.annotation.NonNull; -import android.content.Context; import android.os.Handler; import android.util.Log; @@ -33,7 +32,6 @@ public class DozeScrimController { private final DozeParameters mDozeParameters; private final Handler mHandler = new Handler(); - private final ScrimController mScrimController; private boolean mDozing; private DozeHost.PulseCallback mPulseCallback; @@ -83,9 +81,7 @@ public class DozeScrimController { } }; - public DozeScrimController(ScrimController scrimController, Context context, - DozeParameters dozeParameters) { - mScrimController = scrimController; + public DozeScrimController(DozeParameters dozeParameters) { mDozeParameters = dozeParameters; } @@ -117,8 +113,6 @@ public class DozeScrimController { // be invoked when we're done so that the caller can drop the pulse wakelock. mPulseCallback = callback; mPulseReason = reason; - - mScrimController.transitionTo(ScrimState.PULSING, mScrimCallback); } public void pulseOutNow() { @@ -180,13 +174,11 @@ public class DozeScrimController { mHandler.removeCallbacks(mPulseOutExtended); if (DEBUG) Log.d(TAG, "Pulse out, mDozing=" + mDozing); if (!mDozing) return; - mScrimController.transitionTo(ScrimState.AOD, - new ScrimController.Callback() { - @Override - public void onDisplayBlanked() { - pulseFinished(); - } - }); + pulseFinished(); } }; + + public ScrimController.Callback getScrimCallback() { + return mScrimCallback; + } }
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java index e85ff8ef22c0..96b753679796 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java @@ -347,6 +347,7 @@ public class KeyguardStatusBarView extends RelativeLayout mIconManager = new TintedIconManager(findViewById(R.id.statusIcons)); Dependency.get(StatusBarIconController.class).addIconGroup(mIconManager); onThemeChanged(); + updateDozeState(); } @Override @@ -514,8 +515,7 @@ public class KeyguardStatusBarView extends RelativeLayout private void updateDozeState() { float alpha = 1f - mDarkAmount; int visibility = alpha != 0f ? VISIBLE : INVISIBLE; - mCarrierLabel.setAlpha(alpha); - mCarrierLabel.setVisibility(visibility); + mCarrierLabel.setAlpha(alpha * alpha); mStatusIconContainer.setAlpha(alpha); mStatusIconContainer.setVisibility(visibility); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java index 21b98db11a36..c6097d980c70 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java @@ -1,29 +1,34 @@ package com.android.systemui.statusbar.phone; +import static com.android.systemui.doze.util.BurnInHelperKt.getBurnInOffset; + +import android.app.NotificationManager; import android.content.Context; import android.content.res.Resources; import android.graphics.Color; import android.graphics.Rect; -import androidx.annotation.NonNull; -import androidx.collection.ArrayMap; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.FrameLayout; +import androidx.annotation.NonNull; +import androidx.collection.ArrayMap; + import com.android.internal.statusbar.StatusBarIcon; import com.android.internal.util.ContrastColorUtil; +import com.android.internal.widget.ViewClippingUtil; import com.android.systemui.Dependency; import com.android.systemui.R; -import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; -import com.android.systemui.statusbar.notification.NotificationData; -import com.android.systemui.statusbar.notification.NotificationEntryManager; import com.android.systemui.statusbar.NotificationShelf; import com.android.systemui.statusbar.StatusBarIconView; +import com.android.systemui.statusbar.notification.NotificationData; +import com.android.systemui.statusbar.notification.NotificationEntryManager; import com.android.systemui.statusbar.notification.NotificationUtils; +import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.policy.DarkIconDispatcher; import com.android.systemui.statusbar.policy.DarkIconDispatcher.DarkReceiver; -import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout; +import com.android.systemui.tuner.TunerService; import java.util.ArrayList; import java.util.function.Function; @@ -33,9 +38,23 @@ import java.util.function.Function; * normally reserved for notifications. */ public class NotificationIconAreaController implements DarkReceiver { + + public static final String LOW_PRIORITY = "low_priority"; + private final ContrastColorUtil mContrastColorUtil; private final NotificationEntryManager mEntryManager; private final Runnable mUpdateStatusBarIcons = this::updateStatusBarIcons; + private final TunerService.Tunable mTunable = new TunerService.Tunable() { + @Override + public void onTuningChanged(String key, String newValue) { + if (key.equals(LOW_PRIORITY)) { + mShowLowPriority = "1".equals(newValue); + if (mNotificationScrollLayout != null) { + updateStatusBarIcons(); + } + } + } + }; private int mIconSize; private int mIconHPadding; @@ -49,6 +68,23 @@ public class NotificationIconAreaController implements DarkReceiver { private ViewGroup mNotificationScrollLayout; private Context mContext; private boolean mFullyDark; + private boolean mShowLowPriority; + + /** + * Ratio representing being awake or in ambient mode, where 1 is dark and 0 awake. + */ + private float mDarkAmount; + /** + * Maximum translation to avoid burn in. + */ + private int mBurnInOffset; + /** + * Height of the keyguard status bar (not the one after unlocking.) + */ + private int mKeyguardStatusBarHeight; + + private final ViewClippingUtil.ClippingParameters mClippingParameters = + view -> view instanceof StatusBarWindowView; public NotificationIconAreaController(Context context, StatusBar statusBar) { mStatusBar = statusBar; @@ -56,6 +92,8 @@ public class NotificationIconAreaController implements DarkReceiver { mContext = context; mEntryManager = Dependency.get(NotificationEntryManager.class); + Dependency.get(TunerService.class).addTunable(mTunable, LOW_PRIORITY); + initializeNotificationAreaViews(context); } @@ -104,6 +142,9 @@ public class NotificationIconAreaController implements DarkReceiver { Resources res = context.getResources(); mIconSize = res.getDimensionPixelSize(com.android.internal.R.dimen.status_bar_icon_size); mIconHPadding = res.getDimensionPixelSize(R.dimen.status_bar_icon_padding); + mBurnInOffset = res.getDimensionPixelSize(R.dimen.default_burn_in_prevention_offset); + mKeyguardStatusBarHeight = res + .getDimensionPixelSize(R.dimen.status_bar_header_height_keyguard); } /** @@ -142,10 +183,16 @@ public class NotificationIconAreaController implements DarkReceiver { } protected boolean shouldShowNotificationIcon(NotificationData.Entry entry, - boolean showAmbient, boolean hideDismissed, boolean hideRepliedMessages) { + boolean showAmbient, boolean showLowPriority, boolean hideDismissed, + boolean hideRepliedMessages) { if (mEntryManager.getNotificationData().isAmbient(entry.key) && !showAmbient) { return false; } + if (!showLowPriority + && mEntryManager.getNotificationData().getImportance(entry.key) + < NotificationManager.IMPORTANCE_DEFAULT) { + return false; + } if (!StatusBar.isTopLevelChild(entry)) { return false; } @@ -181,13 +228,14 @@ public class NotificationIconAreaController implements DarkReceiver { private void updateShelfIcons() { updateIconsForLayout(entry -> entry.expandedIcon, mShelfIcons, - NotificationShelf.SHOW_AMBIENT_ICONS, false /* hideDismissed */, - mFullyDark /* hideRepliedMessages */); + false /* showAmbient */, !mFullyDark /* showLowPriority */, + false /* hideDismissed */, mFullyDark /* hideRepliedMessages */); } public void updateStatusBarIcons() { updateIconsForLayout(entry -> entry.icon, mNotificationIcons, - false /* showAmbient */, true /* hideDismissed */, true /* hideRepliedMessages */); + false /* showAmbient */, false /* showLowPriority */, true /* hideDismissed */, + true /* hideRepliedMessages */); } /** @@ -200,8 +248,8 @@ public class NotificationIconAreaController implements DarkReceiver { * @param hideRepliedMessages should messages that have been replied to be hidden */ private void updateIconsForLayout(Function<NotificationData.Entry, StatusBarIconView> function, - NotificationIconContainer hostLayout, boolean showAmbient, boolean hideDismissed, - boolean hideRepliedMessages) { + NotificationIconContainer hostLayout, boolean showAmbient, boolean showLowPriority, + boolean hideDismissed, boolean hideRepliedMessages) { ArrayList<StatusBarIconView> toShow = new ArrayList<>( mNotificationScrollLayout.getChildCount()); @@ -210,7 +258,7 @@ public class NotificationIconAreaController implements DarkReceiver { View view = mNotificationScrollLayout.getChildAt(i); if (view instanceof ExpandableNotificationRow) { NotificationData.Entry ent = ((ExpandableNotificationRow) view).getEntry(); - if (shouldShowNotificationIcon(ent, showAmbient, hideDismissed, + if (shouldShowNotificationIcon(ent, showAmbient, showLowPriority, hideDismissed, hideRepliedMessages)) { toShow.add(function.apply(ent)); } @@ -325,9 +373,22 @@ public class NotificationIconAreaController implements DarkReceiver { v.setDecorColor(mIconTint); } - public void setFullyDark(boolean fullyDark) { - mFullyDark = fullyDark; - updateShelfIcons(); + /** + * Dark amount, from 0 to 1, representing being awake or in AOD. + */ + public void setDarkAmount(float darkAmount) { + mDarkAmount = darkAmount; + if (darkAmount == 0 || darkAmount == 1) { + ViewClippingUtil.setClippingDeactivated(mNotificationIcons, darkAmount != 0, + mClippingParameters); + } + dozeTimeTick(); + + boolean fullyDark = darkAmount == 1f; + if (mFullyDark != fullyDark) { + mFullyDark = fullyDark; + updateShelfIcons(); + } } public void setDark(boolean dark) { @@ -342,4 +403,15 @@ public class NotificationIconAreaController implements DarkReceiver { public void setIsolatedIconLocation(Rect iconDrawingRect, boolean requireStateUpdate) { mNotificationIcons.setIsolatedIconLocation(iconDrawingRect, requireStateUpdate); } + + /** + * Moves icons whenever the device wakes up in AOD, to avoid burn in. + */ + public void dozeTimeTick() { + int yOffset = (mKeyguardStatusBarHeight - getHeight()) / 2; + int translationX = getBurnInOffset(mBurnInOffset, true /* xAxis */); + int translationY = getBurnInOffset(mBurnInOffset, false /* xAxis */) + yOffset; + mNotificationIcons.setTranslationX(translationX * mDarkAmount); + mNotificationIcons.setTranslationY(translationY * mDarkAmount); + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java index 0a724bd50049..964b2210dd18 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java @@ -16,8 +16,8 @@ package com.android.systemui.statusbar.phone; -import static com.android.systemui.statusbar.phone.HeadsUpAppearanceController.CONTENT_FADE_DURATION; import static com.android.systemui.statusbar.phone.HeadsUpAppearanceController.CONTENT_FADE_DELAY; +import static com.android.systemui.statusbar.phone.HeadsUpAppearanceController.CONTENT_FADE_DURATION; import android.content.Context; import android.content.res.Configuration; @@ -26,10 +26,11 @@ import android.graphics.Color; import android.graphics.Paint; import android.graphics.Rect; import android.graphics.drawable.Icon; -import androidx.collection.ArrayMap; import android.util.AttributeSet; import android.view.View; +import androidx.collection.ArrayMap; + import com.android.internal.statusbar.StatusBarIcon; import com.android.systemui.Interpolators; import com.android.systemui.R; @@ -127,7 +128,6 @@ public class NotificationIconContainer extends AlphaOptimizedFrameLayout { } }.setDuration(CONTENT_FADE_DURATION); - public static final int MAX_VISIBLE_ICONS_WHEN_DARK = 5; public static final int MAX_STATIC_ICONS = 4; private static final int MAX_DOTS = 1; @@ -371,8 +371,7 @@ public class NotificationIconContainer extends AlphaOptimizedFrameLayout { float translationX = getActualPaddingStart(); int firstOverflowIndex = -1; int childCount = getChildCount(); - int maxVisibleIcons = mDark ? MAX_VISIBLE_ICONS_WHEN_DARK : - mIsStaticLayout ? MAX_STATIC_ICONS : childCount; + int maxVisibleIcons = mIsStaticLayout ? MAX_STATIC_ICONS : childCount; float layoutEnd = getLayoutEnd(); float overflowStart = getMaxOverflowStart(); mVisualOverflowStart = 0; @@ -388,9 +387,6 @@ public class NotificationIconContainer extends AlphaOptimizedFrameLayout { boolean forceOverflow = mSpeedBumpIndex != -1 && i >= mSpeedBumpIndex && iconState.iconAppearAmount > 0.0f || i >= maxVisibleIcons; boolean noOverflowAfter = i == childCount - 1; - float drawingScale = mDark && view instanceof StatusBarIconView - ? ((StatusBarIconView) view).getIconScaleFullyDark() - : 1f; if (mOpenedAmount != 0.0f) { noOverflowAfter = noOverflowAfter && !hasAmbient && !forceOverflow; } @@ -406,7 +402,7 @@ public class NotificationIconContainer extends AlphaOptimizedFrameLayout { mVisualOverflowStart = Math.min(translationX, mVisualOverflowStart); } } - translationX += iconState.iconAppearAmount * view.getWidth() * drawingScale; + translationX += iconState.iconAppearAmount * view.getWidth(); } mNumDots = 0; if (firstOverflowIndex != -1) { @@ -435,26 +431,6 @@ public class NotificationIconContainer extends AlphaOptimizedFrameLayout { mLastVisibleIconState = mIconStates.get(lastChild); mFirstVisibleIconState = mIconStates.get(getChildAt(0)); } - boolean center = mDark; - if (center && translationX < getLayoutEnd()) { - float initialTranslation = - mFirstVisibleIconState == null ? 0 : mFirstVisibleIconState.xTranslation; - float contentWidth = getFinalTranslationX() - initialTranslation; - float availableSpace = getLayoutEnd() - getActualPaddingStart(); - float delta = (availableSpace - contentWidth) / 2; - - if (firstOverflowIndex != -1) { - // If we have an overflow, only count those half for centering because the dots - // don't have a lot of visual weight. - float deltaIgnoringOverflow = (getLayoutEnd() - mVisualOverflowStart) / 2; - delta = (deltaIgnoringOverflow + delta) / 2; - } - for (int i = 0; i < childCount; i++) { - View view = getChildAt(i); - IconState iconState = mIconStates.get(view); - iconState.xTranslation += delta; - } - } if (isLayoutRtl()) { for (int i = 0; i < childCount; i++) { 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 b2a04add6a48..2337857eda1e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java @@ -882,8 +882,7 @@ public class StatusBar extends SystemUI implements DemoMode, mContext.getSystemService(AlarmManager.class)); mNotificationPanel.initDependencies(this, mGroupManager, mNotificationShelf, mHeadsUpManager, mNotificationIconAreaController, mScrimController); - mDozeScrimController = new DozeScrimController(mScrimController, context, - DozeParameters.getInstance(context)); + mDozeScrimController = new DozeScrimController(DozeParameters.getInstance(context)); mBackdrop = mStatusBarWindow.findViewById(R.id.backdrop); mBackdropFront = mBackdrop.findViewById(R.id.backdrop_front); @@ -1519,7 +1518,7 @@ public class StatusBar extends SystemUI implements DemoMode, } public boolean isPulsing() { - return mDozeScrimController != null && mDozeScrimController.isPulsing(); + return mAmbientPulseManager.hasNotifications(); } public boolean isLaunchTransitionFadingAway() { @@ -3648,7 +3647,6 @@ public class StatusBar extends SystemUI implements DemoMode, mNotificationPanel.setTouchAndAnimationDisabled(false); updateVisibleToUser(); updateIsKeyguard(); - updateScrimController(); } }; @@ -3834,8 +3832,9 @@ public class StatusBar extends SystemUI implements DemoMode, } else if (mBrightnessMirrorVisible) { mScrimController.transitionTo(ScrimState.BRIGHTNESS_MIRROR); } else if (isPulsing()) { - // Handled in DozeScrimController#setPulsing - } else if (mDozing) { + mScrimController.transitionTo(ScrimState.PULSING, + mDozeScrimController.getScrimCallback()); + } else if (mDozing && !wakeAndUnlocking) { mScrimController.transitionTo(ScrimState.AOD); } else if (mIsKeyguard && !wakeAndUnlocking) { mScrimController.transitionTo(mNotificationPanel.isSemiAwake() @@ -3928,8 +3927,12 @@ public class StatusBar extends SystemUI implements DemoMode, mNotificationPanel.setPulsing(pulsing); mVisualStabilityManager.setPulsing(pulsing); mIgnoreTouchWhilePulsing = false; + updateScrimController(); } }, reason); + // DozeScrimController is in pulse state, now let's ask ScrimController to start + // pulsing and draw the black frame, if necessary. + updateScrimController(); } @Override @@ -3961,6 +3964,10 @@ public class StatusBar extends SystemUI implements DemoMode, @Override public void dozeTimeTick() { mNotificationPanel.dozeTimeTick(); + mNotificationIconAreaController.dozeTimeTick(); + if (mAmbientIndicationContainer instanceof DozeReceiver) { + ((DozeReceiver) mAmbientIndicationContainer).dozeTimeTick(); + } } @Override @@ -4027,7 +4034,8 @@ public class StatusBar extends SystemUI implements DemoMode, float viewY = screenY - mTmpInt2[1]; if (0 <= viewX && viewX <= mAmbientIndicationContainer.getWidth() && 0 <= viewY && viewY <= mAmbientIndicationContainer.getHeight()) { - dispatchDoubleTap(viewX, viewY); + if (mAmbientIndicationContainer instanceof DozeReceiver) + ((DozeReceiver) mAmbientIndicationContainer).onDozeDoubleTap(); } } } @@ -4042,17 +4050,6 @@ public class StatusBar extends SystemUI implements DemoMode, mScrimController.setAodFrontScrimAlpha(scrimOpacity); } - public void dispatchDoubleTap(float viewX, float viewY) { - dispatchTap(mAmbientIndicationContainer, viewX, viewY); - dispatchTap(mAmbientIndicationContainer, viewX, viewY); - } - - private void dispatchTap(View view, float x, float y) { - long now = SystemClock.elapsedRealtime(); - dispatchTouchEvent(view, x, y, now, MotionEvent.ACTION_DOWN); - dispatchTouchEvent(view, x, y, now, MotionEvent.ACTION_UP); - } - private void dispatchTouchEvent(View view, float x, float y, long now, int action) { MotionEvent ev = MotionEvent.obtain(now, now, action, x, y, 0 /* meta */); view.dispatchTouchEvent(ev); @@ -4278,12 +4275,9 @@ public class StatusBar extends SystemUI implements DemoMode, } } - public void startPendingIntentDismissingKeyguard(final PendingIntent intent) { + public void executeActionDismissingKeyguard(Runnable action, boolean afterKeyguardGone) { if (!mDeviceProvisionedController.isDeviceProvisioned()) return; - final boolean afterKeyguardGone = intent.isActivity() - && PreviewInflater.wouldLaunchResolverActivity(mContext, intent.getIntent(), - mLockscreenUserManager.getCurrentUserId()); dismissKeyguardThenExecute(() -> { new Thread(() -> { try { @@ -4294,25 +4288,35 @@ public class StatusBar extends SystemUI implements DemoMode, ActivityManager.getService().resumeAppSwitches(); } catch (RemoteException e) { } - try { - intent.send(null, 0, null, null, null, null, getActivityOptions( - null /* animationAdapter */)); - } catch (PendingIntent.CanceledException e) { - // the stack trace isn't very helpful here. - // Just log the exception message. - Log.w(TAG, "Sending intent failed: " + e); - - // TODO: Dismiss Keyguard. - } - if (intent.isActivity()) { - mAssistManager.hideAssist(); - } + action.run(); }).start(); return collapsePanel(); }, afterKeyguardGone); } + public void startPendingIntentDismissingKeyguard(final PendingIntent intent) { + final boolean afterKeyguardGone = intent.isActivity() + && PreviewInflater.wouldLaunchResolverActivity(mContext, intent.getIntent(), + mLockscreenUserManager.getCurrentUserId()); + + executeActionDismissingKeyguard(() -> { + try { + intent.send(null, 0, null, null, null, null, getActivityOptions( + null /* animationAdapter */)); + } catch (PendingIntent.CanceledException e) { + // the stack trace isn't very helpful here. + // Just log the exception message. + Log.w(TAG, "Sending intent failed: " + e); + + // TODO: Dismiss Keyguard. + } + if (intent.isActivity()) { + mAssistManager.hideAssist(); + } + }, afterKeyguardGone); + } + public static Bundle getActivityOptions(@Nullable RemoteAnimationAdapter animationAdapter) { ActivityOptions options; if (animationAdapter != null) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java index c5603016fc56..df99a9c13855 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java @@ -48,6 +48,7 @@ import com.android.systemui.statusbar.RemoteInputController; import com.android.systemui.statusbar.StatusBarStateController; import com.android.systemui.statusbar.phone.KeyguardBouncer.BouncerExpansionCallback; import com.android.systemui.statusbar.policy.KeyguardMonitor; +import com.android.systemui.statusbar.policy.KeyguardMonitorImpl; import java.io.PrintWriter; import java.util.ArrayList; @@ -124,7 +125,8 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb // Dismiss action to be launched when we stop dozing or the keyguard is gone. private DismissWithActionRequest mPendingWakeupAction; - private final KeyguardMonitor mKeyguardMonitor = Dependency.get(KeyguardMonitor.class); + private final KeyguardMonitorImpl mKeyguardMonitor = + (KeyguardMonitorImpl) Dependency.get(KeyguardMonitor.class); private final NotificationMediaManager mMediaManager = Dependency.get(NotificationMediaManager.class); @@ -202,6 +204,8 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb public void show(Bundle options) { mShowing = true; mStatusBarWindowController.setKeyguardShowing(true); + mKeyguardMonitor.notifyKeyguardState( + mShowing, mKeyguardMonitor.isSecure(), mKeyguardMonitor.isOccluded()); reset(true /* hideBouncerWhenShowing */); StatsLog.write(StatsLog.KEYGUARD_STATE_CHANGED, StatsLog.KEYGUARD_STATE_CHANGED__STATE__SHOWN); @@ -424,6 +428,8 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb */ public void hide(long startTime, long fadeoutDuration) { mShowing = false; + mKeyguardMonitor.notifyKeyguardState( + mShowing, mKeyguardMonitor.isSecure(), mKeyguardMonitor.isOccluded()); launchPendingWakeupAction(); if (KeyguardUpdateMonitor.getInstance(mContext).needsSlowUnlockTransition()) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarSignalPolicy.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarSignalPolicy.java index 384a6e7068f4..4c24a21fc4b0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarSignalPolicy.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarSignalPolicy.java @@ -101,7 +101,8 @@ public class StatusBarSignalPolicy implements NetworkControllerImpl.SignalCallba boolean vpnVisible = mSecurityController.isVpnEnabled(); int vpnIconId = currentVpnIconId(mSecurityController.isVpnBranded()); - mIconController.setIcon(mSlotVpn, vpnIconId, null); + mIconController.setIcon(mSlotVpn, vpnIconId, + mContext.getResources().getString(R.string.accessibility_vpn_on)); mIconController.setIconVisibility(mSlotVpn, vpnVisible); } diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSliceViewTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSliceViewTest.java index 4ec30fde40e4..b98ce39f5ed3 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSliceViewTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSliceViewTest.java @@ -73,17 +73,6 @@ public class KeyguardSliceViewTest extends SysuiTestCase { } @Test - public void hasHeader_readsSliceData() { - ListBuilder builder = new ListBuilder(getContext(), mSliceUri, ListBuilder.INFINITY); - mKeyguardSliceView.onChanged(builder.build()); - Assert.assertFalse("View should not have a header", mKeyguardSliceView.hasHeader()); - - builder.setHeader(new ListBuilder.HeaderBuilder().setTitle("header title!")); - mKeyguardSliceView.onChanged(builder.build()); - Assert.assertTrue("View should have a header", mKeyguardSliceView.hasHeader()); - } - - @Test public void refresh_replacesSliceContentAndNotifiesListener() { AtomicBoolean notified = new AtomicBoolean(); mKeyguardSliceView.setContentChangeListener(()-> notified.set(true)); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java index d2fe82fd93c4..fdd89def6641 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java @@ -147,7 +147,6 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase { doNothing().when(mGroupManager).collapseAllGroups(); doNothing().when(mExpandHelper).cancelImmediately(); doNothing().when(notificationShelf).setAnimationsEnabled(anyBoolean()); - doNothing().when(notificationShelf).fadeInTranslating(); } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeScrimControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeScrimControllerTest.java index 203ebe6d5132..fe36b6d14748 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeScrimControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeScrimControllerTest.java @@ -16,14 +16,10 @@ package com.android.systemui.statusbar.phone; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; -import android.os.Debug; import android.support.test.filters.SmallTest; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; @@ -43,41 +39,26 @@ import org.mockito.MockitoAnnotations; public class DozeScrimControllerTest extends SysuiTestCase { @Mock - private ScrimController mScrimController; - @Mock private DozeParameters mDozeParameters; private DozeScrimController mDozeScrimController; @Before public void setup() { MockitoAnnotations.initMocks(this); - // Make sure callbacks will be invoked to complete the lifecycle. - doAnswer(invocationOnMock -> { - ScrimController.Callback callback = invocationOnMock.getArgument(1); - callback.onStart(); - callback.onDisplayBlanked(); - callback.onFinished(); - return null; - }).when(mScrimController).transitionTo(any(ScrimState.class), - any(ScrimController.Callback.class)); - - mDozeScrimController = new DozeScrimController(mScrimController, getContext(), - mDozeParameters); + mDozeScrimController = new DozeScrimController(mDozeParameters); mDozeScrimController.setDozing(true); } @Test - public void changesScrimControllerState() { - mDozeScrimController.pulse(mock(DozeHost.PulseCallback.class), 0); - verify(mScrimController).transitionTo(eq(ScrimState.PULSING), - any(ScrimController.Callback.class)); - } - - @Test public void callsPulseCallback() { DozeHost.PulseCallback callback = mock(DozeHost.PulseCallback.class); mDozeScrimController.pulse(callback, 0); + // Manually simulate a scrim lifecycle + mDozeScrimController.getScrimCallback().onStart(); + mDozeScrimController.getScrimCallback().onDisplayBlanked(); + mDozeScrimController.getScrimCallback().onFinished(); + verify(callback).onPulseStarted(); mDozeScrimController.pulseOutNow(); verify(callback).onPulseFinished(); diff --git a/packages/VpnDialogs/res/values-de/strings.xml b/packages/VpnDialogs/res/values-de/strings.xml index d901104759c4..0f1e00980439 100644 --- a/packages/VpnDialogs/res/values-de/strings.xml +++ b/packages/VpnDialogs/res/values-de/strings.xml @@ -17,7 +17,7 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="prompt" msgid="3183836924226407828">"Verbindungsanfrage"</string> - <string name="warning" msgid="809658604548412033">"<xliff:g id="APP">%s</xliff:g> möchte eine VPN-Verbindung herstellen, über die der Netzwerkverkehr überwacht werden kann. Lass die Verbindung nur zu, wenn die App vertrauenswürdig ist. <br /> <br /> <img src=vpn_icon /> wird oben am Display angezeigt, wenn VPN aktiv ist."</string> + <string name="warning" msgid="809658604548412033">"<xliff:g id="APP">%s</xliff:g> möchte eine VPN-Verbindung herstellen, über die der Netzwerkverkehr überwacht werden kann. Lass die Verbindung nur zu, wenn die App vertrauenswürdig ist. Wenn VPN aktiv ist, wird oben im Display <br /> <br /> <img src=vpn_icon /> angezeigt."</string> <string name="legacy_title" msgid="192936250066580964">"VPN ist verbunden"</string> <string name="session" msgid="6470628549473641030">"Sitzung:"</string> <string name="duration" msgid="3584782459928719435">"Dauer:"</string> diff --git a/packages/VpnDialogs/res/values-hi/strings.xml b/packages/VpnDialogs/res/values-hi/strings.xml index b866e5cdba5c..5560a855627f 100644 --- a/packages/VpnDialogs/res/values-hi/strings.xml +++ b/packages/VpnDialogs/res/values-hi/strings.xml @@ -17,7 +17,7 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="prompt" msgid="3183836924226407828">"कनेक्शन अनुरोध"</string> - <string name="warning" msgid="809658604548412033">"<xliff:g id="APP">%s</xliff:g> वीपीएन कनेक्शन सेट अप करना चाहता है, जिससे वह नेटवर्क ट्रैफ़िक पर नज़र रख पाएगा. इसकी मंज़ूरी तभी दें जब आपको इस पर भरोसा हो. वीपीएन चालू होने पर आपकी स्क्रीन के सबसे ऊपर <br /> <br /> <img src=vpn_icon /> दिखाई देता है."</string> + <string name="warning" msgid="809658604548412033">"<xliff:g id="APP">%s</xliff:g> वीपीएन कनेक्शन सेट अप करना चाहता है, जिससे वह नेटवर्क ट्रैफ़िक पर नज़र रख पाएगा. इसकी मंज़ूरी तभी दें जब आपको इस पर भरोसा हो. वीपीएन चालू होने पर <br /> <br /> <img src=vpn_icon /> आपकी स्क्रीन के सबसे ऊपर दिखाई देता है."</string> <string name="legacy_title" msgid="192936250066580964">"VPN कनेक्ट है"</string> <string name="session" msgid="6470628549473641030">"सत्र:"</string> <string name="duration" msgid="3584782459928719435">"अवधि:"</string> diff --git a/packages/VpnDialogs/res/values-mr/strings.xml b/packages/VpnDialogs/res/values-mr/strings.xml index 129b7b15c2c8..318f854340e2 100644 --- a/packages/VpnDialogs/res/values-mr/strings.xml +++ b/packages/VpnDialogs/res/values-mr/strings.xml @@ -17,7 +17,7 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="prompt" msgid="3183836924226407828">"कनेक्शन विनंती"</string> - <string name="warning" msgid="809658604548412033">"<xliff:g id="APP">%s</xliff:g> नेटवर्क रहदारीचे परीक्षण करण्यासाठी त्यास अनुमती देणारे VPN कनेक्शन सेट करू इच्छितो. आपल्याला स्त्रोत विश्वसनीय वाटत असेल तरच स्वीकार करा. <br /> <br /> <img src=vpn_icon /> VPN सक्रिय असताना आपल्या स्क्रीनच्या शीर्षावर दिसते."</string> + <string name="warning" msgid="809658604548412033">"<xliff:g id="APP">%s</xliff:g> नेटवर्क रहदारीचे परीक्षण करण्यासाठी त्यास अनुमती देणारे VPN कनेक्शन सेट करू इच्छितो. तुम्हाला स्त्रोत विश्वसनीय वाटत असेल तरच स्वीकार करा. <br /> <br /> <img src=vpn_icon /> VPN सक्रिय असताना आपल्या स्क्रीनच्या शीर्षावर दिसते."</string> <string name="legacy_title" msgid="192936250066580964">"VPN कनेक्ट केले"</string> <string name="session" msgid="6470628549473641030">"सत्र:"</string> <string name="duration" msgid="3584782459928719435">"कालावधी:"</string> diff --git a/packages/VpnDialogs/res/values-vi/strings.xml b/packages/VpnDialogs/res/values-vi/strings.xml index fa5e11478493..097c9aeee013 100644 --- a/packages/VpnDialogs/res/values-vi/strings.xml +++ b/packages/VpnDialogs/res/values-vi/strings.xml @@ -17,7 +17,7 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="prompt" msgid="3183836924226407828">"Yêu cầu kết nối"</string> - <string name="warning" msgid="809658604548412033">"<xliff:g id="APP">%s</xliff:g> muốn thiết lập kết nối VPN cho phép ứng dụng giám sát lưu lượng truy cập mạng. Chỉ chấp nhận nếu bạn tin tưởng nguồn. Biểu tượng <br /> <br /> <img src=vpn_icon /> xuất hiện ở đầu màn hình của bạn khi VPN đang hoạt động."</string> + <string name="warning" msgid="809658604548412033">"<xliff:g id="APP">%s</xliff:g> muốn thiết lập kết nối VPN cho phép ứng dụng giám sát lưu lượng truy cập mạng. Chỉ chấp nhận nếu bạn tin tưởng nguồn. <br /> <br /> Biểu tượng <img src=vpn_icon /> xuất hiện ở đầu màn hình của bạn khi VPN đang hoạt động."</string> <string name="legacy_title" msgid="192936250066580964">"VPN được kết nối"</string> <string name="session" msgid="6470628549473641030">"Phiên"</string> <string name="duration" msgid="3584782459928719435">"Thời lượng:"</string> diff --git a/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-af/strings.xml b/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-af/strings.xml new file mode 100644 index 000000000000..09a3ca8fde8b --- /dev/null +++ b/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-af/strings.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (c) 2018, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="experiment_navigationbar_overlay" msgid="7290469683147348228">"Eksperiment met swewende navigasiebalk"</string> +</resources> diff --git a/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-am/strings.xml b/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-am/strings.xml new file mode 100644 index 000000000000..10a5d9db1012 --- /dev/null +++ b/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-am/strings.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (c) 2018, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="experiment_navigationbar_overlay" msgid="7290469683147348228">"ተንሳፋፊ የአሰሳ አሞሌ ሙከራ"</string> +</resources> diff --git a/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-ar/strings.xml b/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-ar/strings.xml new file mode 100644 index 000000000000..123e1bef6e70 --- /dev/null +++ b/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-ar/strings.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (c) 2018, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="experiment_navigationbar_overlay" msgid="7290469683147348228">"تجربة شريط التنقُّل العائم"</string> +</resources> diff --git a/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-az/strings.xml b/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-az/strings.xml new file mode 100644 index 000000000000..bc48759b6bcf --- /dev/null +++ b/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-az/strings.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (c) 2018, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="experiment_navigationbar_overlay" msgid="7290469683147348228">"Dəyişkən Naviqasiya Paneli Təcrübəsi"</string> +</resources> diff --git a/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-b+sr+Latn/strings.xml b/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-b+sr+Latn/strings.xml new file mode 100644 index 000000000000..f83754c91e2a --- /dev/null +++ b/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-b+sr+Latn/strings.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (c) 2018, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="experiment_navigationbar_overlay" msgid="7290469683147348228">"Eksperiment sa plutajućom trakom za navigaciju"</string> +</resources> diff --git a/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-be/strings.xml b/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-be/strings.xml new file mode 100644 index 000000000000..957be5382d49 --- /dev/null +++ b/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-be/strings.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (c) 2018, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="experiment_navigationbar_overlay" msgid="7290469683147348228">"Эксперымент з плаваючай панэллю навігацыі"</string> +</resources> diff --git a/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-bg/strings.xml b/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-bg/strings.xml new file mode 100644 index 000000000000..1fe6d361cee8 --- /dev/null +++ b/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-bg/strings.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (c) 2018, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="experiment_navigationbar_overlay" msgid="7290469683147348228">"Експеримент с плаваща лента за навигация"</string> +</resources> diff --git a/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-bs/strings.xml b/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-bs/strings.xml new file mode 100644 index 000000000000..621cf596781a --- /dev/null +++ b/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-bs/strings.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (c) 2018, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="experiment_navigationbar_overlay" msgid="7290469683147348228">"Eksperiment s plutajućom trakom za navigaciju"</string> +</resources> diff --git a/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-ca/strings.xml b/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-ca/strings.xml new file mode 100644 index 000000000000..e747d0602f04 --- /dev/null +++ b/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-ca/strings.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (c) 2018, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="experiment_navigationbar_overlay" msgid="7290469683147348228">"Experiment amb barra de navegació flotant"</string> +</resources> diff --git a/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-cs/strings.xml b/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-cs/strings.xml new file mode 100644 index 000000000000..3b5a4d8388da --- /dev/null +++ b/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-cs/strings.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (c) 2018, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="experiment_navigationbar_overlay" msgid="7290469683147348228">"Plovoucí navigační panel (experiment)"</string> +</resources> diff --git a/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-da/strings.xml b/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-da/strings.xml new file mode 100644 index 000000000000..0e114df55c58 --- /dev/null +++ b/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-da/strings.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (c) 2018, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="experiment_navigationbar_overlay" msgid="7290469683147348228">"Test med svævende navigationslinje"</string> +</resources> diff --git a/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-de/strings.xml b/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-de/strings.xml new file mode 100644 index 000000000000..320e27528c9d --- /dev/null +++ b/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-de/strings.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (c) 2018, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="experiment_navigationbar_overlay" msgid="7290469683147348228">"Experiment mit unverankerter Navigationsleiste"</string> +</resources> diff --git a/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-el/strings.xml b/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-el/strings.xml new file mode 100644 index 000000000000..2d464031c32f --- /dev/null +++ b/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-el/strings.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (c) 2018, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="experiment_navigationbar_overlay" msgid="7290469683147348228">"Πείραμα κινούμενης γραμμής πλοήγησης"</string> +</resources> diff --git a/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-en-rAU/strings.xml b/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-en-rAU/strings.xml new file mode 100644 index 000000000000..17227fc0721d --- /dev/null +++ b/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-en-rAU/strings.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (c) 2018, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="experiment_navigationbar_overlay" msgid="7290469683147348228">"Floating Navigation Bar Experiment"</string> +</resources> diff --git a/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-en-rCA/strings.xml b/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-en-rCA/strings.xml new file mode 100644 index 000000000000..17227fc0721d --- /dev/null +++ b/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-en-rCA/strings.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (c) 2018, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="experiment_navigationbar_overlay" msgid="7290469683147348228">"Floating Navigation Bar Experiment"</string> +</resources> diff --git a/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-en-rGB/strings.xml b/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-en-rGB/strings.xml new file mode 100644 index 000000000000..17227fc0721d --- /dev/null +++ b/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-en-rGB/strings.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (c) 2018, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="experiment_navigationbar_overlay" msgid="7290469683147348228">"Floating Navigation Bar Experiment"</string> +</resources> diff --git a/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-en-rIN/strings.xml b/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-en-rIN/strings.xml new file mode 100644 index 000000000000..17227fc0721d --- /dev/null +++ b/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-en-rIN/strings.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (c) 2018, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="experiment_navigationbar_overlay" msgid="7290469683147348228">"Floating Navigation Bar Experiment"</string> +</resources> diff --git a/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-en-rXC/strings.xml b/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-en-rXC/strings.xml new file mode 100644 index 000000000000..d379ec1a905b --- /dev/null +++ b/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-en-rXC/strings.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (c) 2018, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="experiment_navigationbar_overlay" msgid="7290469683147348228">"Floating Navigation Bar Experiment"</string> +</resources> diff --git a/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-es-rUS/strings.xml b/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-es-rUS/strings.xml new file mode 100644 index 000000000000..34e70dcfdbb5 --- /dev/null +++ b/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-es-rUS/strings.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (c) 2018, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="experiment_navigationbar_overlay" msgid="7290469683147348228">"Experimento de barra de navegación flotante"</string> +</resources> diff --git a/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-es/strings.xml b/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-es/strings.xml new file mode 100644 index 000000000000..34e70dcfdbb5 --- /dev/null +++ b/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-es/strings.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (c) 2018, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="experiment_navigationbar_overlay" msgid="7290469683147348228">"Experimento de barra de navegación flotante"</string> +</resources> diff --git a/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-et/strings.xml b/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-et/strings.xml new file mode 100644 index 000000000000..c20f38e67834 --- /dev/null +++ b/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-et/strings.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (c) 2018, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="experiment_navigationbar_overlay" msgid="7290469683147348228">"Hõljuva navigeerimisriba katse"</string> +</resources> diff --git a/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-eu/strings.xml b/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-eu/strings.xml new file mode 100644 index 000000000000..4623a88ce32c --- /dev/null +++ b/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-eu/strings.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (c) 2018, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="experiment_navigationbar_overlay" msgid="7290469683147348228">"Nabigazio-barra gainerakorraren esperimentua"</string> +</resources> diff --git a/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-fa/strings.xml b/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-fa/strings.xml new file mode 100644 index 000000000000..f7dbb534b2a0 --- /dev/null +++ b/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-fa/strings.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (c) 2018, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="experiment_navigationbar_overlay" msgid="7290469683147348228">"آزمایش نوار پیمایش شناور"</string> +</resources> diff --git a/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-fi/strings.xml b/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-fi/strings.xml new file mode 100644 index 000000000000..397052d7f204 --- /dev/null +++ b/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-fi/strings.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (c) 2018, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="experiment_navigationbar_overlay" msgid="7290469683147348228">"Kelluvan navigointipalkin kokeilu"</string> +</resources> diff --git a/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-fr-rCA/strings.xml b/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-fr-rCA/strings.xml new file mode 100644 index 000000000000..a831068c0c7f --- /dev/null +++ b/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-fr-rCA/strings.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (c) 2018, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="experiment_navigationbar_overlay" msgid="7290469683147348228">"Expérience de barre de navigation flottante"</string> +</resources> diff --git a/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-fr/strings.xml b/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-fr/strings.xml new file mode 100644 index 000000000000..032ca9de0735 --- /dev/null +++ b/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-fr/strings.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (c) 2018, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="experiment_navigationbar_overlay" msgid="7290469683147348228">"Test relatif à la barre de navigation flottante"</string> +</resources> diff --git a/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-gl/strings.xml b/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-gl/strings.xml new file mode 100644 index 000000000000..34e70dcfdbb5 --- /dev/null +++ b/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-gl/strings.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (c) 2018, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="experiment_navigationbar_overlay" msgid="7290469683147348228">"Experimento de barra de navegación flotante"</string> +</resources> diff --git a/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-hi/strings.xml b/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-hi/strings.xml new file mode 100644 index 000000000000..5f8b1bc4a982 --- /dev/null +++ b/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-hi/strings.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (c) 2018, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="experiment_navigationbar_overlay" msgid="7290469683147348228">"फ़्लोट करता हुआ नेविगेशन बार प्रयोग"</string> +</resources> diff --git a/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-hr/strings.xml b/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-hr/strings.xml new file mode 100644 index 000000000000..8570caa95b18 --- /dev/null +++ b/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-hr/strings.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (c) 2018, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="experiment_navigationbar_overlay" msgid="7290469683147348228">"Eksperiment s plutajućom navigacijskom trakom"</string> +</resources> diff --git a/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-hu/strings.xml b/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-hu/strings.xml new file mode 100644 index 000000000000..56e02a8f8afd --- /dev/null +++ b/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-hu/strings.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (c) 2018, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="experiment_navigationbar_overlay" msgid="7290469683147348228">"Kísérleti lebegő navigációs sáv"</string> +</resources> diff --git a/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-hy/strings.xml b/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-hy/strings.xml new file mode 100644 index 000000000000..6623812c1833 --- /dev/null +++ b/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-hy/strings.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (c) 2018, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="experiment_navigationbar_overlay" msgid="7290469683147348228">"Նավարկման լողացող գոտու փորձարկում"</string> +</resources> diff --git a/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-in/strings.xml b/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-in/strings.xml new file mode 100644 index 000000000000..457db1c30b43 --- /dev/null +++ b/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-in/strings.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (c) 2018, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="experiment_navigationbar_overlay" msgid="7290469683147348228">"Eksperimen Menu Navigasi Mengambang"</string> +</resources> diff --git a/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-is/strings.xml b/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-is/strings.xml new file mode 100644 index 000000000000..c5b2c23c2d3a --- /dev/null +++ b/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-is/strings.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (c) 2018, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="experiment_navigationbar_overlay" msgid="7290469683147348228">"Tilraun með fljótandi yfirlitsstiku"</string> +</resources> diff --git a/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-it/strings.xml b/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-it/strings.xml new file mode 100644 index 000000000000..ef858fbedad0 --- /dev/null +++ b/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-it/strings.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (c) 2018, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="experiment_navigationbar_overlay" msgid="7290469683147348228">"Esperimento Barra di navigazione floating"</string> +</resources> diff --git a/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-iw/strings.xml b/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-iw/strings.xml new file mode 100644 index 000000000000..866ab7803042 --- /dev/null +++ b/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-iw/strings.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (c) 2018, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="experiment_navigationbar_overlay" msgid="7290469683147348228">"ניסוי של סרגל ניווט צף"</string> +</resources> diff --git a/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-ja/strings.xml b/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-ja/strings.xml new file mode 100644 index 000000000000..49264ca6cc18 --- /dev/null +++ b/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-ja/strings.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (c) 2018, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="experiment_navigationbar_overlay" msgid="7290469683147348228">"フローティング ナビゲーション バー テスト"</string> +</resources> diff --git a/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-ka/strings.xml b/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-ka/strings.xml new file mode 100644 index 000000000000..440535e86d89 --- /dev/null +++ b/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-ka/strings.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (c) 2018, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="experiment_navigationbar_overlay" msgid="7290469683147348228">"ნავიგაციის მოლივლივე ზოლის ექსპერიმენტი"</string> +</resources> diff --git a/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-kk/strings.xml b/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-kk/strings.xml new file mode 100644 index 000000000000..63165555d87b --- /dev/null +++ b/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-kk/strings.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (c) 2018, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="experiment_navigationbar_overlay" msgid="7290469683147348228">"Қалқымалы навигация жолағы (эксперимент)"</string> +</resources> diff --git a/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-km/strings.xml b/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-km/strings.xml new file mode 100644 index 000000000000..4888b2b31e2d --- /dev/null +++ b/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-km/strings.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (c) 2018, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="experiment_navigationbar_overlay" msgid="7290469683147348228">"ការពិសោធនៃរបាររុករកដែលអណ្ដែត"</string> +</resources> diff --git a/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-ko/strings.xml b/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-ko/strings.xml new file mode 100644 index 000000000000..89d221c6c1c0 --- /dev/null +++ b/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-ko/strings.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (c) 2018, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="experiment_navigationbar_overlay" msgid="7290469683147348228">"플로팅 탐색 메뉴 실험"</string> +</resources> diff --git a/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-ky/strings.xml b/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-ky/strings.xml new file mode 100644 index 000000000000..bfd96da7207e --- /dev/null +++ b/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-ky/strings.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (c) 2018, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="experiment_navigationbar_overlay" msgid="7290469683147348228">"Калкыма чабыттоо тилкесин колдонуу"</string> +</resources> diff --git a/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-lo/strings.xml b/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-lo/strings.xml new file mode 100644 index 000000000000..4b14abdc510f --- /dev/null +++ b/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-lo/strings.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (c) 2018, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="experiment_navigationbar_overlay" msgid="7290469683147348228">"ການທົດລອງແຖບການນຳທາງແບບລອຍ"</string> +</resources> diff --git a/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-lt/strings.xml b/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-lt/strings.xml new file mode 100644 index 000000000000..bb45ec2fde59 --- /dev/null +++ b/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-lt/strings.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (c) 2018, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="experiment_navigationbar_overlay" msgid="7290469683147348228">"Slankiosios naršymo juostos eksperimentas"</string> +</resources> diff --git a/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-lv/strings.xml b/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-lv/strings.xml new file mode 100644 index 000000000000..e537508edd36 --- /dev/null +++ b/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-lv/strings.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (c) 2018, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="experiment_navigationbar_overlay" msgid="7290469683147348228">"Peldošas navigācijas joslas eksperiments"</string> +</resources> diff --git a/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-mk/strings.xml b/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-mk/strings.xml new file mode 100644 index 000000000000..f1a442d3e7e7 --- /dev/null +++ b/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-mk/strings.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (c) 2018, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="experiment_navigationbar_overlay" msgid="7290469683147348228">"Експеримент со лебдечка лента за навигација"</string> +</resources> diff --git a/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-mn/strings.xml b/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-mn/strings.xml new file mode 100644 index 000000000000..1fd974d0b5ff --- /dev/null +++ b/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-mn/strings.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (c) 2018, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="experiment_navigationbar_overlay" msgid="7290469683147348228">"Хөвөгч навигацийн самбарын туршилт"</string> +</resources> diff --git a/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-mr/strings.xml b/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-mr/strings.xml new file mode 100644 index 000000000000..54387fe3504a --- /dev/null +++ b/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-mr/strings.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (c) 2018, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="experiment_navigationbar_overlay" msgid="7290469683147348228">"फ्लोटिंग नेव्हिगेशन बार प्रयोग"</string> +</resources> diff --git a/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-ms/strings.xml b/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-ms/strings.xml new file mode 100644 index 000000000000..20471d3a140e --- /dev/null +++ b/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-ms/strings.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (c) 2018, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="experiment_navigationbar_overlay" msgid="7290469683147348228">"Percubaan Bar Navigasi Terapung"</string> +</resources> diff --git a/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-my/strings.xml b/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-my/strings.xml new file mode 100644 index 000000000000..7d94bdb43a0c --- /dev/null +++ b/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-my/strings.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (c) 2018, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="experiment_navigationbar_overlay" msgid="7290469683147348228">"မျောနေသော လမ်းညွှန်ဘား စမ်းသပ်မှု"</string> +</resources> diff --git a/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-nb/strings.xml b/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-nb/strings.xml new file mode 100644 index 000000000000..505e28a64596 --- /dev/null +++ b/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-nb/strings.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (c) 2018, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="experiment_navigationbar_overlay" msgid="7290469683147348228">"Eksperiment med flytende navigasjonsrad"</string> +</resources> diff --git a/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-nl/strings.xml b/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-nl/strings.xml new file mode 100644 index 000000000000..69242a7ef2e3 --- /dev/null +++ b/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-nl/strings.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (c) 2018, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="experiment_navigationbar_overlay" msgid="7290469683147348228">"Experiment voor zwevende navigatiebalk"</string> +</resources> diff --git a/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-pl/strings.xml b/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-pl/strings.xml new file mode 100644 index 000000000000..a18afc34c839 --- /dev/null +++ b/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-pl/strings.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (c) 2018, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="experiment_navigationbar_overlay" msgid="7290469683147348228">"Eksperyment z pływającym paskiem nawigacyjnym"</string> +</resources> diff --git a/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-pt-rBR/strings.xml b/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-pt-rBR/strings.xml new file mode 100644 index 000000000000..7f58e758e2f7 --- /dev/null +++ b/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-pt-rBR/strings.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (c) 2018, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="experiment_navigationbar_overlay" msgid="7290469683147348228">"Experimento de barra de navegação flutuante"</string> +</resources> diff --git a/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-pt-rPT/strings.xml b/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-pt-rPT/strings.xml new file mode 100644 index 000000000000..faee8c4438c1 --- /dev/null +++ b/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-pt-rPT/strings.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (c) 2018, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="experiment_navigationbar_overlay" msgid="7290469683147348228">"Experiência de barra de navegação flutuante"</string> +</resources> diff --git a/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-pt/strings.xml b/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-pt/strings.xml new file mode 100644 index 000000000000..7f58e758e2f7 --- /dev/null +++ b/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-pt/strings.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (c) 2018, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="experiment_navigationbar_overlay" msgid="7290469683147348228">"Experimento de barra de navegação flutuante"</string> +</resources> diff --git a/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-ro/strings.xml b/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-ro/strings.xml new file mode 100644 index 000000000000..b7debfc04433 --- /dev/null +++ b/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-ro/strings.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (c) 2018, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="experiment_navigationbar_overlay" msgid="7290469683147348228">"Experiment cu bară de navigare flotantă"</string> +</resources> diff --git a/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-ru/strings.xml b/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-ru/strings.xml new file mode 100644 index 000000000000..8314c9d664d9 --- /dev/null +++ b/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-ru/strings.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (c) 2018, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="experiment_navigationbar_overlay" msgid="7290469683147348228">"Плавающая панель навигации (эксперимент)"</string> +</resources> diff --git a/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-sk/strings.xml b/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-sk/strings.xml new file mode 100644 index 000000000000..6cf17948abc0 --- /dev/null +++ b/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-sk/strings.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (c) 2018, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="experiment_navigationbar_overlay" msgid="7290469683147348228">"Plávajúci navigačný panel (experiment)"</string> +</resources> diff --git a/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-sl/strings.xml b/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-sl/strings.xml new file mode 100644 index 000000000000..4b1417f22cf0 --- /dev/null +++ b/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-sl/strings.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (c) 2018, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="experiment_navigationbar_overlay" msgid="7290469683147348228">"Preizkus s plavajočo vrstico za krmarjenje"</string> +</resources> diff --git a/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-sq/strings.xml b/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-sq/strings.xml new file mode 100644 index 000000000000..254101f1d71b --- /dev/null +++ b/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-sq/strings.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (c) 2018, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="experiment_navigationbar_overlay" msgid="7290469683147348228">"Eksperimenti i shiritit pluskues të navigimit"</string> +</resources> diff --git a/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-sr/strings.xml b/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-sr/strings.xml new file mode 100644 index 000000000000..4ee3f2eab968 --- /dev/null +++ b/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-sr/strings.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (c) 2018, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="experiment_navigationbar_overlay" msgid="7290469683147348228">"Експеримент са плутајућом траком за навигацију"</string> +</resources> diff --git a/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-sv/strings.xml b/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-sv/strings.xml new file mode 100644 index 000000000000..0c1cbf2f2bac --- /dev/null +++ b/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-sv/strings.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (c) 2018, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="experiment_navigationbar_overlay" msgid="7290469683147348228">"Experimentellt flytande navigeringsfält"</string> +</resources> diff --git a/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-sw/strings.xml b/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-sw/strings.xml new file mode 100644 index 000000000000..5ecbf906dbfb --- /dev/null +++ b/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-sw/strings.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (c) 2018, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="experiment_navigationbar_overlay" msgid="7290469683147348228">"Jaribio la Sehemu ya Viungo Muhimu Inayoweza Kusogezwa"</string> +</resources> diff --git a/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-th/strings.xml b/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-th/strings.xml new file mode 100644 index 000000000000..763d1a4fd337 --- /dev/null +++ b/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-th/strings.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (c) 2018, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="experiment_navigationbar_overlay" msgid="7290469683147348228">"การทดสอบแถบนำทางแบบลอย"</string> +</resources> diff --git a/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-tl/strings.xml b/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-tl/strings.xml new file mode 100644 index 000000000000..f3d59812f9ea --- /dev/null +++ b/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-tl/strings.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (c) 2018, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="experiment_navigationbar_overlay" msgid="7290469683147348228">"Eksperimentong Floating na Navigation Bar"</string> +</resources> diff --git a/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-tr/strings.xml b/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-tr/strings.xml new file mode 100644 index 000000000000..08ecdee4e194 --- /dev/null +++ b/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-tr/strings.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (c) 2018, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="experiment_navigationbar_overlay" msgid="7290469683147348228">"Kayan Gezinme Çubuğu Denemesi"</string> +</resources> diff --git a/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-uk/strings.xml b/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-uk/strings.xml new file mode 100644 index 000000000000..497bfe6692a4 --- /dev/null +++ b/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-uk/strings.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (c) 2018, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="experiment_navigationbar_overlay" msgid="7290469683147348228">"Експеримент із плаваючою панеллю навігації"</string> +</resources> diff --git a/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-ur/strings.xml b/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-ur/strings.xml new file mode 100644 index 000000000000..aa7810ea61d1 --- /dev/null +++ b/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-ur/strings.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (c) 2018, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="experiment_navigationbar_overlay" msgid="7290469683147348228">"فلوٹنگ نیویگیشن بار کا تجربہ"</string> +</resources> diff --git a/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-uz/strings.xml b/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-uz/strings.xml new file mode 100644 index 000000000000..0f957166f145 --- /dev/null +++ b/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-uz/strings.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (c) 2018, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="experiment_navigationbar_overlay" msgid="7290469683147348228">"Erkin harakatlanuvchi navigatsiya paneli tajribasi"</string> +</resources> diff --git a/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-vi/strings.xml b/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-vi/strings.xml new file mode 100644 index 000000000000..b0c2b061817c --- /dev/null +++ b/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-vi/strings.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (c) 2018, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="experiment_navigationbar_overlay" msgid="7290469683147348228">"Thử nghiệm thanh điều hướng nổi"</string> +</resources> diff --git a/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-zh-rCN/strings.xml b/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-zh-rCN/strings.xml new file mode 100644 index 000000000000..3edd38286372 --- /dev/null +++ b/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-zh-rCN/strings.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (c) 2018, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="experiment_navigationbar_overlay" msgid="7290469683147348228">"浮动导航栏实验"</string> +</resources> diff --git a/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-zh-rHK/strings.xml b/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-zh-rHK/strings.xml new file mode 100644 index 000000000000..3f038eaeb05d --- /dev/null +++ b/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-zh-rHK/strings.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (c) 2018, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="experiment_navigationbar_overlay" msgid="7290469683147348228">"懸浮導覽列實驗"</string> +</resources> diff --git a/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-zh-rTW/strings.xml b/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-zh-rTW/strings.xml new file mode 100644 index 000000000000..51dc364046fe --- /dev/null +++ b/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-zh-rTW/strings.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (c) 2018, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="experiment_navigationbar_overlay" msgid="7290469683147348228">"浮動導覽列實驗"</string> +</resources> diff --git a/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-zu/strings.xml b/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-zu/strings.xml new file mode 100644 index 000000000000..ea193ea6ff0e --- /dev/null +++ b/packages/overlays/ExperimentNavigationBarFloatingOverlay/res/values-zu/strings.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (c) 2018, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="experiment_navigationbar_overlay" msgid="7290469683147348228">"Ukuhlolwa kwebha entantayo yokuzula"</string> +</resources> diff --git a/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-af/strings.xml b/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-af/strings.xml new file mode 100644 index 000000000000..21a0003bdaa7 --- /dev/null +++ b/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-af/strings.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (c) 2018, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="experiment_navigationbar_overlay" msgid="6953777362606036161">"Eksperiment met dun navigasiebalk"</string> +</resources> diff --git a/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-am/strings.xml b/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-am/strings.xml new file mode 100644 index 000000000000..6a7d644ff432 --- /dev/null +++ b/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-am/strings.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (c) 2018, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="experiment_navigationbar_overlay" msgid="6953777362606036161">"ቀጭን የአሰሳ አሞሌ ሙከራ"</string> +</resources> diff --git a/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-ar/strings.xml b/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-ar/strings.xml new file mode 100644 index 000000000000..2cebf40c0cba --- /dev/null +++ b/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-ar/strings.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (c) 2018, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="experiment_navigationbar_overlay" msgid="6953777362606036161">"تجربة شريط التنقُّل النحيف"</string> +</resources> diff --git a/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-az/strings.xml b/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-az/strings.xml new file mode 100644 index 000000000000..266c051abc7d --- /dev/null +++ b/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-az/strings.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (c) 2018, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="experiment_navigationbar_overlay" msgid="6953777362606036161">"Sabit Naviqasiya Paneli Təcrübəsi"</string> +</resources> diff --git a/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-b+sr+Latn/strings.xml b/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-b+sr+Latn/strings.xml new file mode 100644 index 000000000000..dc57a3f6a076 --- /dev/null +++ b/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-b+sr+Latn/strings.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (c) 2018, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="experiment_navigationbar_overlay" msgid="6953777362606036161">"Eksperiment sa tankom trakom za navigaciju"</string> +</resources> diff --git a/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-be/strings.xml b/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-be/strings.xml new file mode 100644 index 000000000000..8b53bcdfe9d7 --- /dev/null +++ b/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-be/strings.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (c) 2018, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="experiment_navigationbar_overlay" msgid="6953777362606036161">"Эксперымент з тонкай панэллю навігацыі"</string> +</resources> diff --git a/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-bg/strings.xml b/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-bg/strings.xml new file mode 100644 index 000000000000..4bf000e193c0 --- /dev/null +++ b/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-bg/strings.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (c) 2018, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="experiment_navigationbar_overlay" msgid="6953777362606036161">"Експеримент с тънка лента за навигация"</string> +</resources> diff --git a/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-bs/strings.xml b/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-bs/strings.xml new file mode 100644 index 000000000000..981420945aea --- /dev/null +++ b/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-bs/strings.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (c) 2018, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="experiment_navigationbar_overlay" msgid="6953777362606036161">"Eksperiment s tankom trakom za navigaciju"</string> +</resources> diff --git a/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-ca/strings.xml b/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-ca/strings.xml new file mode 100644 index 000000000000..4a9b598158e6 --- /dev/null +++ b/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-ca/strings.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (c) 2018, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="experiment_navigationbar_overlay" msgid="6953777362606036161">"Experiment amb barra de navegació fina"</string> +</resources> diff --git a/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-cs/strings.xml b/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-cs/strings.xml new file mode 100644 index 000000000000..d923a3cc4376 --- /dev/null +++ b/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-cs/strings.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (c) 2018, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="experiment_navigationbar_overlay" msgid="6953777362606036161">"Úzký navigační panel (experiment)"</string> +</resources> diff --git a/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-da/strings.xml b/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-da/strings.xml new file mode 100644 index 000000000000..12bb2f23fe1e --- /dev/null +++ b/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-da/strings.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (c) 2018, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="experiment_navigationbar_overlay" msgid="6953777362606036161">"Test med smal navigationslinje"</string> +</resources> diff --git a/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-de/strings.xml b/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-de/strings.xml new file mode 100644 index 000000000000..960a7d94e7ae --- /dev/null +++ b/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-de/strings.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (c) 2018, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="experiment_navigationbar_overlay" msgid="6953777362606036161">"Experiment mit schmaler Navigationsleiste"</string> +</resources> diff --git a/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-el/strings.xml b/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-el/strings.xml new file mode 100644 index 000000000000..119b7e611cf9 --- /dev/null +++ b/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-el/strings.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (c) 2018, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="experiment_navigationbar_overlay" msgid="6953777362606036161">"Πείραμα λεπτής γραμμής πλοήγησης"</string> +</resources> diff --git a/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-en-rAU/strings.xml b/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-en-rAU/strings.xml new file mode 100644 index 000000000000..5ebf40348199 --- /dev/null +++ b/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-en-rAU/strings.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (c) 2018, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="experiment_navigationbar_overlay" msgid="6953777362606036161">"Slim Navigation Bar Experiment"</string> +</resources> diff --git a/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-en-rCA/strings.xml b/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-en-rCA/strings.xml new file mode 100644 index 000000000000..5ebf40348199 --- /dev/null +++ b/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-en-rCA/strings.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (c) 2018, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="experiment_navigationbar_overlay" msgid="6953777362606036161">"Slim Navigation Bar Experiment"</string> +</resources> diff --git a/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-en-rGB/strings.xml b/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-en-rGB/strings.xml new file mode 100644 index 000000000000..5ebf40348199 --- /dev/null +++ b/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-en-rGB/strings.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (c) 2018, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="experiment_navigationbar_overlay" msgid="6953777362606036161">"Slim Navigation Bar Experiment"</string> +</resources> diff --git a/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-en-rIN/strings.xml b/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-en-rIN/strings.xml new file mode 100644 index 000000000000..5ebf40348199 --- /dev/null +++ b/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-en-rIN/strings.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (c) 2018, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="experiment_navigationbar_overlay" msgid="6953777362606036161">"Slim Navigation Bar Experiment"</string> +</resources> diff --git a/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-en-rXC/strings.xml b/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-en-rXC/strings.xml new file mode 100644 index 000000000000..79884ce615cf --- /dev/null +++ b/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-en-rXC/strings.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (c) 2018, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="experiment_navigationbar_overlay" msgid="6953777362606036161">"Slim Navigation Bar Experiment"</string> +</resources> diff --git a/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-es-rUS/strings.xml b/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-es-rUS/strings.xml new file mode 100644 index 000000000000..00b044459777 --- /dev/null +++ b/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-es-rUS/strings.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (c) 2018, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="experiment_navigationbar_overlay" msgid="6953777362606036161">"Experimento de barra de navegación delgada"</string> +</resources> diff --git a/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-es/strings.xml b/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-es/strings.xml new file mode 100644 index 000000000000..58bdaa26115b --- /dev/null +++ b/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-es/strings.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (c) 2018, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="experiment_navigationbar_overlay" msgid="6953777362606036161">"Experimento de barra navegación delgada"</string> +</resources> diff --git a/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-et/strings.xml b/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-et/strings.xml new file mode 100644 index 000000000000..4c5023cf1d14 --- /dev/null +++ b/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-et/strings.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (c) 2018, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="experiment_navigationbar_overlay" msgid="6953777362606036161">"Kitsa navigeerimisriba katse"</string> +</resources> diff --git a/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-eu/strings.xml b/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-eu/strings.xml new file mode 100644 index 000000000000..fc16eeb8dc3e --- /dev/null +++ b/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-eu/strings.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (c) 2018, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="experiment_navigationbar_overlay" msgid="6953777362606036161">"Nabigazio-barra finaren esperimentua"</string> +</resources> diff --git a/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-fa/strings.xml b/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-fa/strings.xml new file mode 100644 index 000000000000..e4246d79decb --- /dev/null +++ b/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-fa/strings.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (c) 2018, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="experiment_navigationbar_overlay" msgid="6953777362606036161">"آزمایش نوار پیمایش باریک"</string> +</resources> diff --git a/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-fi/strings.xml b/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-fi/strings.xml new file mode 100644 index 000000000000..9385a470368f --- /dev/null +++ b/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-fi/strings.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (c) 2018, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="experiment_navigationbar_overlay" msgid="6953777362606036161">"Ohuen navigointipalkin kokeilu"</string> +</resources> diff --git a/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-fr-rCA/strings.xml b/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-fr-rCA/strings.xml new file mode 100644 index 000000000000..d22848beda3d --- /dev/null +++ b/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-fr-rCA/strings.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (c) 2018, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="experiment_navigationbar_overlay" msgid="6953777362606036161">"Expérience de barre de navigation mince"</string> +</resources> diff --git a/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-fr/strings.xml b/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-fr/strings.xml new file mode 100644 index 000000000000..cfa1db2a06d3 --- /dev/null +++ b/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-fr/strings.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (c) 2018, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="experiment_navigationbar_overlay" msgid="6953777362606036161">"Test relatif à la barre de navigation fine"</string> +</resources> diff --git a/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-gl/strings.xml b/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-gl/strings.xml new file mode 100644 index 000000000000..d2194ce1a820 --- /dev/null +++ b/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-gl/strings.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (c) 2018, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="experiment_navigationbar_overlay" msgid="6953777362606036161">"Experimento de barra de navegación estreita"</string> +</resources> diff --git a/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-hi/strings.xml b/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-hi/strings.xml new file mode 100644 index 000000000000..d51cee34f895 --- /dev/null +++ b/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-hi/strings.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (c) 2018, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="experiment_navigationbar_overlay" msgid="6953777362606036161">"स्लिम नेविगेशन बार प्रयोग"</string> +</resources> diff --git a/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-hr/strings.xml b/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-hr/strings.xml new file mode 100644 index 000000000000..410c58ef2cbc --- /dev/null +++ b/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-hr/strings.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (c) 2018, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="experiment_navigationbar_overlay" msgid="6953777362606036161">"Eksperiment s tankom navigacijskom trakom"</string> +</resources> diff --git a/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-hu/strings.xml b/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-hu/strings.xml new file mode 100644 index 000000000000..d7eafed699ab --- /dev/null +++ b/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-hu/strings.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (c) 2018, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="experiment_navigationbar_overlay" msgid="6953777362606036161">"Kísérleti keskeny navigációs sáv"</string> +</resources> diff --git a/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-hy/strings.xml b/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-hy/strings.xml new file mode 100644 index 000000000000..b0200cab636b --- /dev/null +++ b/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-hy/strings.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (c) 2018, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="experiment_navigationbar_overlay" msgid="6953777362606036161">"Նավարկման նեղ գոտու փորձարկում"</string> +</resources> diff --git a/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-in/strings.xml b/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-in/strings.xml new file mode 100644 index 000000000000..3e0bded06e68 --- /dev/null +++ b/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-in/strings.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (c) 2018, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="experiment_navigationbar_overlay" msgid="6953777362606036161">"Eksperimen Menu Navigasi Ramping"</string> +</resources> diff --git a/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-is/strings.xml b/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-is/strings.xml new file mode 100644 index 000000000000..03ccaf3de068 --- /dev/null +++ b/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-is/strings.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (c) 2018, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="experiment_navigationbar_overlay" msgid="6953777362606036161">"Tilraun með þunna yfirlitsstiku"</string> +</resources> diff --git a/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-it/strings.xml b/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-it/strings.xml new file mode 100644 index 000000000000..f7c5d253e5ab --- /dev/null +++ b/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-it/strings.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (c) 2018, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="experiment_navigationbar_overlay" msgid="6953777362606036161">"Esperimento Barra di navigazione sottile"</string> +</resources> diff --git a/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-iw/strings.xml b/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-iw/strings.xml new file mode 100644 index 000000000000..0d0ec2c7c2a5 --- /dev/null +++ b/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-iw/strings.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (c) 2018, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="experiment_navigationbar_overlay" msgid="6953777362606036161">"ניסוי של סרגל ניווט דק"</string> +</resources> diff --git a/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-ja/strings.xml b/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-ja/strings.xml new file mode 100644 index 000000000000..a3d6874e84f6 --- /dev/null +++ b/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-ja/strings.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (c) 2018, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="experiment_navigationbar_overlay" msgid="6953777362606036161">"スリム ナビゲーション バー テスト"</string> +</resources> diff --git a/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-ka/strings.xml b/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-ka/strings.xml new file mode 100644 index 000000000000..ffddf3bee802 --- /dev/null +++ b/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-ka/strings.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (c) 2018, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="experiment_navigationbar_overlay" msgid="6953777362606036161">"ნავიგაციის მჭიდრო ზოლის ექსპერიმენტი"</string> +</resources> diff --git a/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-kk/strings.xml b/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-kk/strings.xml new file mode 100644 index 000000000000..f34ac0810127 --- /dev/null +++ b/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-kk/strings.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (c) 2018, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="experiment_navigationbar_overlay" msgid="6953777362606036161">"Жіңішке навигация жолағы (эксперимент)"</string> +</resources> diff --git a/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-km/strings.xml b/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-km/strings.xml new file mode 100644 index 000000000000..114a78270d79 --- /dev/null +++ b/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-km/strings.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (c) 2018, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="experiment_navigationbar_overlay" msgid="6953777362606036161">"ការពិសោធនៃរបាររុករកស្ដើង"</string> +</resources> diff --git a/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-ko/strings.xml b/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-ko/strings.xml new file mode 100644 index 000000000000..fca02c3e49bd --- /dev/null +++ b/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-ko/strings.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (c) 2018, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="experiment_navigationbar_overlay" msgid="6953777362606036161">"슬림한 탐색 메뉴 실험"</string> +</resources> diff --git a/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-ky/strings.xml b/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-ky/strings.xml new file mode 100644 index 000000000000..6b15d5113675 --- /dev/null +++ b/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-ky/strings.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (c) 2018, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="experiment_navigationbar_overlay" msgid="6953777362606036161">"Жука чабыттоо тилкесин колдонуу"</string> +</resources> diff --git a/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-lo/strings.xml b/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-lo/strings.xml new file mode 100644 index 000000000000..6ec48ca13d19 --- /dev/null +++ b/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-lo/strings.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (c) 2018, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="experiment_navigationbar_overlay" msgid="6953777362606036161">"ການທົດລອງແຖບການນຳທາງແບບບາງ"</string> +</resources> diff --git a/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-lt/strings.xml b/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-lt/strings.xml new file mode 100644 index 000000000000..1df54aa3fc02 --- /dev/null +++ b/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-lt/strings.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (c) 2018, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="experiment_navigationbar_overlay" msgid="6953777362606036161">"Plonos naršymo juostos eksperimentas"</string> +</resources> diff --git a/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-lv/strings.xml b/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-lv/strings.xml new file mode 100644 index 000000000000..5c6c5658406b --- /dev/null +++ b/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-lv/strings.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (c) 2018, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="experiment_navigationbar_overlay" msgid="6953777362606036161">"Plānas navigācijas joslas eksperiments"</string> +</resources> diff --git a/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-mk/strings.xml b/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-mk/strings.xml new file mode 100644 index 000000000000..3517813f74ad --- /dev/null +++ b/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-mk/strings.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (c) 2018, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="experiment_navigationbar_overlay" msgid="6953777362606036161">"Експеримент со тенка лента за навигација"</string> +</resources> diff --git a/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-mn/strings.xml b/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-mn/strings.xml new file mode 100644 index 000000000000..a2282c4c71c9 --- /dev/null +++ b/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-mn/strings.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (c) 2018, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="experiment_navigationbar_overlay" msgid="6953777362606036161">"Нимгэн навигацийн самбарын туршилт"</string> +</resources> diff --git a/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-mr/strings.xml b/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-mr/strings.xml new file mode 100644 index 000000000000..c714370cdc7e --- /dev/null +++ b/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-mr/strings.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (c) 2018, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="experiment_navigationbar_overlay" msgid="6953777362606036161">"स्लिम नॅव्हिगेशन बार प्रयोग"</string> +</resources> diff --git a/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-ms/strings.xml b/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-ms/strings.xml new file mode 100644 index 000000000000..68f831e39b46 --- /dev/null +++ b/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-ms/strings.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (c) 2018, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="experiment_navigationbar_overlay" msgid="6953777362606036161">"Percubaan Bar Navigasi Langsing"</string> +</resources> diff --git a/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-my/strings.xml b/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-my/strings.xml new file mode 100644 index 000000000000..84db27962308 --- /dev/null +++ b/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-my/strings.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (c) 2018, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="experiment_navigationbar_overlay" msgid="6953777362606036161">"ပါးလွှာသော လမ်းညွှန်ဘား စမ်းသပ်မှု"</string> +</resources> diff --git a/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-nb/strings.xml b/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-nb/strings.xml new file mode 100644 index 000000000000..e1ff863ac5c8 --- /dev/null +++ b/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-nb/strings.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (c) 2018, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="experiment_navigationbar_overlay" msgid="6953777362606036161">"Eksperiment med tynn navigasjonsrad"</string> +</resources> diff --git a/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-nl/strings.xml b/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-nl/strings.xml new file mode 100644 index 000000000000..01190bc624c8 --- /dev/null +++ b/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-nl/strings.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (c) 2018, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="experiment_navigationbar_overlay" msgid="6953777362606036161">"Experiment voor smalle navigatiebalk"</string> +</resources> diff --git a/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-pl/strings.xml b/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-pl/strings.xml new file mode 100644 index 000000000000..1742aadbcdd3 --- /dev/null +++ b/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-pl/strings.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (c) 2018, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="experiment_navigationbar_overlay" msgid="6953777362606036161">"Eksperyment z wąskim paskiem nawigacyjnym"</string> +</resources> diff --git a/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-pt-rBR/strings.xml b/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-pt-rBR/strings.xml new file mode 100644 index 000000000000..22194b7e1ca5 --- /dev/null +++ b/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-pt-rBR/strings.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (c) 2018, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="experiment_navigationbar_overlay" msgid="6953777362606036161">"Experimento de barra de navegação fina"</string> +</resources> diff --git a/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-pt-rPT/strings.xml b/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-pt-rPT/strings.xml new file mode 100644 index 000000000000..f6c030983578 --- /dev/null +++ b/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-pt-rPT/strings.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (c) 2018, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="experiment_navigationbar_overlay" msgid="6953777362606036161">"Experiência de barra de navegação fina"</string> +</resources> diff --git a/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-pt/strings.xml b/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-pt/strings.xml new file mode 100644 index 000000000000..22194b7e1ca5 --- /dev/null +++ b/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-pt/strings.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (c) 2018, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="experiment_navigationbar_overlay" msgid="6953777362606036161">"Experimento de barra de navegação fina"</string> +</resources> diff --git a/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-ro/strings.xml b/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-ro/strings.xml new file mode 100644 index 000000000000..e1655f27b977 --- /dev/null +++ b/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-ro/strings.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (c) 2018, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="experiment_navigationbar_overlay" msgid="6953777362606036161">"Experiment cu bară de navigare subțire"</string> +</resources> diff --git a/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-ru/strings.xml b/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-ru/strings.xml new file mode 100644 index 000000000000..cac66dc921b1 --- /dev/null +++ b/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-ru/strings.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (c) 2018, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="experiment_navigationbar_overlay" msgid="6953777362606036161">"Узкая панель навигации (эксперимент)"</string> +</resources> diff --git a/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-sk/strings.xml b/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-sk/strings.xml new file mode 100644 index 000000000000..6a1ce4128cd9 --- /dev/null +++ b/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-sk/strings.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (c) 2018, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="experiment_navigationbar_overlay" msgid="6953777362606036161">"Úzky navigačný panel (experiment)"</string> +</resources> diff --git a/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-sl/strings.xml b/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-sl/strings.xml new file mode 100644 index 000000000000..beab7b67f60e --- /dev/null +++ b/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-sl/strings.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (c) 2018, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="experiment_navigationbar_overlay" msgid="6953777362606036161">"Preizkus z vitko vrstico za krmarjenje"</string> +</resources> diff --git a/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-sq/strings.xml b/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-sq/strings.xml new file mode 100644 index 000000000000..b7a28d57391d --- /dev/null +++ b/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-sq/strings.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (c) 2018, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="experiment_navigationbar_overlay" msgid="6953777362606036161">"Eksperimenti i shiritit të hollë të navigimit"</string> +</resources> diff --git a/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-sr/strings.xml b/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-sr/strings.xml new file mode 100644 index 000000000000..048f649ddb7b --- /dev/null +++ b/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-sr/strings.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (c) 2018, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="experiment_navigationbar_overlay" msgid="6953777362606036161">"Експеримент са танком траком за навигацију"</string> +</resources> diff --git a/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-sv/strings.xml b/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-sv/strings.xml new file mode 100644 index 000000000000..b94438f6971a --- /dev/null +++ b/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-sv/strings.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (c) 2018, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="experiment_navigationbar_overlay" msgid="6953777362606036161">"Experimentellt tunt navigeringsfält"</string> +</resources> diff --git a/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-sw/strings.xml b/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-sw/strings.xml new file mode 100644 index 000000000000..3a5a73cd0afa --- /dev/null +++ b/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-sw/strings.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (c) 2018, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="experiment_navigationbar_overlay" msgid="6953777362606036161">"Jaribio la Sehemu ya Viungo Muhimu Inayoweza Kupunguzwa"</string> +</resources> diff --git a/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-th/strings.xml b/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-th/strings.xml new file mode 100644 index 000000000000..945297bec53d --- /dev/null +++ b/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-th/strings.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (c) 2018, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="experiment_navigationbar_overlay" msgid="6953777362606036161">"การทดสอบแถบนำทางแบบบาง"</string> +</resources> diff --git a/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-tl/strings.xml b/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-tl/strings.xml new file mode 100644 index 000000000000..0c8087c68b8b --- /dev/null +++ b/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-tl/strings.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (c) 2018, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="experiment_navigationbar_overlay" msgid="6953777362606036161">"Eksperimentong Slim na Navigation Bar"</string> +</resources> diff --git a/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-tr/strings.xml b/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-tr/strings.xml new file mode 100644 index 000000000000..a3ca75455391 --- /dev/null +++ b/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-tr/strings.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (c) 2018, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="experiment_navigationbar_overlay" msgid="6953777362606036161">"İnce Gezinme Çubuğu Denemesi"</string> +</resources> diff --git a/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-uk/strings.xml b/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-uk/strings.xml new file mode 100644 index 000000000000..656c4a97bd6c --- /dev/null +++ b/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-uk/strings.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (c) 2018, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="experiment_navigationbar_overlay" msgid="6953777362606036161">"Експеримент із тонкою панеллю навігації"</string> +</resources> diff --git a/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-ur/strings.xml b/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-ur/strings.xml new file mode 100644 index 000000000000..bcd6bc3f257e --- /dev/null +++ b/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-ur/strings.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (c) 2018, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="experiment_navigationbar_overlay" msgid="6953777362606036161">"سلم نیویگیشن بار کا تجربہ"</string> +</resources> diff --git a/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-uz/strings.xml b/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-uz/strings.xml new file mode 100644 index 000000000000..0d40981784c8 --- /dev/null +++ b/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-uz/strings.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (c) 2018, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="experiment_navigationbar_overlay" msgid="6953777362606036161">"Ingichka navigatsiya paneli tajribasi"</string> +</resources> diff --git a/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-vi/strings.xml b/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-vi/strings.xml new file mode 100644 index 000000000000..dad56b41ae39 --- /dev/null +++ b/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-vi/strings.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (c) 2018, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="experiment_navigationbar_overlay" msgid="6953777362606036161">"Thử nghiệm thanh điều hướng mỏng"</string> +</resources> diff --git a/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-zh-rCN/strings.xml b/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-zh-rCN/strings.xml new file mode 100644 index 000000000000..b2602e0e0093 --- /dev/null +++ b/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-zh-rCN/strings.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (c) 2018, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="experiment_navigationbar_overlay" msgid="6953777362606036161">"精简导航栏实验"</string> +</resources> diff --git a/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-zh-rHK/strings.xml b/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-zh-rHK/strings.xml new file mode 100644 index 000000000000..d5259d38ffa1 --- /dev/null +++ b/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-zh-rHK/strings.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (c) 2018, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="experiment_navigationbar_overlay" msgid="6953777362606036161">"精簡導覽列實驗"</string> +</resources> diff --git a/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-zh-rTW/strings.xml b/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-zh-rTW/strings.xml new file mode 100644 index 000000000000..6586a573b494 --- /dev/null +++ b/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-zh-rTW/strings.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (c) 2018, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="experiment_navigationbar_overlay" msgid="6953777362606036161">"細長導覽列實驗"</string> +</resources> diff --git a/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-zu/strings.xml b/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-zu/strings.xml new file mode 100644 index 000000000000..12d04d3b21f6 --- /dev/null +++ b/packages/overlays/ExperimentNavigationBarSlimOverlay/res/values-zu/strings.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright (c) 2018, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="experiment_navigationbar_overlay" msgid="6953777362606036161">"Ukuhlolwa kwebha yokuzula encane"</string> +</resources> diff --git a/proto/src/metrics_constants/metrics_constants.proto b/proto/src/metrics_constants/metrics_constants.proto index da1fee374520..d15b277b22c3 100644 --- a/proto/src/metrics_constants/metrics_constants.proto +++ b/proto/src/metrics_constants/metrics_constants.proto @@ -6569,6 +6569,11 @@ message MetricsEvent { // OS: Q LOCK_SCREEN_NOTIFICATION_CONTENT = 1584; + // ConfirmDeviceCredentials > BiometricPrompt + // CATEGORY: SETTINGS + // OS: Q + BIOMETRIC_FRAGMENT = 1585; + // ---- End Q Constants, all Q constants go above this line ---- // Add new aosp constants above this line. diff --git a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java index ebd9e77a6a98..6eba914f15bc 100644 --- a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java +++ b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java @@ -457,13 +457,12 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ final int interrogatingPid = Binder.getCallingPid(); callback = mSystemSupport.replaceCallbackIfNeeded(callback, resolvedWindowId, interactionId, interrogatingPid, interrogatingTid); - final int callingUid = Binder.getCallingUid(); final long identityToken = Binder.clearCallingIdentity(); try { connection.getRemote().findAccessibilityNodeInfosByViewId(accessibilityNodeId, viewIdResName, partialInteractiveRegion, interactionId, callback, mFetchFlags, interrogatingPid, interrogatingTid, spec); - return mSecurityPolicy.computeValidReportedPackages(callingUid, + return mSecurityPolicy.computeValidReportedPackages( connection.getPackageName(), connection.getUid()); } catch (RemoteException re) { if (DEBUG) { @@ -514,13 +513,12 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ final int interrogatingPid = Binder.getCallingPid(); callback = mSystemSupport.replaceCallbackIfNeeded(callback, resolvedWindowId, interactionId, interrogatingPid, interrogatingTid); - final int callingUid = Binder.getCallingUid(); final long identityToken = Binder.clearCallingIdentity(); try { connection.getRemote().findAccessibilityNodeInfosByText(accessibilityNodeId, text, partialInteractiveRegion, interactionId, callback, mFetchFlags, interrogatingPid, interrogatingTid, spec); - return mSecurityPolicy.computeValidReportedPackages(callingUid, + return mSecurityPolicy.computeValidReportedPackages( connection.getPackageName(), connection.getUid()); } catch (RemoteException re) { if (DEBUG) { @@ -571,13 +569,12 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ final int interrogatingPid = Binder.getCallingPid(); callback = mSystemSupport.replaceCallbackIfNeeded(callback, resolvedWindowId, interactionId, interrogatingPid, interrogatingTid); - final int callingUid = Binder.getCallingUid(); final long identityToken = Binder.clearCallingIdentity(); try { connection.getRemote().findAccessibilityNodeInfoByAccessibilityId( accessibilityNodeId, partialInteractiveRegion, interactionId, callback, mFetchFlags | flags, interrogatingPid, interrogatingTid, spec, arguments); - return mSecurityPolicy.computeValidReportedPackages(callingUid, + return mSecurityPolicy.computeValidReportedPackages( connection.getPackageName(), connection.getUid()); } catch (RemoteException re) { if (DEBUG) { @@ -628,13 +625,12 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ final int interrogatingPid = Binder.getCallingPid(); callback = mSystemSupport.replaceCallbackIfNeeded(callback, resolvedWindowId, interactionId, interrogatingPid, interrogatingTid); - final int callingUid = Binder.getCallingUid(); final long identityToken = Binder.clearCallingIdentity(); try { connection.getRemote().findFocus(accessibilityNodeId, focusType, partialInteractiveRegion, interactionId, callback, mFetchFlags, interrogatingPid, interrogatingTid, spec); - return mSecurityPolicy.computeValidReportedPackages(callingUid, + return mSecurityPolicy.computeValidReportedPackages( connection.getPackageName(), connection.getUid()); } catch (RemoteException re) { if (DEBUG) { @@ -684,13 +680,12 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ final int interrogatingPid = Binder.getCallingPid(); callback = mSystemSupport.replaceCallbackIfNeeded(callback, resolvedWindowId, interactionId, interrogatingPid, interrogatingTid); - final int callingUid = Binder.getCallingUid(); final long identityToken = Binder.clearCallingIdentity(); try { connection.getRemote().focusSearch(accessibilityNodeId, direction, partialInteractiveRegion, interactionId, callback, mFetchFlags, interrogatingPid, interrogatingTid, spec); - return mSecurityPolicy.computeValidReportedPackages(callingUid, + return mSecurityPolicy.computeValidReportedPackages( connection.getPackageName(), connection.getUid()); } catch (RemoteException re) { if (DEBUG) { diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java index fbceade2341f..e89b9517da84 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java @@ -348,11 +348,24 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub } boolean getBindInstantServiceAllowed(int userId) { - return mSecurityPolicy.getBindInstantServiceAllowed(userId); + final UserState userState = getUserState(userId); + if (userState == null) return false; + return userState.getBindInstantServiceAllowed(); } void setBindInstantServiceAllowed(int userId, boolean allowed) { - mSecurityPolicy.setBindInstantServiceAllowed(userId, allowed); + UserState userState; + synchronized (mLock) { + userState = getUserState(userId); + if (userState == null) { + if (!allowed) { + return; + } + userState = new UserState(userId); + mUserStates.put(userId, userState); + } + } + userState.setBindInstantServiceAllowed(allowed); } private void registerBroadcastReceivers() { @@ -1316,7 +1329,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub | PackageManager.MATCH_DIRECT_BOOT_AWARE | PackageManager.MATCH_DIRECT_BOOT_UNAWARE; - if (userState.mBindInstantServiceAllowed) { + if (userState.getBindInstantServiceAllowed()) { flags |= PackageManager.MATCH_INSTANT; } @@ -2068,7 +2081,9 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub && componentNameToEnable.equals(userState.mServiceToEnableWithShortcut)) { return false; } + userState.mServiceToEnableWithShortcut = componentNameToEnable; + scheduleNotifyClientsOfServicesStateChange(userState); return true; } @@ -2343,10 +2358,10 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub @Override public void performAccessibilityShortcut() { if ((UserHandle.getAppId(Binder.getCallingUid()) != Process.SYSTEM_UID) - && (mContext.checkCallingPermission(Manifest.permission.WRITE_SECURE_SETTINGS) + && (mContext.checkCallingPermission(Manifest.permission.MANAGE_ACCESSIBILITY) != PackageManager.PERMISSION_GRANTED)) { throw new SecurityException( - "performAccessibilityShortcut requires the WRITE_SECURE_SETTINGS permission"); + "performAccessibilityShortcut requires the MANAGE_ACCESSIBILITY permission"); } final Map<ComponentName, ToggleableFrameworkFeatureInfo> frameworkFeatureMap = AccessibilityShortcutController.getFrameworkShortcutFeaturesMap(); @@ -2381,6 +2396,19 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub } }; + @Override + public String getAccessibilityShortcutService() { + if (mContext.checkCallingPermission(Manifest.permission.MANAGE_ACCESSIBILITY) + != PackageManager.PERMISSION_GRANTED) { + throw new SecurityException( + "getAccessibilityShortcutService requires the MANAGE_ACCESSIBILITY permission"); + } + synchronized(mLock) { + final UserState userState = getUserStateLocked(mCurrentUserId); + return userState.mServiceToEnableWithShortcut.flattenToString(); + } + } + /** * Enables accessibility service specified by {@param componentName} for the {@param userId}. */ @@ -3143,9 +3171,15 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub return packageNames[0]; } - String[] computeValidReportedPackages(int callingUid, - String targetPackage, int targetUid) { - if (UserHandle.getAppId(callingUid) == Process.SYSTEM_UID) { + /** + * Get a list of package names an app may report, including any widget packages it owns. + * + * @param targetPackage The known valid target package + * @param targetUid The uid of the target app + * @return + */ + String[] computeValidReportedPackages(String targetPackage, int targetUid) { + if (UserHandle.getAppId(targetUid) == Process.SYSTEM_UID) { // Empty array means any package is Okay return EmptyArray.STRING; } @@ -3169,32 +3203,6 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub return uidPackages; } - private boolean getBindInstantServiceAllowed(int userId) { - mContext.enforceCallingOrSelfPermission( - Manifest.permission.MANAGE_BIND_INSTANT_SERVICE, - "getBindInstantServiceAllowed"); - UserState state = mUserStates.get(userId); - return (state != null) && state.mBindInstantServiceAllowed; - } - - private void setBindInstantServiceAllowed(int userId, boolean allowed) { - mContext.enforceCallingOrSelfPermission( - Manifest.permission.MANAGE_BIND_INSTANT_SERVICE, - "setBindInstantServiceAllowed"); - UserState state = mUserStates.get(userId); - if (state == null) { - if (!allowed) { - return; - } - state = new UserState(userId); - mUserStates.put(userId, state); - } - if (state.mBindInstantServiceAllowed != allowed) { - state.mBindInstantServiceAllowed = allowed; - onUserStateChangedLocked(state); - } - } - public void clearWindowsLocked() { List<WindowInfo> windows = Collections.emptyList(); final int activeWindowId = mActiveWindowId; @@ -3804,7 +3812,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub public boolean mUserMinimumUiTimeoutEnabled; public int mUserMinimumUiTimeout; - public boolean mBindInstantServiceAllowed; + private boolean mBindInstantServiceAllowed; public UserState(int userId) { mUserId = userId; @@ -4036,6 +4044,24 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE, 0) & SHOW_MODE_HARD_KEYBOARD_ORIGINAL_VALUE) != 0; } + + public boolean getBindInstantServiceAllowed() { + synchronized (mLock) { + return mBindInstantServiceAllowed; + } + } + + public void setBindInstantServiceAllowed(boolean allowed) { + synchronized (mLock) { + mContext.enforceCallingOrSelfPermission( + Manifest.permission.MANAGE_BIND_INSTANT_SERVICE, + "setBindInstantServiceAllowed"); + if (allowed) { + mBindInstantServiceAllowed = allowed; + onUserStateChangedLocked(this); + } + } + } } private final class AccessibilityContentObserver extends ContentObserver { diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java index 2396ded6dba5..86132a8e6473 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java @@ -92,7 +92,7 @@ class AccessibilityServiceConnection extends AbstractAccessibilityServiceConnect final long identity = Binder.clearCallingIdentity(); try { int flags = Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE; - if (userState.mBindInstantServiceAllowed) { + if (userState.getBindInstantServiceAllowed()) { flags |= Context.BIND_ALLOW_INSTANT; } if (mService == null && mContext.bindServiceAsUser( diff --git a/services/backup/java/com/android/server/backup/Trampoline.java b/services/backup/java/com/android/server/backup/Trampoline.java index 818154b283da..8e4c243e4487 100644 --- a/services/backup/java/com/android/server/backup/Trampoline.java +++ b/services/backup/java/com/android/server/backup/Trampoline.java @@ -65,18 +65,17 @@ import java.io.PrintWriter; */ public class Trampoline extends IBackupManager.Stub { static final String TAG = "BackupManagerService"; - static final boolean DEBUG_TRAMPOLINE = false; // When this file is present, the backup service is inactive - static final String BACKUP_SUPPRESS_FILENAME = "backup-suppress"; + private static final String BACKUP_SUPPRESS_FILENAME = "backup-suppress"; // Product-level suppression of backup/restore - static final String BACKUP_DISABLE_PROPERTY = "ro.backup.disable"; + private static final String BACKUP_DISABLE_PROPERTY = "ro.backup.disable"; final Context mContext; - final File mSuppressFile; // existence testing & creating synchronized on 'this' - final boolean mGlobalDisable; - volatile BackupManagerService mService; + private final File mSuppressFile; // existence testing & creating synchronized on 'this' + private final boolean mGlobalDisable; + private volatile BackupManagerService mService; private HandlerThread mHandlerThread; diff --git a/services/backup/java/com/android/server/backup/encryption/chunking/ChunkEncryptor.java b/services/backup/java/com/android/server/backup/encryption/chunking/ChunkEncryptor.java new file mode 100644 index 000000000000..812cfbd76e31 --- /dev/null +++ b/services/backup/java/com/android/server/backup/encryption/chunking/ChunkEncryptor.java @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.server.backup.encryption.chunking; + +import com.android.server.backup.encryption.chunk.ChunkHash; +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; +import javax.crypto.BadPaddingException; +import javax.crypto.Cipher; +import javax.crypto.IllegalBlockSizeException; +import javax.crypto.NoSuchPaddingException; +import javax.crypto.SecretKey; +import javax.crypto.spec.GCMParameterSpec; + +/** Encrypts chunks of a file using AES/GCM. */ +public class ChunkEncryptor { + private static final String CIPHER_ALGORITHM = "AES/GCM/NoPadding"; + private static final int GCM_NONCE_LENGTH_BYTES = 12; + private static final int GCM_TAG_LENGTH_BYTES = 16; + + private final SecretKey mSecretKey; + private final SecureRandom mSecureRandom; + + /** + * A new instance using {@code mSecretKey} to encrypt chunks and {@code mSecureRandom} to + * generate nonces. + */ + public ChunkEncryptor(SecretKey secretKey, SecureRandom secureRandom) { + this.mSecretKey = secretKey; + this.mSecureRandom = secureRandom; + } + + /** + * Transforms {@code plaintext} into an {@link EncryptedChunk}. + * + * @param plaintextHash The hash of the plaintext to encrypt, to attach as the key of the chunk. + * @param plaintext Bytes to encrypt. + * @throws InvalidKeyException If the given secret key is not a valid AES key for decryption. + * @throws IllegalBlockSizeException If the input data cannot be encrypted using + * AES/GCM/NoPadding. This should never be the case. + */ + public EncryptedChunk encrypt(ChunkHash plaintextHash, byte[] plaintext) + throws InvalidKeyException, IllegalBlockSizeException { + byte[] nonce = generateNonce(); + Cipher cipher; + try { + cipher = Cipher.getInstance(CIPHER_ALGORITHM); + cipher.init( + Cipher.ENCRYPT_MODE, + mSecretKey, + new GCMParameterSpec(GCM_TAG_LENGTH_BYTES * 8, nonce)); + } catch (NoSuchAlgorithmException + | NoSuchPaddingException + | InvalidAlgorithmParameterException e) { + // This can not happen - AES/GCM/NoPadding is supported. + throw new AssertionError(e); + } + byte[] encryptedBytes; + try { + encryptedBytes = cipher.doFinal(plaintext); + } catch (BadPaddingException e) { + // This can not happen - BadPaddingException can only be thrown in decrypt mode. + throw new AssertionError("Impossible: threw BadPaddingException in encrypt mode."); + } + + return EncryptedChunk.create(/*key=*/ plaintextHash, nonce, encryptedBytes); + } + + private byte[] generateNonce() { + byte[] nonce = new byte[GCM_NONCE_LENGTH_BYTES]; + mSecureRandom.nextBytes(nonce); + return nonce; + } +} diff --git a/services/backup/java/com/android/server/backup/encryption/chunking/ChunkHasher.java b/services/backup/java/com/android/server/backup/encryption/chunking/ChunkHasher.java new file mode 100644 index 000000000000..145b7bf82517 --- /dev/null +++ b/services/backup/java/com/android/server/backup/encryption/chunking/ChunkHasher.java @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.server.backup.encryption.chunking; + +import com.android.server.backup.encryption.chunk.ChunkHash; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import javax.crypto.Mac; +import javax.crypto.SecretKey; + +/** Computes the SHA-256 HMAC of a chunk of bytes. */ +public class ChunkHasher { + private static final String MAC_ALGORITHM = "HmacSHA256"; + + private final SecretKey mSecretKey; + + /** Constructs a new hasher which computes the HMAC using the given secret key. */ + public ChunkHasher(SecretKey secretKey) { + this.mSecretKey = secretKey; + } + + /** Returns the SHA-256 over the given bytes. */ + public ChunkHash computeHash(byte[] plaintext) throws InvalidKeyException { + try { + Mac mac = Mac.getInstance(MAC_ALGORITHM); + mac.init(mSecretKey); + return new ChunkHash(mac.doFinal(plaintext)); + } catch (NoSuchAlgorithmException e) { + // This can not happen - AES/GCM/NoPadding is available as part of the framework. + throw new AssertionError(e); + } + } +} diff --git a/services/backup/java/com/android/server/backup/encryption/chunking/Chunker.java b/services/backup/java/com/android/server/backup/encryption/chunking/Chunker.java new file mode 100644 index 000000000000..b91913e5fc80 --- /dev/null +++ b/services/backup/java/com/android/server/backup/encryption/chunking/Chunker.java @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.server.backup.encryption.chunking; + +import java.io.IOException; +import java.io.InputStream; +import java.security.GeneralSecurityException; + +/** Splits an input stream into chunks, which are to be encrypted separately. */ +public interface Chunker { + /** + * Splits the input stream into chunks. + * + * @param inputStream The input stream. + * @param chunkConsumer A function that processes each chunk as it is produced. + * @throws IOException If there is a problem reading the input stream. + * @throws GeneralSecurityException if the consumer function throws an error. + */ + void chunkify(InputStream inputStream, ChunkConsumer chunkConsumer) + throws IOException, GeneralSecurityException; + + /** Function that consumes chunks. */ + interface ChunkConsumer { + /** + * Invoked for each chunk. + * + * @param chunk Plaintext bytes of chunk. + * @throws GeneralSecurityException if there is an issue encrypting the chunk. + */ + void accept(byte[] chunk) throws GeneralSecurityException; + } +} diff --git a/services/backup/java/com/android/server/backup/encryption/chunking/RawBackupWriter.java b/services/backup/java/com/android/server/backup/encryption/chunking/RawBackupWriter.java new file mode 100644 index 000000000000..839dc7c7b5ce --- /dev/null +++ b/services/backup/java/com/android/server/backup/encryption/chunking/RawBackupWriter.java @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.server.backup.encryption.chunking; + +import java.io.IOException; +import java.io.OutputStream; + +/** Writes data straight to an output stream. */ +public class RawBackupWriter implements BackupWriter { + private final OutputStream outputStream; + private long bytesWritten; + + /** Constructs a new writer which writes bytes to the given output stream. */ + public RawBackupWriter(OutputStream outputStream) { + this.outputStream = outputStream; + } + + @Override + public void writeBytes(byte[] bytes) throws IOException { + outputStream.write(bytes); + bytesWritten += bytes.length; + } + + @Override + public void writeChunk(long start, int length) throws IOException { + throw new UnsupportedOperationException("RawBackupWriter cannot write existing chunks"); + } + + @Override + public long getBytesWritten() { + return bytesWritten; + } + + @Override + public void flush() throws IOException { + outputStream.flush(); + } +} diff --git a/services/core/java/com/android/server/BinderCallsStatsService.java b/services/core/java/com/android/server/BinderCallsStatsService.java index 872261a035e5..dd960751ab21 100644 --- a/services/core/java/com/android/server/BinderCallsStatsService.java +++ b/services/core/java/com/android/server/BinderCallsStatsService.java @@ -49,6 +49,7 @@ public class BinderCallsStatsService extends Binder { private static final String PERSIST_SYS_BINDER_CALLS_DETAILED_TRACKING = "persist.sys.binder_calls_detailed_tracking"; + /** Listens for flag changes. */ private static class SettingsObserver extends ContentObserver { private static final String SETTINGS_ENABLED_KEY = "enabled"; @@ -101,7 +102,14 @@ public class BinderCallsStatsService extends Binder { final boolean enabled = mParser.getBoolean(SETTINGS_ENABLED_KEY, BinderCallsStats.ENABLED_DEFAULT); if (mEnabled != enabled) { - Binder.setObserver(enabled ? mBinderCallsStats : null); + if (enabled) { + Binder.setObserver(mBinderCallsStats); + Binder.setProxyTransactListener( + new Binder.PropagateWorkSourceTransactListener()); + } else { + Binder.setObserver(null); + Binder.setProxyTransactListener(null); + } mEnabled = enabled; mBinderCallsStats.reset(); } diff --git a/services/core/java/com/android/server/MmsServiceBroker.java b/services/core/java/com/android/server/MmsServiceBroker.java index f15cd2adbf07..b6fa1570cdb0 100644 --- a/services/core/java/com/android/server/MmsServiceBroker.java +++ b/services/core/java/com/android/server/MmsServiceBroker.java @@ -17,7 +17,6 @@ package com.android.server; import android.Manifest; -import android.app.ActivityManagerInternal; import android.app.AppOpsManager; import android.app.PendingIntent; import android.content.ComponentName; @@ -337,6 +336,7 @@ public class MmsServiceBroker extends SystemService { mContext.enforceCallingPermission(Manifest.permission.SEND_SMS, "Send MMS message"); if (getAppOpsManager().noteOp(AppOpsManager.OP_SEND_SMS, Binder.getCallingUid(), callingPkg) != AppOpsManager.MODE_ALLOWED) { + Slog.e(TAG, callingPkg + " is not allowed to call sendMessage()"); return; } contentUri = adjustUriForUserAndGrantPermission(contentUri, @@ -355,6 +355,7 @@ public class MmsServiceBroker extends SystemService { "Download MMS message"); if (getAppOpsManager().noteOp(AppOpsManager.OP_RECEIVE_MMS, Binder.getCallingUid(), callingPkg) != AppOpsManager.MODE_ALLOWED) { + Slog.e(TAG, callingPkg + " is not allowed to call downloadMessage()"); return; } contentUri = adjustUriForUserAndGrantPermission(contentUri, diff --git a/services/core/java/com/android/server/NetworkManagementService.java b/services/core/java/com/android/server/NetworkManagementService.java index 865a651fb722..92d8d73d32d1 100644 --- a/services/core/java/com/android/server/NetworkManagementService.java +++ b/services/core/java/com/android/server/NetworkManagementService.java @@ -61,6 +61,7 @@ import android.net.TetherStatsParcel; import android.net.INetworkManagementEventObserver; import android.net.ITetheringStatsProvider; import android.net.InterfaceConfiguration; +import android.net.InterfaceConfigurationParcel; import android.net.IpPrefix; import android.net.LinkAddress; import android.net.Network; @@ -970,55 +971,29 @@ public class NetworkManagementService extends INetworkManagementService.Stub public String[] listInterfaces() { mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG); try { - return NativeDaemonEvent.filterMessageList( - mConnector.executeForList("interface", "list"), InterfaceListResult); - } catch (NativeDaemonConnectorException e) { - throw e.rethrowAsParcelableException(); + final List<String> result = mNetdService.interfaceGetList(); + return result.toArray(EMPTY_STRING_ARRAY); + } catch (RemoteException | ServiceSpecificException e) { + throw new IllegalStateException(e); } } @Override public InterfaceConfiguration getInterfaceConfig(String iface) { mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG); - - final NativeDaemonEvent event; + final InterfaceConfigurationParcel result; try { - event = mConnector.execute("interface", "getcfg", iface); - } catch (NativeDaemonConnectorException e) { - throw e.rethrowAsParcelableException(); + result = mNetdService.interfaceGetCfg(iface); + } catch (RemoteException | ServiceSpecificException e) { + throw new IllegalStateException(e); } - event.checkCode(InterfaceGetCfgResult); - - // Rsp: 213 xx:xx:xx:xx:xx:xx yyy.yyy.yyy.yyy zzz flag1 flag2 flag3 - final StringTokenizer st = new StringTokenizer(event.getMessage()); - - InterfaceConfiguration cfg; try { - cfg = new InterfaceConfiguration(); - cfg.setHardwareAddress(st.nextToken(" ")); - InetAddress addr = null; - int prefixLength = 0; - try { - addr = NetworkUtils.numericToInetAddress(st.nextToken()); - } catch (IllegalArgumentException iae) { - Slog.e(TAG, "Failed to parse ipaddr", iae); - } - - try { - prefixLength = Integer.parseInt(st.nextToken()); - } catch (NumberFormatException nfe) { - Slog.e(TAG, "Failed to parse prefixLength", nfe); - } - - cfg.setLinkAddress(new LinkAddress(addr, prefixLength)); - while (st.hasMoreTokens()) { - cfg.setFlag(st.nextToken()); - } - } catch (NoSuchElementException nsee) { - throw new IllegalStateException("Invalid response from daemon: " + event); + final InterfaceConfiguration cfg = InterfaceConfiguration.fromParcel(result); + return cfg; + } catch (IllegalArgumentException iae) { + throw new IllegalStateException("Invalid InterfaceConfigurationParcel", iae); } - return cfg; } @Override @@ -1029,17 +1004,12 @@ public class NetworkManagementService extends INetworkManagementService.Stub throw new IllegalStateException("Null LinkAddress given"); } - final Command cmd = new Command("interface", "setcfg", iface, - linkAddr.getAddress().getHostAddress(), - linkAddr.getPrefixLength()); - for (String flag : cfg.getFlags()) { - cmd.appendArg(flag); - } + final InterfaceConfigurationParcel cfgParcel = cfg.toParcel(iface); try { - mConnector.execute(cmd); - } catch (NativeDaemonConnectorException e) { - throw e.rethrowAsParcelableException(); + mNetdService.interfaceSetCfg(cfgParcel); + } catch (RemoteException | ServiceSpecificException e) { + throw new IllegalStateException(e); } } @@ -1063,10 +1033,9 @@ public class NetworkManagementService extends INetworkManagementService.Stub public void setInterfaceIpv6PrivacyExtensions(String iface, boolean enable) { mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG); try { - mConnector.execute( - "interface", "ipv6privacyextensions", iface, enable ? "enable" : "disable"); - } catch (NativeDaemonConnectorException e) { - throw e.rethrowAsParcelableException(); + mNetdService.interfaceSetIPv6PrivacyExtensions(iface, enable); + } catch (RemoteException | ServiceSpecificException e) { + throw new IllegalStateException(e); } } @@ -1076,9 +1045,9 @@ public class NetworkManagementService extends INetworkManagementService.Stub public void clearInterfaceAddresses(String iface) { mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG); try { - mConnector.execute("interface", "clearaddrs", iface); - } catch (NativeDaemonConnectorException e) { - throw e.rethrowAsParcelableException(); + mNetdService.interfaceClearAddrs(iface); + } catch (RemoteException | ServiceSpecificException e) { + throw new IllegalStateException(e); } } @@ -1086,9 +1055,9 @@ public class NetworkManagementService extends INetworkManagementService.Stub public void enableIpv6(String iface) { mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG); try { - mConnector.execute("interface", "ipv6", iface, "enable"); - } catch (NativeDaemonConnectorException e) { - throw e.rethrowAsParcelableException(); + mNetdService.interfaceSetEnableIPv6(iface, true); + } catch (RemoteException | ServiceSpecificException e) { + throw new IllegalStateException(e); } } @@ -1105,9 +1074,9 @@ public class NetworkManagementService extends INetworkManagementService.Stub public void disableIpv6(String iface) { mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG); try { - mConnector.execute("interface", "ipv6", iface, "disable"); - } catch (NativeDaemonConnectorException e) { - throw e.rethrowAsParcelableException(); + mNetdService.interfaceSetEnableIPv6(iface, false); + } catch (RemoteException | ServiceSpecificException e) { + throw new IllegalStateException(e); } } @@ -1189,11 +1158,10 @@ public class NetworkManagementService extends INetworkManagementService.Stub public void setMtu(String iface, int mtu) { mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG); - final NativeDaemonEvent event; try { - event = mConnector.execute("interface", "setmtu", iface, mtu); - } catch (NativeDaemonConnectorException e) { - throw e.rethrowAsParcelableException(); + mNetdService.interfaceSetMtu(iface, mtu); + } catch (RemoteException | ServiceSpecificException e) { + throw new IllegalStateException(e); } } diff --git a/services/core/java/com/android/server/am/ActivityDisplay.java b/services/core/java/com/android/server/am/ActivityDisplay.java index ede13ef66ac4..05293b5138a0 100644 --- a/services/core/java/com/android/server/am/ActivityDisplay.java +++ b/services/core/java/com/android/server/am/ActivityDisplay.java @@ -163,6 +163,23 @@ class ActivityDisplay extends ConfigurationContainer<ActivityStack> setBounds(0, 0, mTmpDisplaySize.x, mTmpDisplaySize.y); } + void onDisplayChanged() { + // The window policy is responsible for stopping activities on the default display. + final int displayId = mDisplay.getDisplayId(); + if (displayId != DEFAULT_DISPLAY) { + final int displayState = mDisplay.getState(); + if (displayState == Display.STATE_OFF && mOffToken == null) { + mOffToken = mSupervisor.mService.acquireSleepToken("Display-off", displayId); + } else if (displayState == Display.STATE_ON && mOffToken != null) { + mOffToken.release(); + mOffToken = null; + } + } + + updateBounds(); + mWindowContainerController.onDisplayChanged(); + } + void addChild(ActivityStack stack, int position) { if (position == POSITION_BOTTOM) { position = 0; @@ -356,7 +373,8 @@ class ActivityDisplay extends ConfigurationContainer<ActivityStack> return getOrCreateStack(windowingMode, activityType, onTop); } - private int getNextStackId() { + @VisibleForTesting + int getNextStackId() { return sNextFreeStackId++; } @@ -1021,6 +1039,12 @@ class ActivityDisplay extends ConfigurationContainer<ActivityStack> releaseSelfIfNeeded(); mSupervisor.getKeyguardController().onDisplayRemoved(mDisplayId); + + if (!mAllSleepTokens.isEmpty()) { + mSupervisor.mSleepTokens.removeAll(mAllSleepTokens); + mAllSleepTokens.clear(); + mSupervisor.mService.updateSleepIfNeededLocked(); + } } private void releaseSelfIfNeeded() { diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index e8c6365b38cb..b06320a9c4a1 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -129,7 +129,6 @@ import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM; import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME; import static com.android.server.am.ActivityTaskManagerDebugConfig.DEBUG_CLEANUP; import static com.android.server.am.ActivityTaskManagerDebugConfig.DEBUG_CONFIGURATION; -import static com.android.server.am.ActivityTaskManagerDebugConfig.DEBUG_LOCKTASK; import static com.android.server.am.ActivityTaskManagerDebugConfig.DEBUG_SWITCH; import static com.android.server.am.ActivityTaskManagerDebugConfig.POSTFIX_CONFIGURATION; import static com.android.server.am.ActivityTaskManagerDebugConfig.POSTFIX_LOCKTASK; @@ -5139,6 +5138,14 @@ public class ActivityManagerService extends IActivityManager.Stub @Override public boolean isAppForeground(int uid) { + int callerUid = Binder.getCallingUid(); + if (UserHandle.isCore(callerUid) || callerUid == uid) { + return isAppForegroundInternal(uid); + } + return false; + } + + private boolean isAppForegroundInternal(int uid) { synchronized (this) { UidRecord uidRec = mActiveUids.get(uid); if (uidRec == null || uidRec.idle) { @@ -8380,7 +8387,7 @@ public class ActivityManagerService extends IActivityManager.Stub throw e.rethrowAsRuntimeException(); } } - mAtmInternal.startHomeActivity(currentUserId, "systemReady"); + mAtmInternal.startHomeOnAllDisplays(currentUserId, "systemReady"); mAtmInternal.showSystemReadyErrorDialogsIfNeeded(); diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java index 026c5cc3017d..9492694255c3 100644 --- a/services/core/java/com/android/server/am/ActivityStack.java +++ b/services/core/java/com/android/server/am/ActivityStack.java @@ -48,6 +48,26 @@ import static android.view.WindowManager.TRANSIT_TASK_TO_FRONT; import static com.android.server.am.ActivityDisplay.POSITION_BOTTOM; import static com.android.server.am.ActivityDisplay.POSITION_TOP; +import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_CLEANUP; +import static com.android.server.am.ActivityStack.ActivityState.DESTROYED; +import static com.android.server.am.ActivityStack.ActivityState.DESTROYING; +import static com.android.server.am.ActivityStack.ActivityState.FINISHING; +import static com.android.server.am.ActivityStack.ActivityState.PAUSED; +import static com.android.server.am.ActivityStack.ActivityState.PAUSING; +import static com.android.server.am.ActivityStack.ActivityState.RESUMED; +import static com.android.server.am.ActivityStack.ActivityState.STOPPED; +import static com.android.server.am.ActivityStack.ActivityState.STOPPING; +import static com.android.server.am.ActivityStackProto.BOUNDS; +import static com.android.server.am.ActivityStackProto.CONFIGURATION_CONTAINER; +import static com.android.server.am.ActivityStackProto.DISPLAY_ID; +import static com.android.server.am.ActivityStackProto.FULLSCREEN; +import static com.android.server.am.ActivityStackProto.ID; +import static com.android.server.am.ActivityStackProto.RESUMED_ACTIVITY; +import static com.android.server.am.ActivityStackProto.TASKS; +import static com.android.server.am.ActivityStackSupervisor.FindTaskResult; +import static com.android.server.am.ActivityStackSupervisor.PAUSE_IMMEDIATELY; +import static com.android.server.am.ActivityStackSupervisor.PRESERVE_WINDOWS; +import static com.android.server.am.ActivityStackSupervisor.REMOVE_FROM_RECENTS; import static com.android.server.am.ActivityTaskManagerDebugConfig.DEBUG_ADD_REMOVE; import static com.android.server.am.ActivityTaskManagerDebugConfig.DEBUG_ALL; import static com.android.server.am.ActivityTaskManagerDebugConfig.DEBUG_APP; @@ -66,7 +86,6 @@ import static com.android.server.am.ActivityTaskManagerDebugConfig.DEBUG_USER_LE import static com.android.server.am.ActivityTaskManagerDebugConfig.DEBUG_VISIBILITY; import static com.android.server.am.ActivityTaskManagerDebugConfig.POSTFIX_ADD_REMOVE; import static com.android.server.am.ActivityTaskManagerDebugConfig.POSTFIX_APP; -import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_CLEANUP; import static com.android.server.am.ActivityTaskManagerDebugConfig.POSTFIX_CONTAINERS; import static com.android.server.am.ActivityTaskManagerDebugConfig.POSTFIX_PAUSE; import static com.android.server.am.ActivityTaskManagerDebugConfig.POSTFIX_RELEASE; @@ -81,29 +100,10 @@ import static com.android.server.am.ActivityTaskManagerDebugConfig.POSTFIX_USER_ import static com.android.server.am.ActivityTaskManagerDebugConfig.POSTFIX_VISIBILITY; import static com.android.server.am.ActivityTaskManagerDebugConfig.TAG_ATM; import static com.android.server.am.ActivityTaskManagerDebugConfig.TAG_WITH_CLASS_NAME; +import static com.android.server.am.ActivityTaskManagerService.H.FIRST_ACTIVITY_STACK_MSG; import static com.android.server.am.ActivityTaskManagerService.RELAUNCH_REASON_FREE_RESIZE; import static com.android.server.am.ActivityTaskManagerService.RELAUNCH_REASON_WINDOWING_MODE_RESIZE; -import static com.android.server.am.ActivityStack.ActivityState.DESTROYED; -import static com.android.server.am.ActivityStack.ActivityState.DESTROYING; -import static com.android.server.am.ActivityStack.ActivityState.FINISHING; -import static com.android.server.am.ActivityStack.ActivityState.PAUSED; -import static com.android.server.am.ActivityStack.ActivityState.PAUSING; -import static com.android.server.am.ActivityStack.ActivityState.RESUMED; -import static com.android.server.am.ActivityStack.ActivityState.STOPPED; -import static com.android.server.am.ActivityStack.ActivityState.STOPPING; -import static com.android.server.am.ActivityStackProto.BOUNDS; -import static com.android.server.am.ActivityStackProto.CONFIGURATION_CONTAINER; -import static com.android.server.am.ActivityStackProto.DISPLAY_ID; -import static com.android.server.am.ActivityStackProto.FULLSCREEN; -import static com.android.server.am.ActivityStackProto.ID; -import static com.android.server.am.ActivityStackProto.RESUMED_ACTIVITY; -import static com.android.server.am.ActivityStackProto.TASKS; -import static com.android.server.am.ActivityStackSupervisor.FindTaskResult; -import static com.android.server.am.ActivityStackSupervisor.PAUSE_IMMEDIATELY; -import static com.android.server.am.ActivityStackSupervisor.PRESERVE_WINDOWS; -import static com.android.server.am.ActivityStackSupervisor.REMOVE_FROM_RECENTS; -import static com.android.server.am.ActivityTaskManagerService.H.FIRST_ACTIVITY_STACK_MSG; import static java.lang.Integer.MAX_VALUE; import android.app.Activity; @@ -820,8 +820,13 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai } void positionChildWindowContainerAtBottom(TaskRecord child) { + // If there are other focusable stacks on the display, the z-order of the display should not + // be changed just because a task was placed at the bottom. E.g. if it is moving the topmost + // task to bottom, the next focusable stack on the same display should be focused. + final ActivityStack nextFocusableStack = getDisplay().getNextFocusableStack( + child.getStack(), true /* ignoreCurrent */); mWindowContainerController.positionChildAtBottom(child.getWindowContainerController(), - true /* includingParents */); + nextFocusableStack == null /* includingParents */); } /** @@ -1132,8 +1137,6 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai getDisplay().positionChildAtBottom(this, reason); if (task != null) { - // TODO(b/111541062): We probably don't want to change display z-order to bottom just - // because one of its stacks moved to bottom. insertTaskAtBottom(task); } } @@ -1807,13 +1810,6 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai return false; } - final ActivityRecord top = topRunningActivityLocked(); - if (top == null && isInStackLocked(starting) == null && !isTopStackOnDisplay()) { - // Shouldn't be visible if you don't have any running activities, not starting one, and - // not the top stack on display. - return false; - } - final ActivityDisplay display = getDisplay(); boolean gotSplitScreenStack = false; boolean gotOpaqueSplitScreenPrimary = false; @@ -1822,9 +1818,16 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai final boolean isAssistantType = isActivityTypeAssistant(); for (int i = display.getChildCount() - 1; i >= 0; --i) { final ActivityStack other = display.getChildAt(i); + final boolean hasRunningActivities = other.topRunningActivityLocked() != null; if (other == this) { - // Should be visible if there is no other stack occluding it. - return true; + // Should be visible if there is no other stack occluding it, unless it doesn't + // have any running activities, not starting one and not home stack. + return hasRunningActivities || isInStackLocked(starting) != null + || isActivityTypeHome(); + } + + if (!hasRunningActivities) { + continue; } final int otherWindowingMode = other.getWindowingMode(); @@ -2912,7 +2915,6 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai } private void insertTaskAtBottom(TaskRecord task) { - // Unlike insertTaskAtPosition, this will also position parents of the windowcontroller. mTaskHistory.remove(task); final int position = getAdjustedPositionForTask(task, 0, null); mTaskHistory.add(position, task); diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java index 257a0042b510..87226bf15ecf 100644 --- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java +++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java @@ -44,6 +44,7 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECOND import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; import static android.app.WindowConfiguration.activityTypeToString; import static android.app.WindowConfiguration.windowingModeToString; +import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; import static android.content.pm.ActivityInfo.LAUNCH_SINGLE_INSTANCE; import static android.content.pm.ActivityInfo.LAUNCH_SINGLE_TASK; import static android.content.pm.PackageManager.NOTIFY_PACKAGE_USE_ACTIVITY; @@ -115,8 +116,8 @@ import android.app.ActivityManager.RunningTaskInfo; import android.app.ActivityManager.StackInfo; import android.app.ActivityManagerInternal; import android.app.ActivityOptions; +import android.app.AppGlobals; import android.app.AppOpsManager; -import android.app.IApplicationThread; import android.app.ProfilerInfo; import android.app.ResultInfo; import android.app.WaitResult; @@ -146,6 +147,7 @@ import android.hardware.power.V1_0.PowerHint; import android.os.Binder; import android.os.Bundle; import android.os.Debug; +import android.os.FactoryTest; import android.os.Handler; import android.os.IBinder; import android.os.Looper; @@ -226,9 +228,6 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D static final int RESUME_TOP_ACTIVITY_MSG = FIRST_SUPERVISOR_STACK_MSG + 2; static final int SLEEP_TIMEOUT_MSG = FIRST_SUPERVISOR_STACK_MSG + 3; static final int LAUNCH_TIMEOUT_MSG = FIRST_SUPERVISOR_STACK_MSG + 4; - static final int HANDLE_DISPLAY_ADDED = FIRST_SUPERVISOR_STACK_MSG + 5; - static final int HANDLE_DISPLAY_CHANGED = FIRST_SUPERVISOR_STACK_MSG + 6; - static final int HANDLE_DISPLAY_REMOVED = FIRST_SUPERVISOR_STACK_MSG + 7; static final int LAUNCH_TASK_BEHIND_COMPLETE = FIRST_SUPERVISOR_STACK_MSG + 12; static final int REPORT_MULTI_WINDOW_MODE_CHANGED_MSG = FIRST_SUPERVISOR_STACK_MSG + 14; static final int REPORT_PIP_MODE_CHANGED_MSG = FIRST_SUPERVISOR_STACK_MSG + 15; @@ -675,7 +674,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D setWindowContainerController(new RootWindowContainerController(this)); mDisplayManager = mService.mContext.getSystemService(DisplayManager.class); - mDisplayManager.registerDisplayListener(this, null); + mDisplayManager.registerDisplayListener(this, mHandler); mDisplayManagerInternal = LocalServices.getService(DisplayManagerInternal.class); final Display[] displays = mDisplayManager.getDisplays(); @@ -789,10 +788,24 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D r.moveFocusableActivityToTop(myReason); return resumeFocusedStacksTopActivitiesLocked(r.getStack(), prev, null); } - return mService.startHomeActivityLocked(mCurrentUser, myReason, displayId); + return startHomeOnDisplay(mCurrentUser, myReason, displayId); } - boolean canStartHomeOnDisplay(ActivityInfo homeActivity, int displayId) { + boolean canStartHomeOnDisplay(ActivityInfo homeInfo, int displayId) { + if (mService.mFactoryTest == FactoryTest.FACTORY_TEST_LOW_LEVEL + && mService.mTopAction == null) { + // We are running in factory test mode, but unable to find the factory test app, so + // just sit around displaying the error message and don't try to start anything. + return false; + } + + final WindowProcessController app = + mService.getProcessController(homeInfo.processName, homeInfo.applicationInfo.uid); + if (app != null && app.isInstrumenting()) { + // Don't do this if the home app is currently being instrumented. + return false; + } + if (displayId == DEFAULT_DISPLAY || (displayId != INVALID_DISPLAY && displayId == mService.mVr2dDisplayId)) { // No restrictions to default display or vr 2d display. @@ -805,8 +818,8 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D return false; } - final boolean supportMultipleInstance = homeActivity.launchMode != LAUNCH_SINGLE_TASK - && homeActivity.launchMode != LAUNCH_SINGLE_INSTANCE; + final boolean supportMultipleInstance = homeInfo.launchMode != LAUNCH_SINGLE_TASK + && homeInfo.launchMode != LAUNCH_SINGLE_INSTANCE; if (!supportMultipleInstance) { // Can't launch home on other displays if it requested to be single instance. return false; @@ -1040,12 +1053,14 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) { final ActivityDisplay display = mActivityDisplays.get(displayNdx); for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) { + // We cannot only check the top stack on each display since there might have + // always-on-top stacks (e.g. pinned stack). final ActivityStack stack = display.getChildAt(stackNdx); - if (!isTopDisplayFocusedStack(stack) || stack.numActivities() == 0) { + if (stack.numActivities() == 0) { continue; } final ActivityRecord resumedActivity = stack.getResumedActivity(); - if (resumedActivity == null || !resumedActivity.idle) { + if (resumedActivity != null && !resumedActivity.idle) { if (DEBUG_STATES) Slog.d(TAG_STATES, "allResumedActivitiesIdle: stack=" + stack.mStackId + " " + resumedActivity + " not idle"); return false; @@ -1898,7 +1913,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D } /** - * Called when the frontmost task is idle. + * Called when all resumed tasks/stacks are idle. * @return the state of mService.mAm.mBooting before this was called. */ @GuardedBy("mService") @@ -1953,7 +1968,9 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D r.idle = true; //Slog.i(TAG, "IDLE: mBooted=" + mBooted + ", fromTimeout=" + fromTimeout); - if (isTopDisplayFocusedStack(r.getStack()) || fromTimeout) { + + // Make sure we can finish booting when all resumed activities are idle. + if ((!mService.isBooted() && allResumedActivitiesIdle()) || fromTimeout) { booting = checkFinishBootingLocked(); } @@ -4108,25 +4125,37 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D @Override public void onDisplayAdded(int displayId) { if (DEBUG_STACK) Slog.v(TAG, "Display added displayId=" + displayId); - mHandler.sendMessage(mHandler.obtainMessage(HANDLE_DISPLAY_ADDED, displayId, 0)); + synchronized (mService.mGlobalLock) { + getActivityDisplayOrCreateLocked(displayId); + startHomeOnDisplay(mCurrentUser, "displayAdded", displayId); + } } @Override public void onDisplayRemoved(int displayId) { if (DEBUG_STACK) Slog.v(TAG, "Display removed displayId=" + displayId); - mHandler.sendMessage(mHandler.obtainMessage(HANDLE_DISPLAY_REMOVED, displayId, 0)); + if (displayId == DEFAULT_DISPLAY) { + throw new IllegalArgumentException("Can't remove the primary display."); + } + + synchronized (mService.mGlobalLock) { + final ActivityDisplay activityDisplay = getActivityDisplay(displayId); + if (activityDisplay == null) { + return; + } + + activityDisplay.remove(); + } } @Override public void onDisplayChanged(int displayId) { if (DEBUG_STACK) Slog.v(TAG, "Display changed displayId=" + displayId); - mHandler.sendMessage(mHandler.obtainMessage(HANDLE_DISPLAY_CHANGED, displayId, 0)); - } - - private void handleDisplayAdded(int displayId) { synchronized (mService.mGlobalLock) { - getActivityDisplayOrCreateLocked(displayId); - mService.startHomeActivityLocked(mCurrentUser, "displayAdded", displayId); + final ActivityDisplay activityDisplay = getActivityDisplay(displayId); + if (activityDisplay != null) { + activityDisplay.onDisplayChanged(); + } } } @@ -4173,10 +4202,79 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D // The display hasn't been added to ActivityManager yet, create a new record now. activityDisplay = new ActivityDisplay(this, display); addChild(activityDisplay, ActivityDisplay.POSITION_BOTTOM); - mWindowManager.onDisplayAdded(displayId); return activityDisplay; } + boolean startHomeOnAllDisplays(int userId, String reason) { + boolean homeStarted = false; + for (int i = mActivityDisplays.size() - 1; i >= 0; i--) { + final int displayId = mActivityDisplays.get(i).mDisplayId; + homeStarted |= startHomeOnDisplay(userId, reason, displayId); + } + return homeStarted; + } + + /** + * This starts home activity on displays that can have system decorations and only if the + * home activity can have multiple instances. + */ + boolean startHomeOnDisplay(int userId, String reason, int displayId) { + final Intent homeIntent = mService.getHomeIntent(); + final ActivityInfo aInfo = resolveHomeActivity(userId, homeIntent); + if (aInfo == null) { + return false; + } + + if (!canStartHomeOnDisplay(aInfo, displayId)) { + return false; + } + + // Update the reason for ANR debugging to verify if the user activity is the one that + // actually launched. + final String myReason = reason + ":" + userId + ":" + UserHandle.getUserId( + aInfo.applicationInfo.uid); + mService.getActivityStartController().startHomeActivity(homeIntent, aInfo, myReason, + displayId); + return true; + } + + /** + * This resolves the home activity info and updates the home component of the given intent. + * @return the home activity info if any. + */ + private ActivityInfo resolveHomeActivity(int userId, Intent homeIntent) { + final int flags = ActivityManagerService.STOCK_PM_FLAGS; + final ComponentName comp = homeIntent.getComponent(); + ActivityInfo aInfo = null; + try { + if (comp != null) { + // Factory test. + aInfo = AppGlobals.getPackageManager().getActivityInfo(comp, flags, userId); + } else { + final String resolvedType = + homeIntent.resolveTypeIfNeeded(mService.mContext.getContentResolver()); + final ResolveInfo info = AppGlobals.getPackageManager() + .resolveIntent(homeIntent, resolvedType, flags, userId); + if (info != null) { + aInfo = info.activityInfo; + } + } + } catch (RemoteException e) { + // ignore + } + + if (aInfo == null) { + Slog.wtf(TAG, "No home screen found for " + homeIntent, new Throwable()); + return null; + } + + homeIntent.setComponent(new ComponentName(aInfo.applicationInfo.packageName, aInfo.name)); + aInfo = new ActivityInfo(aInfo); + aInfo.applicationInfo = mService.getAppInfoForUser(aInfo.applicationInfo, userId); + homeIntent.setFlags(homeIntent.getFlags() | FLAG_ACTIVITY_NEW_TASK); + return aInfo; + } + @VisibleForTesting void addChild(ActivityDisplay activityDisplay, int position) { positionChildAt(activityDisplay, position); @@ -4199,47 +4297,6 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D mDefaultMinSizeOfResizeableTaskDp = (int) (minimalSize / dm.density); } - private void handleDisplayRemoved(int displayId) { - if (displayId == DEFAULT_DISPLAY) { - throw new IllegalArgumentException("Can't remove the primary display."); - } - - synchronized (mService.mGlobalLock) { - final ActivityDisplay activityDisplay = getActivityDisplay(displayId); - if (activityDisplay == null) { - return; - } - - activityDisplay.remove(); - - releaseSleepTokens(activityDisplay); - } - } - - private void handleDisplayChanged(int displayId) { - synchronized (mService.mGlobalLock) { - ActivityDisplay activityDisplay = getActivityDisplay(displayId); - // TODO: The following code block should be moved into {@link ActivityDisplay}. - if (activityDisplay != null) { - // The window policy is responsible for stopping activities on the default display - if (displayId != Display.DEFAULT_DISPLAY) { - int displayState = activityDisplay.mDisplay.getState(); - if (displayState == Display.STATE_OFF && activityDisplay.mOffToken == null) { - activityDisplay.mOffToken = - mService.acquireSleepToken("Display-off", displayId); - } else if (displayState == Display.STATE_ON - && activityDisplay.mOffToken != null) { - activityDisplay.mOffToken.release(); - activityDisplay.mOffToken = null; - } - } - - activityDisplay.updateBounds(); - } - mWindowManager.onDisplayChanged(displayId); - } - } - SleepToken createSleepTokenLocked(String tag, int displayId) { final ActivityDisplay display = getActivityDisplay(displayId); if (display == null) { @@ -4264,18 +4321,6 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D } } - private void releaseSleepTokens(ActivityDisplay display) { - if (display.mAllSleepTokens.isEmpty()) { - return; - } - for (SleepToken token : display.mAllSleepTokens) { - mSleepTokens.remove(token); - } - display.mAllSleepTokens.clear(); - - mService.updateSleepIfNeededLocked(); - } - private StackInfo getStackInfo(ActivityStack stack) { final int displayId = stack.mDisplayId; final ActivityDisplay display = getActivityDisplay(displayId); @@ -4597,15 +4642,6 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D } } } break; - case HANDLE_DISPLAY_ADDED: { - handleDisplayAdded(msg.arg1); - } break; - case HANDLE_DISPLAY_CHANGED: { - handleDisplayChanged(msg.arg1); - } break; - case HANDLE_DISPLAY_REMOVED: { - handleDisplayRemoved(msg.arg1); - } break; case LAUNCH_TASK_BEHIND_COMPLETE: { synchronized (mService.mGlobalLock) { ActivityRecord r = ActivityRecord.forTokenLocked((IBinder) msg.obj); diff --git a/services/core/java/com/android/server/am/ActivityStartController.java b/services/core/java/com/android/server/am/ActivityStartController.java index 20d5ab201307..3151ec9c1b83 100644 --- a/services/core/java/com/android/server/am/ActivityStartController.java +++ b/services/core/java/com/android/server/am/ActivityStartController.java @@ -20,8 +20,8 @@ import static android.app.ActivityManager.START_SUCCESS; import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; - import static android.os.FactoryTest.FACTORY_TEST_LOW_LEVEL; + import static com.android.server.am.ActivityTaskManagerDebugConfig.TAG_ATM; import static com.android.server.am.ActivityTaskManagerDebugConfig.TAG_WITH_CLASS_NAME; @@ -35,7 +35,6 @@ import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.os.Binder; -import android.os.FactoryTest; import android.os.Handler; import android.os.IBinder; import android.os.Looper; @@ -167,10 +166,6 @@ public class ActivityStartController { } void startHomeActivity(Intent intent, ActivityInfo aInfo, String reason, int displayId) { - if (!mSupervisor.canStartHomeOnDisplay(aInfo, displayId)) { - return; - } - final ActivityOptions options = ActivityOptions.makeBasic(); options.setLaunchWindowingMode(WINDOWING_MODE_FULLSCREEN); options.setLaunchActivityType(ACTIVITY_TYPE_HOME); diff --git a/services/core/java/com/android/server/am/ActivityTaskManagerService.java b/services/core/java/com/android/server/am/ActivityTaskManagerService.java index 5b0a4a9b0596..5e3c1da2310b 100644 --- a/services/core/java/com/android/server/am/ActivityTaskManagerService.java +++ b/services/core/java/com/android/server/am/ActivityTaskManagerService.java @@ -85,13 +85,10 @@ import static com.android.server.am.ActivityManagerServiceDumpProcessesProto.HEA import static com.android.server.am.ActivityManagerServiceDumpProcessesProto.HOME_PROC; import static com.android.server.am.ActivityManagerServiceDumpProcessesProto.LAUNCHING_ACTIVITY; import static com.android.server.am.ActivityManagerServiceDumpProcessesProto.PREVIOUS_PROC; -import static com.android.server.am.ActivityManagerServiceDumpProcessesProto - .PREVIOUS_PROC_VISIBLE_TIME_MS; +import static com.android.server.am.ActivityManagerServiceDumpProcessesProto.PREVIOUS_PROC_VISIBLE_TIME_MS; import static com.android.server.am.ActivityManagerServiceDumpProcessesProto.SCREEN_COMPAT_PACKAGES; -import static com.android.server.am.ActivityManagerServiceDumpProcessesProto.ScreenCompatPackage - .MODE; -import static com.android.server.am.ActivityManagerServiceDumpProcessesProto.ScreenCompatPackage - .PACKAGE; +import static com.android.server.am.ActivityManagerServiceDumpProcessesProto.ScreenCompatPackage.MODE; +import static com.android.server.am.ActivityManagerServiceDumpProcessesProto.ScreenCompatPackage.PACKAGE; import static com.android.server.am.ActivityStack.REMOVE_TASK_MODE_DESTROYING; import static com.android.server.am.ActivityStackSupervisor.DEFER_RESUME; import static com.android.server.am.ActivityStackSupervisor.MATCH_TASK_IN_STACKS_ONLY; @@ -5353,65 +5350,6 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { return intent; } - /** - * This starts home activity on displays that can have system decorations and only if the - * home activity can have multiple instances. - */ - boolean startHomeActivityLocked(int userId, String reason, int displayId) { - if (mFactoryTest == FactoryTest.FACTORY_TEST_LOW_LEVEL && mTopAction == null) { - // We are running in factory test mode, but unable to find the factory test app, so just - // sit around displaying the error message and don't try to start anything. - return false; - } - - final Intent intent = getHomeIntent(); - ActivityInfo aInfo = resolveActivityInfo(intent, STOCK_PM_FLAGS, userId); - if (aInfo != null) { - intent.setComponent(new ComponentName(aInfo.applicationInfo.packageName, aInfo.name)); - // Don't do this if the home app is currently being instrumented. - aInfo = new ActivityInfo(aInfo); - aInfo.applicationInfo = getAppInfoForUser(aInfo.applicationInfo, userId); - WindowProcessController app = - getProcessController(aInfo.processName, aInfo.applicationInfo.uid); - if (app == null || !app.isInstrumenting()) { - intent.setFlags(intent.getFlags() | FLAG_ACTIVITY_NEW_TASK); - final int resolvedUserId = UserHandle.getUserId(aInfo.applicationInfo.uid); - // For ANR debugging to verify if the user activity is the one that actually - // launched. - final String myReason = reason + ":" + userId + ":" + resolvedUserId; - getActivityStartController().startHomeActivity(intent, aInfo, myReason, displayId); - } - } else { - Slog.wtf(TAG, "No home screen found for " + intent, new Throwable()); - } - - return true; - } - - private ActivityInfo resolveActivityInfo(Intent intent, int flags, int userId) { - ActivityInfo ai = null; - final ComponentName comp = intent.getComponent(); - try { - if (comp != null) { - // Factory test. - ai = AppGlobals.getPackageManager().getActivityInfo(comp, flags, userId); - } else { - ResolveInfo info = AppGlobals.getPackageManager().resolveIntent( - intent, - intent.resolveTypeIfNeeded(mContext.getContentResolver()), - flags, userId); - - if (info != null) { - ai = info.activityInfo; - } - } - } catch (RemoteException e) { - // ignore - } - - return ai; - } - ApplicationInfo getAppInfoForUser(ApplicationInfo info, int userId) { if (info == null) return null; ApplicationInfo newInfo = new ApplicationInfo(info); @@ -6127,7 +6065,14 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { @Override public boolean startHomeActivity(int userId, String reason) { synchronized (mGlobalLock) { - return startHomeActivityLocked(userId, reason, DEFAULT_DISPLAY); + return mStackSupervisor.startHomeOnDisplay(userId, reason, DEFAULT_DISPLAY); + } + } + + @Override + public boolean startHomeOnAllDisplays(int userId, String reason) { + synchronized (mGlobalLock) { + return mStackSupervisor.startHomeOnAllDisplays(userId, reason); } } diff --git a/services/core/java/com/android/server/am/TaskLaunchParamsModifier.java b/services/core/java/com/android/server/am/TaskLaunchParamsModifier.java index 111adecb933f..c15b4acd971d 100644 --- a/services/core/java/com/android/server/am/TaskLaunchParamsModifier.java +++ b/services/core/java/com/android/server/am/TaskLaunchParamsModifier.java @@ -135,7 +135,7 @@ class TaskLaunchParamsModifier implements LaunchParamsModifier { } // STEP 1: Determine the display to launch the activity/task. - final int displayId = getPreferredLaunchDisplay(options, source, currentParams); + final int displayId = getPreferredLaunchDisplay(task, options, source, currentParams); outParams.mPreferredDisplayId = displayId; ActivityDisplay display = mSupervisor.getActivityDisplay(displayId); if (DEBUG) { @@ -268,8 +268,8 @@ class TaskLaunchParamsModifier implements LaunchParamsModifier { return RESULT_CONTINUE; } - private int getPreferredLaunchDisplay(@Nullable ActivityOptions options, - ActivityRecord source, LaunchParams currentParams) { + private int getPreferredLaunchDisplay(@Nullable TaskRecord task, + @Nullable ActivityOptions options, ActivityRecord source, LaunchParams currentParams) { int displayId = INVALID_DISPLAY; final int optionLaunchId = options != null ? options.getLaunchDisplayId() : INVALID_DISPLAY; if (optionLaunchId != INVALID_DISPLAY) { @@ -283,6 +283,13 @@ class TaskLaunchParamsModifier implements LaunchParamsModifier { displayId = sourceDisplayId; } + ActivityStack stack = + (displayId == INVALID_DISPLAY && task != null) ? task.getStack() : null; + if (stack != null) { + if (DEBUG) appendLog("display-from-task=" + stack.mDisplayId); + displayId = stack.mDisplayId; + } + if (displayId != INVALID_DISPLAY && mSupervisor.getActivityDisplay(displayId) == null) { displayId = INVALID_DISPLAY; } diff --git a/services/core/java/com/android/server/connectivity/PermissionMonitor.java b/services/core/java/com/android/server/connectivity/PermissionMonitor.java index 7b8571c131b6..deaa33485170 100644 --- a/services/core/java/com/android/server/connectivity/PermissionMonitor.java +++ b/services/core/java/com/android/server/connectivity/PermissionMonitor.java @@ -23,6 +23,8 @@ import static android.Manifest.permission.NETWORK_STACK; import static android.content.pm.ApplicationInfo.FLAG_SYSTEM; import static android.content.pm.ApplicationInfo.FLAG_UPDATED_SYSTEM_APP; import static android.content.pm.PackageManager.GET_PERMISSIONS; +import static android.os.Process.INVALID_UID; +import static android.os.Process.SYSTEM_UID; import android.annotation.NonNull; import android.content.BroadcastReceiver; @@ -64,6 +66,7 @@ public class PermissionMonitor { private static final boolean DBG = true; private static final Boolean SYSTEM = Boolean.TRUE; private static final Boolean NETWORK = Boolean.FALSE; + private static final int VERSION_Q = Build.VERSION_CODES.Q; private final Context mContext; private final PackageManager mPackageManager; @@ -87,7 +90,7 @@ public class PermissionMonitor { public void onReceive(Context context, Intent intent) { String action = intent.getAction(); int user = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL); - int appUid = intent.getIntExtra(Intent.EXTRA_UID, -1); + int appUid = intent.getIntExtra(Intent.EXTRA_UID, INVALID_UID); Uri appData = intent.getData(); String appName = appData != null ? appData.getSchemeSpecificPart() : null; @@ -127,7 +130,7 @@ public class PermissionMonitor { } for (PackageInfo app : apps) { - int uid = app.applicationInfo != null ? app.applicationInfo.uid : -1; + int uid = app.applicationInfo != null ? app.applicationInfo.uid : INVALID_UID; if (uid < 0) { continue; } @@ -162,6 +165,11 @@ public class PermissionMonitor { } @VisibleForTesting + int getDeviceFirstSdkInt() { + return Build.VERSION.FIRST_SDK_INT; + } + + @VisibleForTesting boolean hasPermission(PackageInfo app, String permission) { if (app.requestedPermissions != null) { for (String p : app.requestedPermissions) { @@ -180,10 +188,17 @@ public class PermissionMonitor { private boolean hasRestrictedNetworkPermission(PackageInfo app) { // TODO : remove this check in the future(b/31479477). All apps should just // request the appropriate permission for their use case since android Q. - if (app.applicationInfo != null - && app.applicationInfo.targetSdkVersion < Build.VERSION_CODES.Q - && isVendorApp(app.applicationInfo)) { - return true; + if (app.applicationInfo != null) { + // Backward compatibility for b/114245686, on devices that launched before Q daemons + // and apps running as the system UID are exempted from this check. + if (app.applicationInfo.uid == SYSTEM_UID && getDeviceFirstSdkInt() < VERSION_Q) { + return true; + } + + if (app.applicationInfo.targetSdkVersion < VERSION_Q + && isVendorApp(app.applicationInfo)) { + return true; + } } return hasPermission(app, CONNECTIVITY_INTERNAL) || hasPermission(app, CONNECTIVITY_USE_RESTRICTED_NETWORKS); diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java index e3a1a913bdad..9d5d65d87062 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java @@ -136,6 +136,7 @@ import com.android.internal.content.PackageMonitor; import com.android.internal.inputmethod.IInputContentUriToken; import com.android.internal.inputmethod.IInputMethodPrivilegedOperations; import com.android.internal.inputmethod.InputMethodDebug; +import com.android.internal.inputmethod.StartInputFlags; import com.android.internal.inputmethod.StartInputReason; import com.android.internal.inputmethod.UnbindReason; import com.android.internal.messages.nano.SystemMessageProto.SystemMessage; @@ -1916,8 +1917,8 @@ public class InputMethodManagerService extends IInputMethodManager.Stub @GuardedBy("mMethodMap") @NonNull InputBindResult startInputUncheckedLocked(@NonNull ClientState cs, IInputContext inputContext, - @MissingMethodFlags int missingMethods, @NonNull EditorInfo attribute, int controlFlags, - @StartInputReason int startInputReason) { + @MissingMethodFlags int missingMethods, @NonNull EditorInfo attribute, + @StartInputFlags int startInputFlags, @StartInputReason int startInputReason) { // If no method is currently selected, do nothing. if (mCurMethodId == null) { return InputBindResult.NO_IME; @@ -1979,7 +1980,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub // Fast case: if we are already connected to the input method, // then just return it. return attachNewInputLocked(startInputReason, - (controlFlags&InputMethodManager.CONTROL_START_INITIAL) != 0); + (startInputFlags & StartInputFlags.INITIAL_CONNECTION) != 0); } if (mHaveConnection) { if (mCurMethod != null) { @@ -2794,15 +2795,15 @@ public class InputMethodManagerService extends IInputMethodManager.Stub @Override public InputBindResult startInputOrWindowGainedFocus( @StartInputReason int startInputReason, IInputMethodClient client, IBinder windowToken, - int controlFlags, @SoftInputModeFlags int softInputMode, int windowFlags, - @Nullable EditorInfo attribute, IInputContext inputContext, + @StartInputFlags int startInputFlags, @SoftInputModeFlags int softInputMode, + int windowFlags, @Nullable EditorInfo attribute, IInputContext inputContext, @MissingMethodFlags int missingMethods, int unverifiedTargetSdkVersion) { if (windowToken == null) { Slog.e(TAG, "windowToken cannot be null."); return InputBindResult.NULL; } final InputBindResult result = startInputOrWindowGainedFocusInternal(startInputReason, - client, windowToken, controlFlags, softInputMode, windowFlags, attribute, + client, windowToken, startInputFlags, softInputMode, windowFlags, attribute, inputContext, missingMethods, unverifiedTargetSdkVersion); if (result == null) { // This must never happen, but just in case. @@ -2818,9 +2819,10 @@ public class InputMethodManagerService extends IInputMethodManager.Stub @NonNull private InputBindResult startInputOrWindowGainedFocusInternal( @StartInputReason int startInputReason, IInputMethodClient client, - @NonNull IBinder windowToken, int controlFlags, @SoftInputModeFlags int softInputMode, - int windowFlags, EditorInfo attribute, IInputContext inputContext, - @MissingMethodFlags int missingMethods, int unverifiedTargetSdkVersion) { + @NonNull IBinder windowToken, @StartInputFlags int startInputFlags, + @SoftInputModeFlags int softInputMode, int windowFlags, EditorInfo attribute, + IInputContext inputContext, @MissingMethodFlags int missingMethods, + int unverifiedTargetSdkVersion) { // Needs to check the validity before clearing calling identity final boolean calledFromValidUser = calledFromValidUser(); InputBindResult res = null; @@ -2836,7 +2838,8 @@ public class InputMethodManagerService extends IInputMethodManager.Stub + " missingMethods=" + InputConnectionInspector.getMissingMethodFlagsAsString(missingMethods) + " attribute=" + attribute - + " controlFlags=#" + Integer.toHexString(controlFlags) + + " startInputFlags=" + + InputMethodDebug.startInputFlagsToString(startInputFlags) + " softInputMode=" + InputMethodDebug.softInputModeToString(softInputMode) + " windowFlags=#" + Integer.toHexString(windowFlags) + " unverifiedTargetSdkVersion=" + unverifiedTargetSdkVersion); @@ -2882,7 +2885,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } if (attribute != null) { return startInputUncheckedLocked(cs, inputContext, missingMethods, - attribute, controlFlags, startInputReason); + attribute, startInputFlags, startInputReason); } return new InputBindResult( InputBindResult.ResultCode.SUCCESS_REPORT_WINDOW_FOCUS_ONLY, @@ -2905,7 +2908,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub || mRes.getConfiguration().isLayoutSizeAtLeast( Configuration.SCREENLAYOUT_SIZE_LARGE); final boolean isTextEditor = - (controlFlags&InputMethodManager.CONTROL_WINDOW_IS_TEXT_EDITOR) != 0; + (startInputFlags & StartInputFlags.IS_TEXT_EDITOR) != 0; // We want to start input before showing the IME, but after closing // it. We want to do this after closing it to help the IME disappear @@ -2943,8 +2946,8 @@ public class InputMethodManagerService extends IInputMethodManager.Stub // is more room for the target window + IME. if (DEBUG) Slog.v(TAG, "Unspecified window will show input"); if (attribute != null) { - res = startInputUncheckedLocked(cs, inputContext, - missingMethods, attribute, controlFlags, startInputReason); + res = startInputUncheckedLocked(cs, inputContext, missingMethods, + attribute, startInputFlags, startInputReason); didStart = true; } showCurrentInputLocked(InputMethodManager.SHOW_IMPLICIT, null); @@ -2969,10 +2972,10 @@ public class InputMethodManagerService extends IInputMethodManager.Stub WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) != 0) { if (DEBUG) Slog.v(TAG, "Window asks to show input going forward"); if (InputMethodUtils.isSoftInputModeStateVisibleAllowed( - unverifiedTargetSdkVersion, controlFlags)) { + unverifiedTargetSdkVersion, startInputFlags)) { if (attribute != null) { res = startInputUncheckedLocked(cs, inputContext, - missingMethods, attribute, controlFlags, + missingMethods, attribute, startInputFlags, startInputReason); didStart = true; } @@ -2987,10 +2990,10 @@ public class InputMethodManagerService extends IInputMethodManager.Stub case WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE: if (DEBUG) Slog.v(TAG, "Window asks to always show input"); if (InputMethodUtils.isSoftInputModeStateVisibleAllowed( - unverifiedTargetSdkVersion, controlFlags)) { + unverifiedTargetSdkVersion, startInputFlags)) { if (attribute != null) { res = startInputUncheckedLocked(cs, inputContext, missingMethods, - attribute, controlFlags, startInputReason); + attribute, startInputFlags, startInputReason); didStart = true; } showCurrentInputLocked(InputMethodManager.SHOW_IMPLICIT, null); @@ -3005,11 +3008,10 @@ public class InputMethodManagerService extends IInputMethodManager.Stub if (!didStart) { if (attribute != null) { if (!DebugFlags.FLAG_OPTIMIZE_START_INPUT.value() - || (controlFlags - & InputMethodManager.CONTROL_WINDOW_IS_TEXT_EDITOR) != 0) { + || (startInputFlags & StartInputFlags.IS_TEXT_EDITOR) != 0) { res = startInputUncheckedLocked(cs, inputContext, missingMethods, attribute, - controlFlags, startInputReason); + startInputFlags, startInputReason); } else { res = InputBindResult.NO_EDITOR; } diff --git a/services/core/java/com/android/server/inputmethod/InputMethodUtils.java b/services/core/java/com/android/server/inputmethod/InputMethodUtils.java index e951b27e85c6..154e8b310128 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodUtils.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodUtils.java @@ -16,9 +16,6 @@ package com.android.server.inputmethod; -import static android.view.inputmethod.InputMethodManager.CONTROL_WINDOW_IS_TEXT_EDITOR; -import static android.view.inputmethod.InputMethodManager.CONTROL_WINDOW_VIEW_HAS_FOCUS; - import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.UserIdInt; @@ -44,6 +41,7 @@ import android.view.textservice.TextServicesManager; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.inputmethod.StartInputFlags; import java.util.ArrayList; import java.util.Arrays; @@ -1299,15 +1297,15 @@ final class InputMethodUtils { } public static boolean isSoftInputModeStateVisibleAllowed( - int targetSdkVersion, int controlFlags) { + int targetSdkVersion, @StartInputFlags int startInputFlags) { if (targetSdkVersion < Build.VERSION_CODES.P) { // for compatibility. return true; } - if ((controlFlags & CONTROL_WINDOW_VIEW_HAS_FOCUS) == 0) { + if ((startInputFlags & StartInputFlags.VIEW_HAS_FOCUS) == 0) { return false; } - if ((controlFlags & CONTROL_WINDOW_IS_TEXT_EDITOR) == 0) { + if ((startInputFlags & StartInputFlags.IS_TEXT_EDITOR) == 0) { return false; } return true; diff --git a/services/core/java/com/android/server/location/ContextHubClientBroker.java b/services/core/java/com/android/server/location/ContextHubClientBroker.java index 1d0ab8fc7641..60f70c70b8e0 100644 --- a/services/core/java/com/android/server/location/ContextHubClientBroker.java +++ b/services/core/java/com/android/server/location/ContextHubClientBroker.java @@ -16,12 +16,15 @@ package com.android.server.location; +import android.Manifest; import android.app.PendingIntent; import android.content.Context; +import android.content.Intent; import android.hardware.contexthub.V1_0.ContextHubMsg; import android.hardware.contexthub.V1_0.IContexthub; import android.hardware.contexthub.V1_0.Result; import android.hardware.location.ContextHubInfo; +import android.hardware.location.ContextHubManager; import android.hardware.location.ContextHubTransaction; import android.hardware.location.IContextHubClient; import android.hardware.location.IContextHubClientCallback; @@ -30,7 +33,7 @@ import android.os.IBinder; import android.os.RemoteException; import android.util.Log; -import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.Supplier; /** * A class that acts as a broker for the ContextHubClient, which handles messaging and life-cycle @@ -69,14 +72,15 @@ public class ContextHubClientBroker extends IContextHubClient.Stub private final short mHostEndPointId; /* - * The remote callback interface for this client. + * The remote callback interface for this client. This will be set to null whenever the + * client connection is closed (either explicitly or via binder death). */ - private final IContextHubClientCallback mCallbackInterface; + private IContextHubClientCallback mCallbackInterface = null; /* - * false if the connection has been closed by the client, true otherwise. + * True if the client is still registered with the Context Hub Service, false otherwise. */ - private final AtomicBoolean mConnectionOpen = new AtomicBoolean(true); + private boolean mRegistered = true; /* * Internal interface used to invoke client callbacks. @@ -85,6 +89,74 @@ public class ContextHubClientBroker extends IContextHubClient.Stub void accept(IContextHubClientCallback callback) throws RemoteException; } + /* + * The PendingIntent registered with this client. + */ + private final PendingIntentRequest mPendingIntentRequest = new PendingIntentRequest(); + + /* + * Helper class to manage registered PendingIntent requests from the client. + */ + private class PendingIntentRequest { + /* + * The PendingIntent object to request, null if there is no open request. + */ + private PendingIntent mPendingIntent; + + /* + * The ID of the nanoapp the request is for, invalid if there is no open request. + */ + private long mNanoAppId; + + PendingIntentRequest() {} + + PendingIntentRequest(PendingIntent pendingIntent, long nanoAppId) { + mPendingIntent = pendingIntent; + mNanoAppId = nanoAppId; + } + + public long getNanoAppId() { + return mNanoAppId; + } + + public PendingIntent getPendingIntent() { + return mPendingIntent; + } + + public boolean hasPendingIntent() { + return mPendingIntent != null; + } + + public void clear() { + mPendingIntent = null; + } + + public boolean register(PendingIntent pendingIntent, long nanoAppId) { + boolean success = false; + if (hasPendingIntent()) { + Log.e(TAG, "Failed to register PendingIntent: registered PendingIntent exists"); + } else { + mNanoAppId = nanoAppId; + mPendingIntent = pendingIntent; + success = true; + } + + return success; + } + + public boolean unregister(PendingIntent pendingIntent) { + boolean success = false; + if (!hasPendingIntent() || !mPendingIntent.equals(pendingIntent)) { + Log.e(TAG, "Failed to unregister PendingIntent: PendingIntent is not registered"); + } else { + mPendingIntent = null; + success = true; + } + + return success; + } + } + /* package */ ContextHubClientBroker( Context context, IContexthub contextHubProxy, ContextHubClientManager clientManager, ContextHubInfo contextHubInfo, short hostEndPointId, @@ -102,8 +174,10 @@ public class ContextHubClientBroker extends IContextHubClient.Stub * * @throws RemoteException if the client has already died */ - /* package */ void attachDeathRecipient() throws RemoteException { - mCallbackInterface.asBinder().linkToDeath(this, 0 /* flags */); + /* package */ synchronized void attachDeathRecipient() throws RemoteException { + if (mCallbackInterface != null) { + mCallbackInterface.asBinder().linkToDeath(this, 0 /* flags */); + } } /** @@ -118,9 +192,13 @@ public class ContextHubClientBroker extends IContextHubClient.Stub ContextHubServiceUtil.checkPermissions(mContext); int result; - if (mConnectionOpen.get()) { - ContextHubMsg messageToNanoApp = ContextHubServiceUtil.createHidlContextHubMessage( - mHostEndPointId, message); + IContextHubClientCallback callback = null; + synchronized (this) { + callback = mCallbackInterface; + } + if (callback != null) { + ContextHubMsg messageToNanoApp = + ContextHubServiceUtil.createHidlContextHubMessage(mHostEndPointId, message); int contextHubId = mAttachedContextHubInfo.getId(); try { @@ -139,24 +217,37 @@ public class ContextHubClientBroker extends IContextHubClient.Stub } /** - * @param intent the intent to register - * @param nanoAppId the ID of the nanoapp to send events for + * @param pendingIntent the intent to register + * @param nanoAppId the ID of the nanoapp to send events for * @return true on success, false otherwise */ @Override - public boolean registerIntent(PendingIntent intent, long nanoAppId) { - // TODO: Implement this - return false; + public boolean registerIntent(PendingIntent pendingIntent, long nanoAppId) { + ContextHubServiceUtil.checkPermissions(mContext); + + boolean success = false; + synchronized (this) { + if (mCallbackInterface == null) { + Log.e(TAG, "Failed to register PendingIntent: client connection is closed"); + } else { + success = mPendingIntentRequest.register(pendingIntent, nanoAppId); + } + } + + return success; } /** - * @param intent the intent to unregister + * @param pendingIntent the intent to unregister * @return true on success, false otherwise */ @Override - public boolean unregisterIntent(PendingIntent intent) { - // TODO: Implement this - return false; + public boolean unregisterIntent(PendingIntent pendingIntent) { + ContextHubServiceUtil.checkPermissions(mContext); + + synchronized (this) { + return mPendingIntentRequest.unregister(pendingIntent); + } } /** @@ -164,8 +255,15 @@ public class ContextHubClientBroker extends IContextHubClient.Stub */ @Override public void close() { - if (mConnectionOpen.getAndSet(false)) { - mClientManager.unregisterClient(mHostEndPointId); + synchronized (this) { + if (mCallbackInterface != null) { + mCallbackInterface.asBinder().unlinkToDeath(this, 0 /* flags */); + mCallbackInterface = null; + } + if (!mPendingIntentRequest.hasPendingIntent() && mRegistered) { + mClientManager.unregisterClient(mHostEndPointId); + mRegistered = false; + } } } @@ -197,7 +295,12 @@ public class ContextHubClientBroker extends IContextHubClient.Stub * @param message the message that came from a nanoapp */ /* package */ void sendMessageToClient(NanoAppMessage message) { - invokeCallbackConcurrent(callback -> callback.onMessageFromNanoApp(message)); + invokeCallback(callback -> callback.onMessageFromNanoApp(message)); + + Supplier<Intent> supplier = + () -> createIntent(ContextHubManager.EVENT_NANOAPP_MESSAGE, message.getNanoAppId()) + .putExtra(ContextHubManager.EXTRA_MESSAGE, message); + sendPendingIntent(supplier); } /** @@ -206,7 +309,8 @@ public class ContextHubClientBroker extends IContextHubClient.Stub * @param nanoAppId the ID of the nanoapp that was loaded. */ /* package */ void onNanoAppLoaded(long nanoAppId) { - invokeCallbackConcurrent(callback -> callback.onNanoAppLoaded(nanoAppId)); + invokeCallback(callback -> callback.onNanoAppLoaded(nanoAppId)); + sendPendingIntent(() -> createIntent(ContextHubManager.EVENT_NANOAPP_LOADED, nanoAppId)); } /** @@ -215,14 +319,16 @@ public class ContextHubClientBroker extends IContextHubClient.Stub * @param nanoAppId the ID of the nanoapp that was unloaded. */ /* package */ void onNanoAppUnloaded(long nanoAppId) { - invokeCallbackConcurrent(callback -> callback.onNanoAppUnloaded(nanoAppId)); + invokeCallback(callback -> callback.onNanoAppUnloaded(nanoAppId)); + sendPendingIntent(() -> createIntent(ContextHubManager.EVENT_NANOAPP_UNLOADED, nanoAppId)); } /** * Notifies the client of a hub reset event if the connection is open. */ /* package */ void onHubReset() { - invokeCallbackConcurrent(callback -> callback.onHubReset()); + invokeCallback(callback -> callback.onHubReset()); + sendPendingIntent(() -> createIntent(ContextHubManager.EVENT_HUB_RESET)); } /** @@ -232,7 +338,12 @@ public class ContextHubClientBroker extends IContextHubClient.Stub * @param abortCode the nanoapp specific abort code */ /* package */ void onNanoAppAborted(long nanoAppId, int abortCode) { - invokeCallbackConcurrent(callback -> callback.onNanoAppAborted(nanoAppId, abortCode)); + invokeCallback(callback -> callback.onNanoAppAborted(nanoAppId, abortCode)); + + Supplier<Intent> supplier = + () -> createIntent(ContextHubManager.EVENT_NANOAPP_ABORTED, nanoAppId) + .putExtra(ContextHubManager.EXTRA_NANOAPP_ABORT_CODE, abortCode); + sendPendingIntent(supplier); } /** @@ -240,8 +351,8 @@ public class ContextHubClientBroker extends IContextHubClient.Stub * * @param consumer the consumer specifying the callback to invoke */ - private void invokeCallbackConcurrent(CallbackConsumer consumer) { - if (mConnectionOpen.get()) { + private synchronized void invokeCallback(CallbackConsumer consumer) { + if (mCallbackInterface != null) { try { consumer.accept(mCallbackInterface); } catch (RemoteException e) { @@ -250,4 +361,53 @@ public class ContextHubClientBroker extends IContextHubClient.Stub } } } + + /** + * Creates an Intent object containing the ContextHubManager.EXTRA_EVENT_TYPE extra field + * + * @param eventType the ContextHubManager.Event type describing the event + * @return the Intent object + */ + private Intent createIntent(int eventType) { + Intent intent = new Intent(); + intent.putExtra(ContextHubManager.EXTRA_EVENT_TYPE, eventType); + intent.putExtra(ContextHubManager.EXTRA_CONTEXT_HUB_INFO, mAttachedContextHubInfo); + return intent; + } + + /** + * Creates an Intent object containing the ContextHubManager.EXTRA_EVENT_TYPE and the + * ContextHubManager.EXTRA_NANOAPP_ID extra fields + * + * @param eventType the ContextHubManager.Event type describing the event + * @param nanoAppId the ID of the nanoapp this event is for + * @return the Intent object + */ + private Intent createIntent(int eventType, long nanoAppId) { + Intent intent = createIntent(eventType); + intent.putExtra(ContextHubManager.EXTRA_NANOAPP_ID, nanoAppId); + return intent; + } + + /** + * Sends an intent to any existing PendingIntent + * + * @param supplier method to create the extra Intent + */ + private synchronized void sendPendingIntent(Supplier<Intent> supplier) { + if (mPendingIntentRequest.hasPendingIntent()) { + Intent intent = supplier.get(); + try { + mPendingIntentRequest.getPendingIntent().send( + mContext, 0 /* code */, intent, null /* onFinished */, null /* Handler */, + Manifest.permission.LOCATION_HARDWARE /* requiredPermission */, + null /* options */); + } catch (PendingIntent.CanceledException e) { + // The PendingIntent is no longer valid + Log.w(TAG, "PendingIntent has been canceled, unregistering from client" + + " (host endpoint ID " + mHostEndPointId + ")"); + mPendingIntentRequest.clear(); + } + } + } } diff --git a/services/core/java/com/android/server/net/NetworkStatsService.java b/services/core/java/com/android/server/net/NetworkStatsService.java index 6d3a3b69bcb5..dc3bfbca893c 100644 --- a/services/core/java/com/android/server/net/NetworkStatsService.java +++ b/services/core/java/com/android/server/net/NetworkStatsService.java @@ -1627,7 +1627,8 @@ public class NetworkStatsService extends INetworkStatsService.Stub { // fold tethering stats and operations into uid snapshot final NetworkStats tetherSnapshot = getNetworkStatsTethering(STATS_PER_UID); tetherSnapshot.filter(UID_ALL, ifaces, TAG_ALL); - NetworkStatsFactory.apply464xlatAdjustments(uidSnapshot, tetherSnapshot); + NetworkStatsFactory.apply464xlatAdjustments(uidSnapshot, tetherSnapshot, + mUseBpfTrafficStats); uidSnapshot.combineAllValues(tetherSnapshot); final TelephonyManager telephonyManager = (TelephonyManager) mContext.getSystemService( @@ -1637,7 +1638,8 @@ public class NetworkStatsService extends INetworkStatsService.Stub { final NetworkStats vtStats = telephonyManager.getVtDataUsage(STATS_PER_UID); if (vtStats != null) { vtStats.filter(UID_ALL, ifaces, TAG_ALL); - NetworkStatsFactory.apply464xlatAdjustments(uidSnapshot, vtStats); + NetworkStatsFactory.apply464xlatAdjustments(uidSnapshot, vtStats, + mUseBpfTrafficStats); uidSnapshot.combineAllValues(vtStats); } diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index b988c6a1c739..9210d46c1476 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -4288,8 +4288,12 @@ public class PackageManagerService extends IPackageManager.Stub int filterCallingUid, int userId) { if (!sUserManager.exists(userId)) return null; flags = updateFlagsForApplication(flags, userId, packageName); - mPermissionManager.enforceCrossUserPermission(Binder.getCallingUid(), userId, - false /* requireFullPermission */, false /* checkShell */, "get application info"); + + if (!isRecentsAccessingChildProfiles(Binder.getCallingUid(), userId)) { + mPermissionManager.enforceCrossUserPermission(Binder.getCallingUid(), userId, + false /* requireFullPermission */, false /* checkShell */, + "get application info"); + } // writer synchronized (mPackages) { diff --git a/services/core/java/com/android/server/pm/permission/PermissionsState.java b/services/core/java/com/android/server/pm/permission/PermissionsState.java index 82d6b226df9f..c615ee502fa9 100644 --- a/services/core/java/com/android/server/pm/permission/PermissionsState.java +++ b/services/core/java/com/android/server/pm/permission/PermissionsState.java @@ -30,6 +30,7 @@ import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Set; +import com.android.internal.annotations.GuardedBy; /** * This class encapsulates the permissions for a package or a shared user. @@ -62,6 +63,9 @@ public final class PermissionsState { private static final int[] NO_GIDS = {}; + private final Object mLock = new Object(); + + @GuardedBy("mLock") private ArrayMap<String, PermissionData> mPermissions; private int[] mGlobalGids = NO_GIDS; @@ -96,22 +100,25 @@ public final class PermissionsState { if (other == this) { return; } - if (mPermissions != null) { - if (other.mPermissions == null) { - mPermissions = null; - } else { - mPermissions.clear(); - } - } - if (other.mPermissions != null) { - if (mPermissions == null) { - mPermissions = new ArrayMap<>(); + + synchronized (mLock) { + if (mPermissions != null) { + if (other.mPermissions == null) { + mPermissions = null; + } else { + mPermissions.clear(); + } } - final int permissionCount = other.mPermissions.size(); - for (int i = 0; i < permissionCount; i++) { - String name = other.mPermissions.keyAt(i); - PermissionData permissionData = other.mPermissions.valueAt(i); - mPermissions.put(name, new PermissionData(permissionData)); + if (other.mPermissions != null) { + if (mPermissions == null) { + mPermissions = new ArrayMap<>(); + } + final int permissionCount = other.mPermissions.size(); + for (int i = 0; i < permissionCount; i++) { + String name = other.mPermissions.keyAt(i); + PermissionData permissionData = other.mPermissions.valueAt(i); + mPermissions.put(name, new PermissionData(permissionData)); + } } } @@ -154,13 +161,16 @@ public final class PermissionsState { } final PermissionsState other = (PermissionsState) obj; - if (mPermissions == null) { - if (other.mPermissions != null) { + synchronized (mLock) { + if (mPermissions == null) { + if (other.mPermissions != null) { + return false; + } + } else if (!mPermissions.equals(other.mPermissions)) { return false; } - } else if (!mPermissions.equals(other.mPermissions)) { - return false; } + if (mPermissionReviewRequired == null) { if (other.mPermissionReviewRequired != null) { return false; @@ -267,12 +277,15 @@ public final class PermissionsState { public boolean hasPermission(String name, int userId) { enforceValidUserId(userId); - if (mPermissions == null) { - return false; + synchronized (mLock) { + if (mPermissions == null) { + return false; + } + PermissionData permissionData = mPermissions.get(name); + + return permissionData != null && permissionData.isGranted(userId); } - PermissionData permissionData = mPermissions.get(name); - return permissionData != null && permissionData.isGranted(userId); } /** @@ -280,14 +293,17 @@ public final class PermissionsState { * whether or not it has been granted. */ public boolean hasRequestedPermission(ArraySet<String> names) { - if (mPermissions == null) { - return false; - } - for (int i=names.size()-1; i>=0; i--) { - if (mPermissions.get(names.valueAt(i)) != null) { - return true; + synchronized (mLock) { + if (mPermissions == null) { + return false; + } + for (int i=names.size()-1; i>=0; i--) { + if (mPermissions.get(names.valueAt(i)) != null) { + return true; + } } } + return false; } @@ -308,29 +324,31 @@ public final class PermissionsState { public Set<String> getPermissions(int userId) { enforceValidUserId(userId); - if (mPermissions == null) { - return Collections.emptySet(); - } - - Set<String> permissions = new ArraySet<>(mPermissions.size()); + synchronized (mLock) { + if (mPermissions == null) { + return Collections.emptySet(); + } - final int permissionCount = mPermissions.size(); - for (int i = 0; i < permissionCount; i++) { - String permission = mPermissions.keyAt(i); + Set<String> permissions = new ArraySet<>(mPermissions.size()); - if (hasInstallPermission(permission)) { - permissions.add(permission); - continue; - } + final int permissionCount = mPermissions.size(); + for (int i = 0; i < permissionCount; i++) { + String permission = mPermissions.keyAt(i); - if (userId != UserHandle.USER_ALL) { - if (hasRuntimePermission(permission, userId)) { + if (hasInstallPermission(permission)) { permissions.add(permission); + continue; + } + + if (userId != UserHandle.USER_ALL) { + if (hasRuntimePermission(permission, userId)) { + permissions.add(permission); + } } } - } - return permissions; + return permissions; + } } /** @@ -407,14 +425,20 @@ public final class PermissionsState { final boolean mayChangeFlags = flagValues != 0 || flagMask != 0; - if (mPermissions == null) { - if (!mayChangeFlags) { - return false; + synchronized (mLock) { + if (mPermissions == null) { + if (!mayChangeFlags) { + return false; + } + ensurePermissionData(permission); } - ensurePermissionData(permission); } - PermissionData permissionData = mPermissions.get(permission.getName()); + PermissionData permissionData = null; + synchronized (mLock) { + permissionData = mPermissions.get(permission.getName()); + } + if (permissionData == null) { if (!mayChangeFlags) { return false; @@ -447,14 +471,17 @@ public final class PermissionsState { } private boolean hasPermissionRequiringReview(int userId) { - final int permissionCount = mPermissions.size(); - for (int i = 0; i < permissionCount; i++) { - final PermissionData permission = mPermissions.valueAt(i); - if ((permission.getFlags(userId) - & PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED) != 0) { - return true; + synchronized (mLock) { + final int permissionCount = mPermissions.size(); + for (int i = 0; i < permissionCount; i++) { + final PermissionData permission = mPermissions.valueAt(i); + if ((permission.getFlags(userId) + & PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED) != 0) { + return true; + } } } + return false; } @@ -462,16 +489,19 @@ public final class PermissionsState { int userId, int flagMask, int flagValues) { enforceValidUserId(userId); - if (mPermissions == null) { - return false; - } - boolean changed = false; - final int permissionCount = mPermissions.size(); - for (int i = 0; i < permissionCount; i++) { - PermissionData permissionData = mPermissions.valueAt(i); - changed |= permissionData.updateFlags(userId, flagMask, flagValues); + synchronized (mLock) { + if (mPermissions == null) { + return false; + } + boolean changed = false; + final int permissionCount = mPermissions.size(); + for (int i = 0; i < permissionCount; i++) { + PermissionData permissionData = mPermissions.valueAt(i); + changed |= permissionData.updateFlags(userId, flagMask, flagValues); + } + + return changed; } - return changed; } /** @@ -487,17 +517,19 @@ public final class PermissionsState { int[] gids = mGlobalGids; - if (mPermissions != null) { - final int permissionCount = mPermissions.size(); - for (int i = 0; i < permissionCount; i++) { - String permission = mPermissions.keyAt(i); - if (!hasPermission(permission, userId)) { - continue; - } - PermissionData permissionData = mPermissions.valueAt(i); - final int[] permGids = permissionData.computeGids(userId); - if (permGids != NO_GIDS) { - gids = appendInts(gids, permGids); + synchronized (mLock) { + if (mPermissions != null) { + final int permissionCount = mPermissions.size(); + for (int i = 0; i < permissionCount; i++) { + String permission = mPermissions.keyAt(i); + if (!hasPermission(permission, userId)) { + continue; + } + PermissionData permissionData = mPermissions.valueAt(i); + final int[] permGids = permissionData.computeGids(userId); + if (permGids != NO_GIDS) { + gids = appendInts(gids, permGids); + } } } } @@ -527,41 +559,50 @@ public final class PermissionsState { */ public void reset() { mGlobalGids = NO_GIDS; - mPermissions = null; + + synchronized (mLock) { + mPermissions = null; + } + mPermissionReviewRequired = null; } private PermissionState getPermissionState(String name, int userId) { - if (mPermissions == null) { - return null; - } - PermissionData permissionData = mPermissions.get(name); - if (permissionData == null) { - return null; + synchronized (mLock) { + if (mPermissions == null) { + return null; + } + PermissionData permissionData = mPermissions.get(name); + if (permissionData == null) { + return null; + } + + return permissionData.getPermissionState(userId); } - return permissionData.getPermissionState(userId); } private List<PermissionState> getPermissionStatesInternal(int userId) { enforceValidUserId(userId); - if (mPermissions == null) { - return Collections.emptyList(); - } + synchronized (mLock) { + if (mPermissions == null) { + return Collections.emptyList(); + } - List<PermissionState> permissionStates = new ArrayList<>(); + List<PermissionState> permissionStates = new ArrayList<>(); - final int permissionCount = mPermissions.size(); - for (int i = 0; i < permissionCount; i++) { - PermissionData permissionData = mPermissions.valueAt(i); + final int permissionCount = mPermissions.size(); + for (int i = 0; i < permissionCount; i++) { + PermissionData permissionData = mPermissions.valueAt(i); - PermissionState permissionState = permissionData.getPermissionState(userId); - if (permissionState != null) { - permissionStates.add(permissionState); + PermissionState permissionState = permissionData.getPermissionState(userId); + if (permissionState != null) { + permissionStates.add(permissionState); + } } - } - return permissionStates; + return permissionStates; + } } private int grantPermission(BasePermission permission, int userId) { @@ -597,7 +638,10 @@ public final class PermissionsState { final boolean hasGids = !ArrayUtils.isEmpty(permission.computeGids(userId)); final int[] oldGids = hasGids ? computeGids(userId) : NO_GIDS; - PermissionData permissionData = mPermissions.get(permName); + PermissionData permissionData = null; + synchronized (mLock) { + permissionData = mPermissions.get(permName); + } if (!permissionData.revoke(userId)) { return PERMISSION_OPERATION_FAILURE; @@ -635,25 +679,32 @@ public final class PermissionsState { private PermissionData ensurePermissionData(BasePermission permission) { final String permName = permission.getName(); - if (mPermissions == null) { - mPermissions = new ArrayMap<>(); - } - PermissionData permissionData = mPermissions.get(permName); - if (permissionData == null) { - permissionData = new PermissionData(permission); - mPermissions.put(permName, permissionData); + + synchronized (mLock) { + if (mPermissions == null) { + mPermissions = new ArrayMap<>(); + } + PermissionData permissionData = mPermissions.get(permName); + if (permissionData == null) { + permissionData = new PermissionData(permission); + mPermissions.put(permName, permissionData); + } + return permissionData; } - return permissionData; + } private void ensureNoPermissionData(String name) { - if (mPermissions == null) { - return; - } - mPermissions.remove(name); - if (mPermissions.isEmpty()) { - mPermissions = null; + synchronized (mLock) { + if (mPermissions == null) { + return; + } + mPermissions.remove(name); + if (mPermissions.isEmpty()) { + mPermissions = null; + } } + } private static final class PermissionData { diff --git a/services/core/java/com/android/server/role/RemoteRoleControllerService.java b/services/core/java/com/android/server/role/RemoteRoleControllerService.java new file mode 100644 index 000000000000..c737e8bb8710 --- /dev/null +++ b/services/core/java/com/android/server/role/RemoteRoleControllerService.java @@ -0,0 +1,263 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.role; + +import android.annotation.MainThread; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.UserIdInt; +import android.app.role.IRoleManagerCallback; +import android.app.role.RoleManagerCallback; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.ServiceConnection; +import android.os.Handler; +import android.os.IBinder; +import android.os.RemoteException; +import android.os.UserHandle; +import android.rolecontrollerservice.IRoleControllerService; +import android.rolecontrollerservice.RoleControllerService; +import android.util.Slog; + +import com.android.internal.util.function.pooled.PooledLambda; + +import java.util.ArrayDeque; +import java.util.Queue; + +/** + * Handles connection with {@link RoleControllerService}. + */ +public class RemoteRoleControllerService { + + private static final String LOG_TAG = RemoteRoleControllerService.class.getSimpleName(); + + @NonNull + private final Connection mConnection; + + public RemoteRoleControllerService(@UserIdInt int userId, @NonNull Context context) { + mConnection = new Connection(userId, context); + } + + /** + * Add a specific application to the holders of a role. If the role is exclusive, the previous + * holder will be replaced. + * + * @see RoleControllerService#onAddRoleHolder(String, String, RoleManagerCallback) + */ + public void onAddRoleHolder(@NonNull String roleName, @NonNull String packageName, + @NonNull IRoleManagerCallback callback) { + mConnection.enqueueCall(new Connection.Call((service, callbackDelegate) -> + service.onAddRoleHolder(roleName, packageName, callbackDelegate), callback)); + } + + /** + * Remove a specific application from the holders of a role. + * + * @see RoleControllerService#onRemoveRoleHolder(String, String, RoleManagerCallback) + */ + public void onRemoveRoleHolder(@NonNull String roleName, @NonNull String packageName, + @NonNull IRoleManagerCallback callback) { + mConnection.enqueueCall(new Connection.Call((service, callbackDelegate) -> + service.onRemoveRoleHolder(roleName, packageName, callbackDelegate), callback)); + } + + /** + * Remove all holders of a role. + * + * @see RoleControllerService#onClearRoleHolders(String, RoleManagerCallback) + */ + public void onClearRoleHolders(@NonNull String roleName, + @NonNull IRoleManagerCallback callback) { + mConnection.enqueueCall(new Connection.Call((service, callbackDelegate) -> + service.onClearRoleHolders(roleName, callbackDelegate), callback)); + } + + private static final class Connection implements ServiceConnection { + + private static final long UNBIND_DELAY_MILLIS = 15 * 1000; + + @UserIdInt + private final int mUserId; + + @NonNull + private final Context mContext; + + private boolean mBound; + + @Nullable + private IRoleControllerService mService; + + @NonNull + private final Queue<Call> mPendingCalls = new ArrayDeque<>(); + + @NonNull + private final Runnable mUnbindRunnable = this::unbind; + + Connection(@UserIdInt int userId, @NonNull Context context) { + mUserId = userId; + mContext = context; + } + + @MainThread + @Override + public void onServiceConnected(@NonNull ComponentName name, @NonNull IBinder service) { + mService = IRoleControllerService.Stub.asInterface(service); + executePendingCalls(); + } + + @MainThread + private void executePendingCalls() { + while (!mPendingCalls.isEmpty()) { + Call call = mPendingCalls.poll(); + call.execute(mService); + } + scheduleUnbind(); + } + + @MainThread + @Override + public void onServiceDisconnected(@NonNull ComponentName name) { + mService = null; + } + + @MainThread + @Override + public void onBindingDied(@NonNull ComponentName name) { + unbind(); + } + + public void enqueueCall(@NonNull Call call) { + Handler.getMain().post(PooledLambda.obtainRunnable(this::executeCall, call)); + } + + @MainThread + private void executeCall(@NonNull Call call) { + ensureBound(); + if (mService == null) { + mPendingCalls.offer(call); + return; + } + call.execute(mService); + scheduleUnbind(); + } + + @MainThread + private void ensureBound() { + Handler.getMain().removeCallbacks(mUnbindRunnable); + if (!mBound) { + Intent intent = new Intent(RoleControllerService.SERVICE_INTERFACE); + intent.setPackage(mContext.getPackageManager() + .getPermissionControllerPackageName()); + mBound = mContext.bindServiceAsUser(intent, this, Context.BIND_AUTO_CREATE, + UserHandle.of(mUserId)); + } + } + + private void scheduleUnbind() { + Handler mainHandler = Handler.getMain(); + mainHandler.removeCallbacks(mUnbindRunnable); + mainHandler.postDelayed(mUnbindRunnable, UNBIND_DELAY_MILLIS); + } + + @MainThread + private void unbind() { + if (mBound) { + mService = null; + mContext.unbindService(this); + mBound = false; + } + } + + public static class Call { + + private static final int TIMEOUT_MILLIS = 15 * 1000; + + @NonNull + private final CallExecutor mCallExecutor; + + @NonNull + private final IRoleManagerCallback mCallback; + + @NonNull + private final Handler mMainHandler = Handler.getMain(); + + @NonNull + private final Runnable mTimeoutRunnable = () -> notifyCallback(false); + + private boolean mCallbackNotified; + + private Call(@NonNull CallExecutor callExecutor, + @NonNull IRoleManagerCallback callback) { + mCallExecutor = callExecutor; + mCallback = callback; + } + + @MainThread + public void execute(IRoleControllerService service) { + try { + mMainHandler.postDelayed(mTimeoutRunnable, TIMEOUT_MILLIS); + mCallExecutor.execute(service, new CallbackDelegate()); + } catch (RemoteException e) { + Slog.e(LOG_TAG, "Error calling RoleControllerService", e); + notifyCallback(false); + } + } + + @MainThread + private void notifyCallback(boolean success) { + if (mCallbackNotified) { + return; + } + mCallbackNotified = true; + mMainHandler.removeCallbacks(mTimeoutRunnable); + try { + if (success) { + mCallback.onSuccess(); + } else { + mCallback.onFailure(); + } + } catch (RemoteException e) { + Slog.e(LOG_TAG, "Error calling " + (success ? "onSuccess()" : "onFailure()") + + " callback", e); + } + } + + @FunctionalInterface + public interface CallExecutor { + + @MainThread + void execute(IRoleControllerService service, IRoleManagerCallback callbackDelegate) + throws RemoteException; + } + + private class CallbackDelegate extends IRoleManagerCallback.Stub { + + @Override + public void onSuccess() throws RemoteException { + mMainHandler.post(PooledLambda.obtainRunnable(Call.this::notifyCallback, true)); + } + + @Override + public void onFailure() throws RemoteException { + mMainHandler.post(PooledLambda.obtainRunnable(Call.this::notifyCallback, + false)); + } + } + } + } +} diff --git a/services/core/java/com/android/server/role/RoleManagerService.java b/services/core/java/com/android/server/role/RoleManagerService.java new file mode 100644 index 000000000000..5c9cef507b67 --- /dev/null +++ b/services/core/java/com/android/server/role/RoleManagerService.java @@ -0,0 +1,261 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.role; + +import android.Manifest; +import android.annotation.CheckResult; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.UserIdInt; +import android.app.ActivityManager; +import android.app.AppOpsManager; +import android.app.role.IRoleManager; +import android.app.role.IRoleManagerCallback; +import android.app.role.RoleManager; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.os.UserHandle; +import android.os.UserManagerInternal; +import android.text.TextUtils; +import android.util.ArraySet; +import android.util.Slog; +import android.util.SparseArray; + +import com.android.internal.annotations.GuardedBy; +import com.android.internal.util.Preconditions; +import com.android.server.LocalServices; +import com.android.server.SystemService; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * Service for role management. + * + * @see RoleManager + */ +public class RoleManagerService extends SystemService { + + private static final String LOG_TAG = RoleManagerService.class.getSimpleName(); + + @NonNull + private final UserManagerInternal mUserManagerInternal; + @NonNull + private final AppOpsManager mAppOpsManager; + + @NonNull + private final Object mLock = new Object(); + + /** + * Maps user id to its state. + */ + @GuardedBy("mLock") + @NonNull + private final SparseArray<RoleUserState> mUserStates = new SparseArray<>(); + + /** + * Maps user id to its controller service. + */ + @GuardedBy("mLock") + @NonNull + private final SparseArray<RemoteRoleControllerService> mControllerServices = + new SparseArray<>(); + + public RoleManagerService(@NonNull Context context) { + super(context); + + mUserManagerInternal = LocalServices.getService(UserManagerInternal.class); + mAppOpsManager = context.getSystemService(AppOpsManager.class); + + registerUserRemovedReceiver(); + } + + private void registerUserRemovedReceiver() { + IntentFilter intentFilter = new IntentFilter(); + intentFilter.addAction(Intent.ACTION_USER_REMOVED); + getContext().registerReceiverAsUser(new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + if (TextUtils.equals(intent.getAction(), Intent.ACTION_USER_REMOVED)) { + int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0); + onRemoveUser(userId); + } + } + }, UserHandle.ALL, intentFilter, null, null); + } + + @Override + public void onStart() { + publishBinderService(Context.ROLE_SERVICE, new Stub()); + } + + @Override + public void onStartUser(@UserIdInt int userId) { + synchronized (mLock) { + getUserStateLocked(userId); + } + } + + @GuardedBy("mLock") + @NonNull + private RoleUserState getUserStateLocked(@UserIdInt int userId) { + RoleUserState userState = mUserStates.get(userId); + if (userState == null) { + userState = new RoleUserState(userId); + userState.readSyncLocked(); + mUserStates.put(userId, userState); + } + return userState; + } + + @GuardedBy("mLock") + @NonNull + private RemoteRoleControllerService getControllerService(@UserIdInt int userId) { + RemoteRoleControllerService controllerService = mControllerServices.get(userId); + if (controllerService == null) { + controllerService = new RemoteRoleControllerService(userId, getContext()); + mControllerServices.put(userId, controllerService); + } + return controllerService; + } + + private void onRemoveUser(@UserIdInt int userId) { + synchronized (mLock) { + mControllerServices.remove(userId); + RoleUserState userState = mUserStates.removeReturnOld(userId); + if (userState != null) { + userState.destroySyncLocked(); + } + } + } + + private class Stub extends IRoleManager.Stub { + + @Override + public boolean isRoleAvailable(@NonNull String roleName) { + Preconditions.checkStringNotEmpty(roleName, "roleName cannot be null or empty"); + + int userId = UserHandle.getUserId(getCallingUid()); + synchronized (mLock) { + RoleUserState userState = getUserStateLocked(userId); + return userState.isRoleAvailableLocked(roleName); + } + } + + @Override + public boolean isRoleHeld(@NonNull String roleName, @NonNull String packageName) { + Preconditions.checkStringNotEmpty(roleName, "roleName cannot be null or empty"); + Preconditions.checkStringNotEmpty(packageName, "packageName cannot be null or empty"); + int callingUid = getCallingUid(); + mAppOpsManager.checkPackage(callingUid, packageName); + + int userId = UserHandle.getUserId(callingUid); + ArraySet<String> roleHolders = getRoleHoldersInternal(roleName, userId); + if (roleHolders == null) { + return false; + } + return roleHolders.contains(packageName); + } + + @NonNull + @Override + public List<String> getRoleHoldersAsUser(@NonNull String roleName, @UserIdInt int userId) { + Preconditions.checkStringNotEmpty(roleName, "roleName cannot be null or empty"); + if (!mUserManagerInternal.exists(userId)) { + Slog.e(LOG_TAG, "user " + userId + " does not exist"); + return Collections.emptyList(); + } + userId = handleIncomingUser(userId, "getRoleHoldersAsUser"); + getContext().enforceCallingOrSelfPermission(Manifest.permission.MANAGE_ROLE_HOLDERS, + "getRoleHoldersAsUser"); + + ArraySet<String> roleHolders = getRoleHoldersInternal(roleName, userId); + if (roleHolders == null) { + return Collections.emptyList(); + } + return new ArrayList<>(roleHolders); + } + + @Nullable + private ArraySet<String> getRoleHoldersInternal(@NonNull String roleName, + @UserIdInt int userId) { + synchronized (mLock) { + RoleUserState userState = getUserStateLocked(userId); + return userState.getRoleHoldersLocked(roleName); + } + } + + @Override + public void addRoleHolderAsUser(@NonNull String roleName, @NonNull String packageName, + @UserIdInt int userId, @NonNull IRoleManagerCallback callback) { + Preconditions.checkStringNotEmpty(roleName, "roleName cannot be null or empty"); + Preconditions.checkStringNotEmpty(packageName, "packageName cannot be null or empty"); + Preconditions.checkNotNull(callback, "callback cannot be null"); + if (!mUserManagerInternal.exists(userId)) { + Slog.e(LOG_TAG, "user " + userId + " does not exist"); + return; + } + userId = handleIncomingUser(userId, "addRoleHolderAsUser"); + getContext().enforceCallingOrSelfPermission(Manifest.permission.MANAGE_ROLE_HOLDERS, + "addRoleHolderAsUser"); + + getControllerService(userId).onAddRoleHolder(roleName, packageName, callback); + } + + @Override + public void removeRoleHolderAsUser(@NonNull String roleName, @NonNull String packageName, + @UserIdInt int userId, @NonNull IRoleManagerCallback callback) { + Preconditions.checkStringNotEmpty(roleName, "roleName cannot be null or empty"); + Preconditions.checkStringNotEmpty(packageName, "packageName cannot be null or empty"); + Preconditions.checkNotNull(callback, "callback cannot be null"); + if (!mUserManagerInternal.exists(userId)) { + Slog.e(LOG_TAG, "user " + userId + " does not exist"); + return; + } + userId = handleIncomingUser(userId, "removeRoleHolderAsUser"); + getContext().enforceCallingOrSelfPermission(Manifest.permission.MANAGE_ROLE_HOLDERS, + "removeRoleHolderAsUser"); + + getControllerService(userId).onRemoveRoleHolder(roleName, packageName, + callback); + } + + @Override + public void clearRoleHoldersAsUser(@NonNull String roleName, @UserIdInt int userId, + @NonNull IRoleManagerCallback callback) { + Preconditions.checkStringNotEmpty(roleName, "roleName cannot be null or empty"); + Preconditions.checkNotNull(callback, "callback cannot be null"); + if (!mUserManagerInternal.exists(userId)) { + Slog.e(LOG_TAG, "user " + userId + " does not exist"); + return; + } + userId = handleIncomingUser(userId, "clearRoleHoldersAsUser"); + getContext().enforceCallingOrSelfPermission(Manifest.permission.MANAGE_ROLE_HOLDERS, + "clearRoleHoldersAsUser"); + getControllerService(userId).onClearRoleHolders(roleName, callback); + } + + @CheckResult + private int handleIncomingUser(@UserIdInt int userId, @NonNull String name) { + return ActivityManager.handleIncomingUser(getCallingPid(), getCallingUid(), userId, + false, true, name, null); + } + } +} diff --git a/services/core/java/com/android/server/role/RoleUserState.java b/services/core/java/com/android/server/role/RoleUserState.java new file mode 100644 index 000000000000..bd5449151beb --- /dev/null +++ b/services/core/java/com/android/server/role/RoleUserState.java @@ -0,0 +1,370 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.role; + +import android.annotation.CheckResult; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.UserIdInt; +import android.annotation.WorkerThread; +import android.os.Environment; +import android.os.Handler; +import android.util.ArrayMap; +import android.util.ArraySet; +import android.util.AtomicFile; +import android.util.Slog; +import android.util.Xml; + +import com.android.internal.annotations.GuardedBy; +import com.android.internal.os.BackgroundThread; +import com.android.internal.util.function.pooled.PooledLambda; + +import libcore.io.IoUtils; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; +import org.xmlpull.v1.XmlSerializer; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.nio.charset.StandardCharsets; + +/** + * Stores the state of roles for a user. + */ +public class RoleUserState { + + private static final String LOG_TAG = RoleUserState.class.getSimpleName(); + + public static final int VERSION_UNDEFINED = -1; + + private static final String ROLES_FILE_NAME = "roles.xml"; + + private static final String TAG_ROLES = "roles"; + private static final String TAG_ROLE = "role"; + private static final String TAG_HOLDER = "holder"; + private static final String ATTRIBUTE_VERSION = "version"; + private static final String ATTRIBUTE_NAME = "name"; + + @UserIdInt + private final int mUserId; + + @GuardedBy("RoleManagerService.mLock") + private int mVersion = VERSION_UNDEFINED; + + /** + * Maps role names to its holders' package names. The values should never be null. + */ + @GuardedBy("RoleManagerService.mLock") + private ArrayMap<String, ArraySet<String>> mRoles = new ArrayMap<>(); + + @GuardedBy("RoleManagerService.mLock") + private boolean mDestroyed; + + private final Handler mWriteHandler = new Handler(BackgroundThread.getHandler().getLooper()); + + public RoleUserState(@UserIdInt int userId) { + mUserId = userId; + } + + /** + * Get the version of this user state. + */ + @GuardedBy("RoleManagerService.mLock") + public int getVersionLocked() { + throwIfDestroyedLocked(); + return mVersion; + } + + /** + * Set the version of this user state. + * + * @param version the version to set + */ + @GuardedBy("RoleManagerService.mLock") + public void setVersionLocked(int version) { + throwIfDestroyedLocked(); + mVersion = version; + } + + /** + * Get whether the role is available. + * + * @param roleName the name of the role to get the holders for + * + * @return whether the role is available + */ + @GuardedBy("RoleManagerService.mLock") + public boolean isRoleAvailableLocked(@NonNull String roleName) { + throwIfDestroyedLocked(); + return mRoles.containsKey(roleName); + } + + /** + * Get the holders of a role. + * + * @param roleName the name of the role to query for + * + * @return the set of role holders. {@code null} should not be returned and indicates an issue. + */ + @GuardedBy("RoleManagerService.mLock") + @Nullable + public ArraySet<String> getRoleHoldersLocked(@NonNull String roleName) { + throwIfDestroyedLocked(); + return mRoles.get(roleName); + } + + /** + * Add a holder to a role. + * + * @param roleName the name of the role to add the holder to + * @param packageName the package name of the new holder + * + * @return {@code false} only if the set of role holders is null, which should not happen and + * indicates an issue. + */ + @CheckResult + @GuardedBy("RoleManagerService.mLock") + public boolean addRoleHolderLocked(@NonNull String roleName, @NonNull String packageName) { + throwIfDestroyedLocked(); + ArraySet<String> roleHolders = mRoles.get(roleName); + if (roleHolders == null) { + return false; + } + roleHolders.add(packageName); + return true; + } + + /** + * Remove a holder from a role. + * + * @param roleName the name of the role to remove the holder from + * @param packageName the package name of the holder to remove + * + * @return {@code false} only if the set of role holders is null, which should not happen and + * indicates an issue. + */ + @CheckResult + @GuardedBy("RoleManagerService.mLock") + public boolean removeRoleHolderLocked(@NonNull String roleName, @NonNull String packageName) { + throwIfDestroyedLocked(); + ArraySet<String> roleHolders = mRoles.get(roleName); + if (roleHolders == null) { + return false; + } + roleHolders.remove(packageName); + return true; + } + + /** + * Remove all holders of a role. + * + * @param roleName the name of the role to remove all its holders + * + * @return {@code false} only if the set of role holders is null, which should not happen and + * indicates an issue. + */ + @GuardedBy("RoleManagerService.mLock") + public boolean clearRoleHolderLocked(@NonNull String roleName) { + throwIfDestroyedLocked(); + ArraySet<String> roleHolders = mRoles.get(roleName); + if (roleHolders == null) { + return false; + } + roleHolders.clear(); + return true; + } + + /** + * Schedule writing the state to file. + */ + @GuardedBy("RoleManagerService.mLock") + public void writeAsyncLocked() { + throwIfDestroyedLocked(); + int version = mVersion; + ArrayMap<String, ArraySet<String>> roles = new ArrayMap<>(); + for (int i = 0, size = mRoles.size(); i < size; ++i) { + String roleName = mRoles.keyAt(i); + ArraySet<String> roleHolders = mRoles.valueAt(i); + roleHolders = new ArraySet<>(roleHolders); + roles.put(roleName, roleHolders); + } + mWriteHandler.removeCallbacksAndMessages(null); + mWriteHandler.sendMessage(PooledLambda.obtainMessage(this::writeSync, version, roles)); + } + + @WorkerThread + private void writeSync(int version, @NonNull ArrayMap<String, ArraySet<String>> roles) { + AtomicFile destination = new AtomicFile(getFile(mUserId), "roles-" + mUserId); + FileOutputStream out = null; + try { + out = destination.startWrite(); + + XmlSerializer serializer = Xml.newSerializer(); + serializer.setOutput(out, StandardCharsets.UTF_8.name()); + serializer.setFeature( + "http://xmlpull.org/v1/doc/features.html#indent-output", true); + serializer.startDocument(null, true); + + serializeRoles(serializer, version, roles); + + serializer.endDocument(); + destination.finishWrite(out); + } catch (Throwable t) { + // Any error while writing is fatal. + Slog.wtf(LOG_TAG, "Failed to write roles file, restoring backup", t); + destination.failWrite(out); + } finally { + IoUtils.closeQuietly(out); + } + } + + @WorkerThread + private void serializeRoles(@NonNull XmlSerializer serializer, int version, + @NonNull ArrayMap<String, ArraySet<String>> roles) throws IOException { + serializer.startTag(null, TAG_ROLES); + serializer.attribute(null, ATTRIBUTE_VERSION, Integer.toString(version)); + for (int i = 0, size = roles.size(); i < size; ++i) { + String roleName = roles.keyAt(i); + ArraySet<String> roleHolders = roles.valueAt(i); + serializer.startTag(null, TAG_ROLE); + serializer.attribute(null, ATTRIBUTE_NAME, roleName); + serializeRoleHolders(serializer, roleHolders); + serializer.endTag(null, TAG_ROLE); + } + serializer.endTag(null, TAG_ROLES); + } + + @WorkerThread + private void serializeRoleHolders(@NonNull XmlSerializer serializer, + @NonNull ArraySet<String> roleHolders) throws IOException { + for (int i = 0, size = roleHolders.size(); i < size; ++i) { + String roleHolder = roleHolders.valueAt(i); + serializer.startTag(null, TAG_HOLDER); + serializer.attribute(null, ATTRIBUTE_NAME, roleHolder); + serializer.endTag(null, TAG_HOLDER); + } + } + + /** + * Read the state from file. + */ + @GuardedBy("RoleManagerService.mLock") + public void readSyncLocked() { + if (mRoles != null) { + throw new IllegalStateException("This RoleUserState has already read the XML file"); + } + File file = getFile(mUserId); + FileInputStream in; + try { + in = new AtomicFile(file).openRead(); + } catch (FileNotFoundException e) { + Slog.i(LOG_TAG, "No roles file found"); + return; + } + + try { + XmlPullParser parser = Xml.newPullParser(); + parser.setInput(in, null); + parseXmlLocked(parser); + } catch (XmlPullParserException | IOException e) { + throw new IllegalStateException("Failed to parse roles file: " + file , e); + } finally { + IoUtils.closeQuietly(in); + } + } + + private void parseXmlLocked(@NonNull XmlPullParser parser) throws IOException, + XmlPullParserException { + int outerDepth = parser.getDepth(); + int type; + while ((type = parser.next()) != XmlPullParser.END_DOCUMENT + && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) { + if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) { + continue; + } + if (parser.getName().equals(TAG_ROLES)) { + parseRolesLocked(parser); + return; + } + } + } + + private void parseRolesLocked(@NonNull XmlPullParser parser) throws IOException, + XmlPullParserException { + mVersion = Integer.parseInt(parser.getAttributeValue(null, ATTRIBUTE_VERSION)); + mRoles = new ArrayMap<>(); + int outerDepth = parser.getDepth(); + int type; + while ((type = parser.next()) != XmlPullParser.END_DOCUMENT + && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) { + if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) { + continue; + } + if (parser.getName().equals(TAG_ROLE)) { + String roleName = parser.getAttributeValue(null, ATTRIBUTE_NAME); + ArraySet<String> roleHolders = parseRoleHoldersLocked(parser); + mRoles.put(roleName, roleHolders); + } + } + } + + @NonNull + private ArraySet<String> parseRoleHoldersLocked(@NonNull XmlPullParser parser) + throws IOException, XmlPullParserException { + ArraySet<String> roleHolders = new ArraySet<>(); + int outerDepth = parser.getDepth(); + int type; + while ((type = parser.next()) != XmlPullParser.END_DOCUMENT + && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) { + if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) { + continue; + } + if (parser.getName().equals(TAG_HOLDER)) { + String roleHolder = parser.getAttributeValue(null, ATTRIBUTE_NAME); + roleHolders.add(roleHolder); + } + } + return roleHolders; + } + + /** + * Destroy this state and delete the corresponding file. Any pending writes to the file will be + * cancelled and any future interaction with this state will throw an exception. + */ + @GuardedBy("RoleManagerService.mLock") + public void destroySyncLocked() { + throwIfDestroyedLocked(); + mWriteHandler.removeCallbacksAndMessages(null); + getFile(mUserId).delete(); + mDestroyed = true; + } + + @GuardedBy("RoleManagerService.mLock") + private void throwIfDestroyedLocked() { + if (mDestroyed) { + throw new IllegalStateException("This RoleUserState has already been destroyed"); + } + } + + private static @NonNull File getFile(@UserIdInt int userId) { + return new File(Environment.getUserSystemDirectory(userId), ROLES_FILE_NAME); + } +} diff --git a/services/core/java/com/android/server/stats/StatsCompanionService.java b/services/core/java/com/android/server/stats/StatsCompanionService.java index 19487061496b..465a2cfcc457 100644 --- a/services/core/java/com/android/server/stats/StatsCompanionService.java +++ b/services/core/java/com/android/server/stats/StatsCompanionService.java @@ -80,6 +80,7 @@ import com.android.internal.app.procstats.ProcessStats; import com.android.internal.net.NetworkStatsFactory; import com.android.internal.os.BinderCallsStats.ExportedCallStat; import com.android.internal.os.KernelCpuSpeedReader; +import com.android.internal.os.KernelCpuThreadReader; import com.android.internal.os.KernelUidCpuActiveTimeReader; import com.android.internal.os.KernelUidCpuClusterTimeReader; import com.android.internal.os.KernelUidCpuFreqTimeReader; @@ -191,6 +192,8 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { new KernelUidCpuClusterTimeReader(); private StoragedUidIoStatsReader mStoragedUidIoStatsReader = new StoragedUidIoStatsReader(); + @Nullable + private final KernelCpuThreadReader mKernelCpuThreadReader; private static IThermalService sThermalService; private File mBaseDir = @@ -265,6 +268,7 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { handlerThread.start(); mHandler = new CompanionHandler(handlerThread.getLooper()); + mKernelCpuThreadReader = KernelCpuThreadReader.create(); } @Override @@ -1048,6 +1052,7 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { e.writeLong(callStat.maxRequestSizeBytes); e.writeLong(callStat.recordedCallCount); e.writeInt(callStat.screenInteractive ? 1 : 0); + e.writeInt(callStat.callingUid); pulledData.add(e); } } @@ -1445,6 +1450,46 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { } } + private void pullCpuTimePerThreadFreq(int tagId, long elapsedNanos, long wallClockNanos, + List<StatsLogEventWrapper> pulledData) { + if (this.mKernelCpuThreadReader == null) { + return; + } + KernelCpuThreadReader.ProcessCpuUsage processCpuUsage = this.mKernelCpuThreadReader + .getCurrentProcessCpuUsage(); + if (processCpuUsage == null) { + return; + } + int[] cpuFrequencies = mKernelCpuThreadReader.getCpuFrequenciesKhz(); + for (KernelCpuThreadReader.ThreadCpuUsage threadCpuUsage + : processCpuUsage.threadCpuUsages) { + if (threadCpuUsage.usageTimesMillis.length != cpuFrequencies.length) { + Slog.w(TAG, "Unexpected number of usage times," + + " expected " + cpuFrequencies.length + + " but got " + threadCpuUsage.usageTimesMillis.length); + continue; + } + + for (int i = 0; i < threadCpuUsage.usageTimesMillis.length; i++) { + // Do not report CPU usage at a frequency when it's zero + if (threadCpuUsage.usageTimesMillis[i] == 0) { + continue; + } + + StatsLogEventWrapper e = + new StatsLogEventWrapper(tagId, elapsedNanos, wallClockNanos); + e.writeInt(processCpuUsage.uid); + e.writeInt(processCpuUsage.processId); + e.writeInt(threadCpuUsage.threadId); + e.writeString(processCpuUsage.processName); + e.writeString(threadCpuUsage.threadName); + e.writeInt(cpuFrequencies[i]); + e.writeInt(threadCpuUsage.usageTimesMillis[i]); + pulledData.add(e); + } + } + } + /** * Pulls various data. */ @@ -1583,6 +1628,10 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { pullProcessCpuTime(tagId, elapsedNanos, wallClockNanos, ret); break; } + case StatsLog.CPU_TIME_PER_THREAD_FREQ: { + pullCpuTimePerThreadFreq(tagId, elapsedNanos, wallClockNanos, ret); + break; + } default: Slog.w(TAG, "No such tagId data as " + tagId); return null; diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java index b011da60b210..caa2da3f5125 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java @@ -33,8 +33,8 @@ import android.os.RemoteException; import android.os.SystemClock; import android.service.voice.IVoiceInteractionSession; import android.util.SparseIntArray; - import android.util.proto.ProtoOutputStream; + import com.android.internal.app.IVoiceInteractor; import com.android.server.am.ActivityServiceConnectionsHolder; import com.android.server.am.PendingIntentRecord; @@ -48,7 +48,6 @@ import java.io.PrintWriter; import java.lang.ref.WeakReference; import java.util.List; import java.util.Set; -import java.util.function.Predicate; /** * Activity Task manager local system service interface. @@ -348,6 +347,8 @@ public abstract class ActivityTaskManagerInternal { /** @return The intent used to launch the home activity. */ public abstract Intent getHomeIntent(); public abstract boolean startHomeActivity(int userId, String reason); + /** Start home activities on all displays that support system decorations. */ + public abstract boolean startHomeOnAllDisplays(int userId, String reason); /** @return true if the given process is the factory test process. */ public abstract boolean isFactoryTestProcess(WindowProcessController wpc); public abstract void updateTopComponentForFactoryTest(); diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index ca2336029ed9..fa9ae529c6e1 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -120,6 +120,7 @@ import static com.android.server.wm.WindowStateAnimator.DRAW_PENDING; import static com.android.server.wm.WindowStateAnimator.READY_TO_SHOW; import android.annotation.CallSuper; +import android.annotation.IntDef; import android.annotation.NonNull; import android.content.pm.PackageManager; import android.content.res.CompatibilityInfo; @@ -137,6 +138,7 @@ import android.os.IBinder; import android.os.RemoteException; import android.os.SystemClock; import android.os.Trace; +import android.os.UserHandle; import android.util.ArraySet; import android.util.DisplayMetrics; import android.util.Slog; @@ -162,6 +164,8 @@ import com.android.server.wm.utils.RotationCache; import com.android.server.wm.utils.WmDisplayCutout; import java.io.PrintWriter; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.Comparator; import java.util.HashMap; @@ -183,6 +187,18 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo implements WindowManagerPolicy.DisplayContentInfo { private static final String TAG = TAG_WITH_CLASS_NAME ? "DisplayContent" : TAG_WM; + /** The default scaling mode that scales content automatically. */ + static final int FORCE_SCALING_MODE_AUTO = 0; + /** For {@link #setForcedScalingMode} to apply flag {@link Display#FLAG_SCALING_DISABLED}. */ + static final int FORCE_SCALING_MODE_DISABLED = 1; + + @IntDef(prefix = { "FORCE_SCALING_MODE_" }, value = { + FORCE_SCALING_MODE_AUTO, + FORCE_SCALING_MODE_DISABLED + }) + @Retention(RetentionPolicy.SOURCE) + @interface ForceScalingMode {} + /** Unique identifier of this stack. */ private final int mDisplayId; @@ -237,6 +253,11 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo * @see WindowManagerService#setForcedDisplayDensityForUser(int, int, int) */ int mBaseDisplayDensity = 0; + + /** + * Whether to disable display scaling. This can be set via shell command "adb shell wm scaling". + * @see WindowManagerService#setForcedDisplayScalingMode(int, int) + */ boolean mDisplayScalingDisabled; private final DisplayInfo mDisplayInfo = new DisplayInfo(); private final Display mDisplay; @@ -836,6 +857,7 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo // {@link DisplayContent} ready for use. mDisplayReady = true; + mService.mAnimator.addDisplayLocked(mDisplayId); mInputMonitor = new InputMonitor(service, mDisplayId); if (mService.mInputManager != null) { @@ -1790,7 +1812,7 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo // The display size information is heavily dependent on the resources in the current // configuration, so we need to reconfigure it every time the configuration changes. - // See {@link PhoneWindowManager#setInitialDisplaySize}...sigh... + // See {@link #configureDisplayPolicy}...sigh... mService.reconfigureDisplayLocked(this); final DockedStackDividerController dividerController = getDockedDividerController(); @@ -2027,6 +2049,68 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo updateBounds(); } + /** + * Forces this display to use the specified density. + * + * @param density The density in DPI to use. If the value equals to initial density, the setting + * will be cleared. + * @param userId The target user to apply. Only meaningful when this is default display. If the + * user id is {@link UserHandle#USER_CURRENT}, it means to apply current settings + * so only need to configure display. + */ + void setForcedDensity(int density, int userId) { + final boolean clear = density == mInitialDisplayDensity; + final boolean updateCurrent = userId == UserHandle.USER_CURRENT; + if (mService.mCurrentUserId == userId || updateCurrent) { + mBaseDisplayDensity = density; + mService.reconfigureDisplayLocked(this); + } + if (updateCurrent) { + // We are applying existing settings so no need to save it again. + return; + } + + if (density == mInitialDisplayDensity) { + density = 0; + } + mService.mDisplaySettings.setForcedDensity(this, density, userId); + } + + /** @param mode {@link #FORCE_SCALING_MODE_AUTO} or {@link #FORCE_SCALING_MODE_DISABLED}. */ + void setForcedScalingMode(@ForceScalingMode int mode) { + if (mode != FORCE_SCALING_MODE_DISABLED) { + mode = FORCE_SCALING_MODE_AUTO; + } + + mDisplayScalingDisabled = (mode != FORCE_SCALING_MODE_AUTO); + Slog.i(TAG_WM, "Using display scaling mode: " + (mDisplayScalingDisabled ? "off" : "auto")); + mService.reconfigureDisplayLocked(this); + + mService.mDisplaySettings.setForcedScalingMode(this, mode); + } + + /** If the given width and height equal to initial size, the setting will be cleared. */ + void setForcedSize(int width, int height) { + final boolean clear = mInitialDisplayWidth == width && mInitialDisplayHeight == height; + if (!clear) { + // Set some sort of reasonable bounds on the size of the display that we will try + // to emulate. + final int minSize = 200; + final int maxScale = 2; + width = Math.min(Math.max(width, minSize), mInitialDisplayWidth * maxScale); + height = Math.min(Math.max(height, minSize), mInitialDisplayHeight * maxScale); + } + + Slog.i(TAG_WM, "Using new display size: " + width + "x" + height); + updateBaseDisplayMetrics(width, height, mBaseDisplayDensity); + mService.reconfigureDisplayLocked(this); + + if (clear) { + width = height = 0; + } + mService.mDisplaySettings.setForcedSize(this, width, height); + } + void getStableRect(Rect out) { out.set(mDisplayFrames.mStable); } @@ -2225,7 +2309,7 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo } mInputMonitor.onRemoved(); - mService.onDisplayRemoved(mDisplayId); + mService.mWindowPlacerLocked.requestTraversal(); } /** Returns true if a removal action is still being deferred. */ diff --git a/services/core/java/com/android/server/wm/DisplayRotation.java b/services/core/java/com/android/server/wm/DisplayRotation.java index 847cff9c6646..9f98dc5ee5c7 100644 --- a/services/core/java/com/android/server/wm/DisplayRotation.java +++ b/services/core/java/com/android/server/wm/DisplayRotation.java @@ -269,7 +269,6 @@ public class DisplayRotation { if (changed) { mService.updateRotation(true /* alwaysSendConfiguration */, false /* forceRelayout */); - mService.mDisplaySettings.writeSettingsLocked(); } } diff --git a/services/core/java/com/android/server/wm/DisplaySettings.java b/services/core/java/com/android/server/wm/DisplaySettings.java index 28dc008fda9b..44956ab6a762 100644 --- a/services/core/java/com/android/server/wm/DisplaySettings.java +++ b/services/core/java/com/android/server/wm/DisplaySettings.java @@ -16,12 +16,14 @@ package com.android.server.wm; +import static com.android.server.wm.DisplayContent.FORCE_SCALING_MODE_AUTO; +import static com.android.server.wm.DisplayContent.FORCE_SCALING_MODE_DISABLED; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; import android.app.WindowConfiguration; -import android.graphics.Rect; import android.os.Environment; +import android.provider.Settings; import android.util.AtomicFile; import android.util.Slog; import android.util.Xml; @@ -33,6 +35,7 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.FastXmlSerializer; import com.android.internal.util.XmlUtils; import com.android.server.policy.WindowManagerPolicy; +import com.android.server.wm.DisplayContent.ForceScalingMode; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; @@ -65,17 +68,24 @@ class DisplaySettings { private int mWindowingMode = WindowConfiguration.WINDOWING_MODE_UNDEFINED; private int mUserRotationMode = WindowManagerPolicy.USER_ROTATION_FREE; private int mUserRotation = Surface.ROTATION_0; + private int mForcedWidth; + private int mForcedHeight; + private int mForcedDensity; + private int mForcedScalingMode = FORCE_SCALING_MODE_AUTO; private Entry(String _name) { mName = _name; } + /** @return {@code true} if all values are default. */ private boolean isEmpty() { return mOverscanLeft == 0 && mOverscanTop == 0 && mOverscanRight == 0 && mOverscanBottom == 0 && mWindowingMode == WindowConfiguration.WINDOWING_MODE_UNDEFINED && mUserRotationMode == WindowManagerPolicy.USER_ROTATION_FREE - && mUserRotation == Surface.ROTATION_0; + && mUserRotation == Surface.ROTATION_0 + && mForcedWidth == 0 && mForcedHeight == 0 && mForcedDensity == 0 + && mForcedScalingMode == FORCE_SCALING_MODE_AUTO; } } @@ -87,64 +97,84 @@ class DisplaySettings { DisplaySettings(WindowManagerService service, File folder) { mService = service; mFile = new AtomicFile(new File(folder, "display_settings.xml"), "wm-displays"); + readSettings(); } - private Entry getEntry(String name, String uniqueId) { + private Entry getEntry(DisplayInfo displayInfo) { // Try to get the entry with the unique if possible. // Else, fall back on the display name. Entry entry; - if (uniqueId == null || (entry = mEntries.get(uniqueId)) == null) { - entry = mEntries.get(name); + if (displayInfo.uniqueId == null || (entry = mEntries.get(displayInfo.uniqueId)) == null) { + entry = mEntries.get(displayInfo.name); } return entry; } - private Entry getOrCreateEntry(String uniqueId, String name) { - Entry entry = getEntry(uniqueId, name); - if (entry == null) { - entry = new Entry(uniqueId); - mEntries.put(uniqueId, entry); - } - return entry; + private Entry getOrCreateEntry(DisplayInfo displayInfo) { + final Entry entry = getEntry(displayInfo); + return entry != null ? entry : new Entry(displayInfo.uniqueId); + } + + void setOverscanLocked(DisplayInfo displayInfo, int left, int top, int right, int bottom) { + final Entry entry = getOrCreateEntry(displayInfo); + entry.mOverscanLeft = left; + entry.mOverscanTop = top; + entry.mOverscanRight = right; + entry.mOverscanBottom = bottom; + writeSettingsIfNeeded(entry, displayInfo); + } + + void setUserRotation(DisplayContent displayContent, int rotationMode, int rotation) { + final DisplayInfo displayInfo = displayContent.getDisplayInfo(); + final Entry entry = getOrCreateEntry(displayInfo); + entry.mUserRotationMode = rotationMode; + entry.mUserRotation = rotation; + writeSettingsIfNeeded(entry, displayInfo); } - private void removeEntryIfEmpty(String uniqueId, String name) { - final Entry entry = getEntry(uniqueId, name); - if (entry.isEmpty()) { - mEntries.remove(uniqueId); - mEntries.remove(name); + void setForcedSize(DisplayContent displayContent, int width, int height) { + if (displayContent.isDefaultDisplay) { + final String sizeString = (width == 0 || height == 0) ? "" : (width + "," + height); + Settings.Global.putString(mService.mContext.getContentResolver(), + Settings.Global.DISPLAY_SIZE_FORCED, sizeString); + return; } + + final DisplayInfo displayInfo = displayContent.getDisplayInfo(); + final Entry entry = getOrCreateEntry(displayInfo); + entry.mForcedWidth = width; + entry.mForcedHeight = height; + writeSettingsIfNeeded(entry, displayInfo); } - private void getOverscanLocked(String name, String uniqueId, Rect outRect) { - final Entry entry = getEntry(name, uniqueId); - if (entry != null) { - outRect.left = entry.mOverscanLeft; - outRect.top = entry.mOverscanTop; - outRect.right = entry.mOverscanRight; - outRect.bottom = entry.mOverscanBottom; - } else { - outRect.set(0, 0, 0, 0); + void setForcedDensity(DisplayContent displayContent, int density, int userId) { + if (displayContent.isDefaultDisplay) { + final String densityString = density == 0 ? "" : Integer.toString(density); + Settings.Secure.putStringForUser(mService.mContext.getContentResolver(), + Settings.Secure.DISPLAY_DENSITY_FORCED, densityString, userId); + return; } + + final DisplayInfo displayInfo = displayContent.getDisplayInfo(); + final Entry entry = getOrCreateEntry(displayInfo); + entry.mForcedDensity = density; + writeSettingsIfNeeded(entry, displayInfo); } - void setOverscanLocked(String uniqueId, String name, int left, int top, int right, - int bottom) { - Entry entry = mEntries.get(uniqueId); - if (left == 0 && top == 0 && right == 0 && bottom == 0 && entry == null) { - // All default value, no action needed. + void setForcedScalingMode(DisplayContent displayContent, @ForceScalingMode int mode) { + if (displayContent.isDefaultDisplay) { + Settings.Global.putInt(mService.mContext.getContentResolver(), + Settings.Global.DISPLAY_SCALING_FORCE, mode); return; } - entry = getOrCreateEntry(uniqueId, name); - entry.mOverscanLeft = left; - entry.mOverscanTop = top; - entry.mOverscanRight = right; - entry.mOverscanBottom = bottom; - removeEntryIfEmpty(uniqueId, name); + + final DisplayInfo displayInfo = displayContent.getDisplayInfo(); + final Entry entry = getOrCreateEntry(displayInfo); + entry.mForcedScalingMode = mode; + writeSettingsIfNeeded(entry, displayInfo); } - private int getWindowingModeLocked(String name, String uniqueId, int displayId) { - final Entry entry = getEntry(name, uniqueId); + private int getWindowingModeLocked(Entry entry, int displayId) { int windowingMode = entry != null ? entry.mWindowingMode : WindowConfiguration.WINDOWING_MODE_UNDEFINED; // This display used to be in freeform, but we don't support freeform anymore, so fall @@ -168,54 +198,35 @@ class DisplaySettings { return windowingMode; } - void setUserRotation(DisplayContent dc, int rotationMode, int rotation) { + void applySettingsToDisplayLocked(DisplayContent dc) { final DisplayInfo displayInfo = dc.getDisplayInfo(); + final Entry entry = getEntry(displayInfo); - final String uniqueId = displayInfo.uniqueId; - final String name = displayInfo.name; - Entry entry = getEntry(displayInfo.name, uniqueId); - if (rotationMode == WindowManagerPolicy.USER_ROTATION_FREE - && rotation == Surface.ROTATION_0 && entry == null) { - // All default values. No action needed. + // Setting windowing mode first, because it may override overscan values later. + dc.setWindowingMode(getWindowingModeLocked(entry, dc.getDisplayId())); + + if (entry == null) { return; } - entry = getOrCreateEntry(uniqueId, name); - entry.mUserRotationMode = rotationMode; - entry.mUserRotation = rotation; - removeEntryIfEmpty(uniqueId, name); - } - - private void restoreUserRotation(DisplayContent dc) { - final DisplayInfo info = dc.getDisplayInfo(); - - final Entry entry = getEntry(info.name, info.uniqueId); - final int userRotationMode = entry != null ? entry.mUserRotationMode - : WindowManagerPolicy.USER_ROTATION_FREE; - final int userRotation = entry != null ? entry.mUserRotation - : Surface.ROTATION_0; - - dc.getDisplayRotation().restoreUserRotation(userRotationMode, userRotation); - } - - void applySettingsToDisplayLocked(DisplayContent dc) { - final DisplayInfo displayInfo = dc.getDisplayInfo(); - - // Setting windowing mode first, because it may override overscan values later. - dc.setWindowingMode(getWindowingModeLocked(displayInfo.name, displayInfo.uniqueId, - dc.getDisplayId())); + displayInfo.overscanLeft = entry.mOverscanLeft; + displayInfo.overscanTop = entry.mOverscanTop; + displayInfo.overscanRight = entry.mOverscanRight; + displayInfo.overscanBottom = entry.mOverscanBottom; - final Rect rect = new Rect(); - getOverscanLocked(displayInfo.name, displayInfo.uniqueId, rect); - displayInfo.overscanLeft = rect.left; - displayInfo.overscanTop = rect.top; - displayInfo.overscanRight = rect.right; - displayInfo.overscanBottom = rect.bottom; + dc.getDisplayRotation().restoreUserRotation(entry.mUserRotationMode, entry.mUserRotation); - restoreUserRotation(dc); + if (entry.mForcedDensity != 0) { + dc.mBaseDisplayDensity = entry.mForcedDensity; + } + if (entry.mForcedWidth != 0 && entry.mForcedHeight != 0) { + dc.updateBaseDisplayMetrics(entry.mForcedWidth, entry.mForcedHeight, + dc.mBaseDisplayDensity); + } + dc.mDisplayScalingDisabled = entry.mForcedScalingMode == FORCE_SCALING_MODE_DISABLED; } - void readSettingsLocked() { + private void readSettings() { FileInputStream stream; try { stream = mFile.openRead(); @@ -306,12 +317,29 @@ class DisplaySettings { WindowManagerPolicy.USER_ROTATION_FREE); entry.mUserRotation = getIntAttribute(parser, "userRotation", Surface.ROTATION_0); + entry.mForcedWidth = getIntAttribute(parser, "forcedWidth"); + entry.mForcedHeight = getIntAttribute(parser, "forcedHeight"); + entry.mForcedDensity = getIntAttribute(parser, "forcedDensity"); + entry.mForcedScalingMode = getIntAttribute(parser, "forcedScalingMode", + FORCE_SCALING_MODE_AUTO); mEntries.put(name, entry); } XmlUtils.skipCurrentTag(parser); } - void writeSettingsLocked() { + private void writeSettingsIfNeeded(Entry changedEntry, DisplayInfo displayInfo) { + if (changedEntry.isEmpty()) { + boolean removed = mEntries.remove(displayInfo.uniqueId) != null; + // Legacy name might have been in used, so we need to clear it. + removed |= mEntries.remove(displayInfo.name) != null; + if (!removed) { + // The entry didn't exist so nothing is changed and no need to update the file. + return; + } + } else { + mEntries.put(displayInfo.uniqueId, changedEntry); + } + FileOutputStream stream; try { stream = mFile.startWrite(); @@ -351,6 +379,17 @@ class DisplaySettings { if (entry.mUserRotation != Surface.ROTATION_0) { out.attribute(null, "userRotation", Integer.toString(entry.mUserRotation)); } + if (entry.mForcedWidth != 0 && entry.mForcedHeight != 0) { + out.attribute(null, "forcedWidth", Integer.toString(entry.mForcedWidth)); + out.attribute(null, "forcedHeight", Integer.toString(entry.mForcedHeight)); + } + if (entry.mForcedDensity != 0) { + out.attribute(null, "forcedDensity", Integer.toString(entry.mForcedDensity)); + } + if (entry.mForcedScalingMode != FORCE_SCALING_MODE_AUTO) { + out.attribute(null, "forcedScalingMode", + Integer.toString(entry.mForcedScalingMode)); + } out.endTag(null, "display"); } diff --git a/services/core/java/com/android/server/wm/DisplayWindowController.java b/services/core/java/com/android/server/wm/DisplayWindowController.java index ab8775983291..3282b1c8848a 100644 --- a/services/core/java/com/android/server/wm/DisplayWindowController.java +++ b/services/core/java/com/android/server/wm/DisplayWindowController.java @@ -81,6 +81,22 @@ public class DisplayWindowController } /** + * Called when the corresponding display receives + * {@link android.hardware.display.DisplayManager.DisplayListener#onDisplayChanged(int)}. + */ + public void onDisplayChanged() { + synchronized (mWindowMap) { + if (mContainer == null) { + if (DEBUG_DISPLAY) Slog.i(TAG_WM, "onDisplayChanged: could not find display=" + + mDisplayId); + return; + } + mContainer.updateDisplayInfo(); + mService.mWindowPlacerLocked.requestTraversal(); + } + } + + /** * Positions the task stack at the given position in the task stack container. */ public void positionChildAt(StackWindowController child, int position, diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index 6aa0e0144c40..2a529d95fcff 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -45,6 +45,7 @@ import android.graphics.Rect; import android.util.EventLog; import android.util.Slog; import android.util.proto.ProtoOutputStream; +import android.view.Display; import android.view.Surface; import android.view.SurfaceControl; @@ -68,7 +69,12 @@ class Task extends WindowContainer<AppWindowToken> { // Bounds used to calculate the insets. private final Rect mTempInsetBounds = new Rect(); - // Device rotation as of the last time {@link #mBounds} was set. + /** ID of the display which rotation {@link #mRotation} has. */ + private int mLastRotationDisplayId = Display.INVALID_DISPLAY; + /** + * Display rotation as of the last time {@link #setBounds(Rect)} was called or this task was + * moved to a new display. + */ private int mRotation; // For comparison with DisplayContent bounds. @@ -510,8 +516,20 @@ class Task extends WindowContainer<AppWindowToken> { setBounds(null); return; } + final int displayId = displayContent.getDisplayId(); final int newRotation = displayContent.getDisplayInfo().rotation; + if (displayId != mLastRotationDisplayId) { + // This task is on a display that it wasn't on. There is no point to keep the relative + // position if display rotations for old and new displays are different. Just keep these + // values. + mLastRotationDisplayId = displayId; + mRotation = newRotation; + return; + } + if (mRotation == newRotation) { + // Rotation didn't change. We don't need to adjust the bounds to keep the relative + // position. return; } diff --git a/services/core/java/com/android/server/wm/TaskStack.java b/services/core/java/com/android/server/wm/TaskStack.java index 00cacebe2960..3d27bab7d8ed 100644 --- a/services/core/java/com/android/server/wm/TaskStack.java +++ b/services/core/java/com/android/server/wm/TaskStack.java @@ -32,6 +32,7 @@ import static android.view.WindowManager.DOCKED_INVALID; import static android.view.WindowManager.DOCKED_LEFT; import static android.view.WindowManager.DOCKED_RIGHT; import static android.view.WindowManager.DOCKED_TOP; + import static com.android.server.wm.DragResizeMode.DRAG_RESIZE_MODE_DOCKED_DIVIDER; import static com.android.server.wm.StackProto.ADJUSTED_BOUNDS; import static com.android.server.wm.StackProto.ADJUSTED_FOR_IME; @@ -63,10 +64,12 @@ import android.util.proto.ProtoOutputStream; import android.view.DisplayInfo; import android.view.Surface; import android.view.SurfaceControl; + import com.android.internal.policy.DividerSnapAlgorithm; import com.android.internal.policy.DividerSnapAlgorithm.SnapTarget; import com.android.internal.policy.DockedDividerUtils; import com.android.server.EventLogTags; + import java.io.PrintWriter; public class TaskStack extends WindowContainer<Task> implements @@ -99,7 +102,7 @@ public class TaskStack extends WindowContainer<Task> implements */ private final Rect mFullyAdjustedImeBounds = new Rect(); - // Device rotation as of the last time {@link #mBounds} was set. + // Display rotation as of the last time {@link #mBounds} was set. private int mRotation; /** Density as of last time {@link #mBounds} was set. */ diff --git a/services/core/java/com/android/server/wm/WindowAnimator.java b/services/core/java/com/android/server/wm/WindowAnimator.java index ad0b8ece7a80..bc0f19aa1b3a 100644 --- a/services/core/java/com/android/server/wm/WindowAnimator.java +++ b/services/core/java/com/android/server/wm/WindowAnimator.java @@ -105,9 +105,6 @@ public class WindowAnimator { // Create the DisplayContentsAnimator object by retrieving it if the associated // {@link DisplayContent} exists. getDisplayContentsAnimatorLocked(displayId); - if (displayId == DEFAULT_DISPLAY) { - mInitialized = true; - } } void removeDisplayLocked(final int displayId) { @@ -122,6 +119,10 @@ public class WindowAnimator { mDisplayContentsAnimators.delete(displayId); } + void ready() { + mInitialized = true; + } + /** * DO NOT HOLD THE WINDOW MANAGER LOCK WHILE CALLING THIS METHOD. Reason: the method closes * an animation transaction, that might be blocking until the next sf-vsync, so we want to make diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index 5642b1f91700..3aad73c28f78 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -930,7 +930,6 @@ public class WindowManagerService extends IWindowManager.Stub mInputManager = inputManager; // Must be before createDisplayContentLocked. mDisplayManagerInternal = LocalServices.getService(DisplayManagerInternal.class); mDisplaySettings = new DisplaySettings(this); - mDisplaySettings.readSettingsLocked(); mPolicy = policy; mAnimator = new WindowAnimator(this); @@ -3241,7 +3240,7 @@ public class WindowManagerService extends IWindowManager.Stub final int forcedDensity = getForcedDisplayDensityForUserLocked(newUserId); final int targetDensity = forcedDensity != 0 ? forcedDensity : displayContent.mInitialDisplayDensity; - setForcedDisplayDensityLocked(displayContent, targetDensity); + displayContent.setForcedDensity(targetDensity, UserHandle.USER_CURRENT); } } } @@ -4415,31 +4414,15 @@ public class WindowManagerService extends IWindowManager.Stub } public void displayReady() { - final int displayCount = mRoot.mChildren.size(); - for (int i = 0; i < displayCount; ++i) { - final DisplayContent display = mRoot.mChildren.get(i); - displayReady(display.getDisplayId()); - } - - - synchronized(mWindowMap) { - final DisplayContent displayContent = getDefaultDisplayContentLocked(); + synchronized (mWindowMap) { if (mMaxUiWidth > 0) { - displayContent.setMaxUiWidth(mMaxUiWidth); + mRoot.forAllDisplays(displayContent -> displayContent.setMaxUiWidth(mMaxUiWidth)); } - readForcedDisplayPropertiesLocked(displayContent); + applyForcedPropertiesForDefaultDisplay(); + mAnimator.ready(); mDisplayReady = true; - } - - try { - mActivityTaskManager.updateConfiguration(null); - } catch (RemoteException e) { - } - - synchronized(mWindowMap) { mIsTouchDevice = mContext.getPackageManager().hasSystemFeature( PackageManager.FEATURE_TOUCHSCREEN); - getDefaultDisplayContentLocked().configureDisplayPolicy(); } try { @@ -4450,17 +4433,6 @@ public class WindowManagerService extends IWindowManager.Stub updateCircularDisplayMaskIfNeeded(); } - private void displayReady(int displayId) { - synchronized(mWindowMap) { - final DisplayContent displayContent = mRoot.getDisplayContent(displayId); - if (displayContent != null) { - mAnimator.addDisplayLocked(displayId); - displayContent.initializeDisplayBaseInfo(); - reconfigureDisplayLocked(displayContent); - } - } - } - public void systemReady() { mPolicy.systemReady(); mTaskSnapshotController.systemReady(); @@ -5017,20 +4989,9 @@ public class WindowManagerService extends IWindowManager.Stub final long ident = Binder.clearCallingIdentity(); try { synchronized(mWindowMap) { - // Set some sort of reasonable bounds on the size of the display that we - // will try to emulate. - final int MIN_WIDTH = 200; - final int MIN_HEIGHT = 200; - final int MAX_SCALE = 2; final DisplayContent displayContent = mRoot.getDisplayContent(displayId); if (displayContent != null) { - width = Math.min(Math.max(width, MIN_WIDTH), - displayContent.mInitialDisplayWidth * MAX_SCALE); - height = Math.min(Math.max(height, MIN_HEIGHT), - displayContent.mInitialDisplayHeight * MAX_SCALE); - setForcedDisplaySizeLocked(displayContent, width, height); - Settings.Global.putString(mContext.getContentResolver(), - Settings.Global.DISPLAY_SIZE_FORCED, width + "," + height); + displayContent.setForcedSize(width, height); } } } finally { @@ -5052,12 +5013,7 @@ public class WindowManagerService extends IWindowManager.Stub synchronized(mWindowMap) { final DisplayContent displayContent = mRoot.getDisplayContent(displayId); if (displayContent != null) { - if (mode < 0 || mode > 1) { - mode = 0; - } - setForcedDisplayScalingModeLocked(displayContent, mode); - Settings.Global.putInt(mContext.getContentResolver(), - Settings.Global.DISPLAY_SCALING_FORCE, mode); + displayContent.setForcedScalingMode(mode); } } } finally { @@ -5065,13 +5021,10 @@ public class WindowManagerService extends IWindowManager.Stub } } - private void setForcedDisplayScalingModeLocked(DisplayContent displayContent, int mode) { - Slog.i(TAG_WM, "Using display scaling mode: " + (mode == 0 ? "auto" : "off")); - displayContent.mDisplayScalingDisabled = (mode != 0); - reconfigureDisplayLocked(displayContent); - } - - private void readForcedDisplayPropertiesLocked(final DisplayContent displayContent) { + /** The global settings only apply to default display. */ + private void applyForcedPropertiesForDefaultDisplay() { + final DisplayContent displayContent = getDefaultDisplayContentLocked(); + boolean changed = false; // Display size. String sizeStr = Settings.Global.getString(mContext.getContentResolver(), Settings.Global.DISPLAY_SIZE_FORCED); @@ -5090,6 +5043,7 @@ public class WindowManagerService extends IWindowManager.Stub Slog.i(TAG_WM, "FORCED DISPLAY SIZE: " + width + "x" + height); displayContent.updateBaseDisplayMetrics(width, height, displayContent.mBaseDisplayDensity); + changed = true; } } catch (NumberFormatException ex) { } @@ -5100,6 +5054,7 @@ public class WindowManagerService extends IWindowManager.Stub final int density = getForcedDisplayDensityForUserLocked(mCurrentUserId); if (density != 0) { displayContent.mBaseDisplayDensity = density; + changed = true; } // Display scaling mode. @@ -5108,14 +5063,12 @@ public class WindowManagerService extends IWindowManager.Stub if (mode != 0) { Slog.i(TAG_WM, "FORCED DISPLAY SCALING DISABLED"); displayContent.mDisplayScalingDisabled = true; + changed = true; } - } - // displayContent must not be null - private void setForcedDisplaySizeLocked(DisplayContent displayContent, int width, int height) { - Slog.i(TAG_WM, "Using new display size: " + width + "x" + height); - displayContent.updateBaseDisplayMetrics(width, height, displayContent.mBaseDisplayDensity); - reconfigureDisplayLocked(displayContent); + if (changed) { + reconfigureDisplayLocked(displayContent); + } } @Override @@ -5132,10 +5085,8 @@ public class WindowManagerService extends IWindowManager.Stub synchronized(mWindowMap) { final DisplayContent displayContent = mRoot.getDisplayContent(displayId); if (displayContent != null) { - setForcedDisplaySizeLocked(displayContent, displayContent.mInitialDisplayWidth, + displayContent.setForcedSize(displayContent.mInitialDisplayWidth, displayContent.mInitialDisplayHeight); - Settings.Global.putString(mContext.getContentResolver(), - Settings.Global.DISPLAY_SIZE_FORCED, ""); } } } finally { @@ -5181,12 +5132,9 @@ public class WindowManagerService extends IWindowManager.Stub try { synchronized(mWindowMap) { final DisplayContent displayContent = mRoot.getDisplayContent(displayId); - if (displayContent != null && mCurrentUserId == targetUserId) { - setForcedDisplayDensityLocked(displayContent, density); + if (displayContent != null) { + displayContent.setForcedDensity(density, targetUserId); } - Settings.Secure.putStringForUser(mContext.getContentResolver(), - Settings.Secure.DISPLAY_DENSITY_FORCED, - Integer.toString(density), targetUserId); } } finally { Binder.restoreCallingIdentity(ident); @@ -5209,12 +5157,10 @@ public class WindowManagerService extends IWindowManager.Stub try { synchronized(mWindowMap) { final DisplayContent displayContent = mRoot.getDisplayContent(displayId); - if (displayContent != null && mCurrentUserId == callingUserId) { - setForcedDisplayDensityLocked(displayContent, - displayContent.mInitialDisplayDensity); + if (displayContent != null) { + displayContent.setForcedDensity(displayContent.mInitialDisplayDensity, + callingUserId); } - Settings.Secure.putStringForUser(mContext.getContentResolver(), - Settings.Secure.DISPLAY_DENSITY_FORCED, "", callingUserId); } } finally { Binder.restoreCallingIdentity(ident); @@ -5241,18 +5187,6 @@ public class WindowManagerService extends IWindowManager.Stub return 0; } - /** - * Forces the given display to the use the specified density. - * - * @param displayContent the display to modify - * @param density the density in DPI to use - */ - private void setForcedDisplayDensityLocked(@NonNull DisplayContent displayContent, - int density) { - displayContent.mBaseDisplayDensity = density; - reconfigureDisplayLocked(displayContent); - } - void reconfigureDisplayLocked(@NonNull DisplayContent displayContent) { if (!displayContent.isReady()) { return; @@ -5306,9 +5240,7 @@ public class WindowManagerService extends IWindowManager.Stub displayInfo.overscanRight = right; displayInfo.overscanBottom = bottom; - mDisplaySettings.setOverscanLocked(displayInfo.uniqueId, displayInfo.name, left, top, - right, bottom); - mDisplaySettings.writeSettingsLocked(); + mDisplaySettings.setOverscanLocked(displayInfo, left, top, right, bottom); reconfigureDisplayLocked(displayContent); } @@ -6507,23 +6439,6 @@ public class WindowManagerService extends IWindowManager.Stub return mRoot.getDisplayContent(DEFAULT_DISPLAY); } - public void onDisplayAdded(int displayId) { - synchronized (mWindowMap) { - final Display display = mDisplayManager.getDisplay(displayId); - if (display != null) { - displayReady(displayId); - } - mWindowPlacerLocked.requestTraversal(); - } - } - - public void onDisplayRemoved(int displayId) { - synchronized (mWindowMap) { - mAnimator.removeDisplayLocked(displayId); - mWindowPlacerLocked.requestTraversal(); - } - } - public void onOverlayChanged() { synchronized (mWindowMap) { mRoot.forAllDisplays(displayContent -> { diff --git a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java index bf2d0df2bec3..bf77ba86075d 100644 --- a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java +++ b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java @@ -233,9 +233,11 @@ public class WindowManagerShellCommand extends ShellCommand { private int runDisplayScaling(PrintWriter pw) throws RemoteException { String scalingStr = getNextArgRequired(); if ("auto".equals(scalingStr)) { - mInterface.setForcedDisplayScalingMode(getDisplayId(scalingStr), 0); + mInterface.setForcedDisplayScalingMode(getDisplayId(scalingStr), + DisplayContent.FORCE_SCALING_MODE_AUTO); } else if ("off".equals(scalingStr)) { - mInterface.setForcedDisplayScalingMode(getDisplayId(scalingStr), 1); + mInterface.setForcedDisplayScalingMode(getDisplayId(scalingStr), + DisplayContent.FORCE_SCALING_MODE_DISABLED); } else { getErrPrintWriter().println("Error: scaling must be 'auto' or 'off'"); return -1; diff --git a/services/core/jni/com_android_server_location_GnssLocationProvider.cpp b/services/core/jni/com_android_server_location_GnssLocationProvider.cpp index b47dbfa58669..a4983a96afd8 100644 --- a/services/core/jni/com_android_server_location_GnssLocationProvider.cpp +++ b/services/core/jni/com_android_server_location_GnssLocationProvider.cpp @@ -40,7 +40,7 @@ #include <cinttypes> #include <iomanip> -static jobject mCallbacksObj = NULL; +static jobject mCallbacksObj = nullptr; static jmethodID method_reportLocation; static jmethodID method_reportStatus; @@ -717,7 +717,7 @@ Return<void> GnssNavigationMessageCallback::gnssNavigationMessageCb( std::vector<uint8_t> navigationData = message.data; uint8_t* data = &(navigationData[0]); - if (dataLength == 0 || data == NULL) { + if (dataLength == 0 || data == nullptr) { ALOGE("Invalid Navigation Message found: data=%p, length=%zd", data, dataLength); return Void(); @@ -749,7 +749,7 @@ struct GnssMeasurementCallback : public IGnssMeasurementCallback_V1_1 { Return<void> GnssMeasurementCb(const IGnssMeasurementCallback_V1_0::GnssData& data) override; private: void translateGnssMeasurement_V1_0( - JNIEnv* env, const IGnssMeasurementCallback_V1_0::GnssMeasurement* measurement, + const IGnssMeasurementCallback_V1_0::GnssMeasurement* measurement, JavaObject& object); jobjectArray translateGnssMeasurements( JNIEnv* env, @@ -772,7 +772,7 @@ Return<void> GnssMeasurementCallback::gnssMeasurementCb( clock = translateGnssClock(env, &data.clock); measurementArray = translateGnssMeasurements( - env, data.measurements.data(), NULL, data.measurements.size()); + env, data.measurements.data(), nullptr, data.measurements.size()); setMeasurementData(env, clock, measurementArray); env->DeleteLocalRef(clock); @@ -789,7 +789,7 @@ Return<void> GnssMeasurementCallback::GnssMeasurementCb( clock = translateGnssClock(env, &data.clock); measurementArray = translateGnssMeasurements( - env, NULL, data.measurements.data(), data.measurementCount); + env, nullptr, data.measurements.data(), data.measurementCount); setMeasurementData(env, clock, measurementArray); env->DeleteLocalRef(clock); @@ -799,7 +799,7 @@ Return<void> GnssMeasurementCallback::GnssMeasurementCb( // preallocate object as: JavaObject object(env, "android/location/GnssMeasurement"); void GnssMeasurementCallback::translateGnssMeasurement_V1_0( - JNIEnv* env, const IGnssMeasurementCallback_V1_0::GnssMeasurement* measurement, + const IGnssMeasurementCallback_V1_0::GnssMeasurement* measurement, JavaObject& object) { uint32_t flags = static_cast<uint32_t>(measurement->flags); @@ -816,7 +816,7 @@ void GnssMeasurementCallback::translateGnssMeasurement_V1_0( measurement->pseudorangeRateUncertaintyMps); SET(AccumulatedDeltaRangeState, (static_cast<int32_t>(measurement->accumulatedDeltaRangeState) & - !ADR_STATE_HALF_CYCLE_REPORTED)); // Half Cycle state not reported from Hardware in V1_0 + ~ADR_STATE_HALF_CYCLE_REPORTED)); // Half Cycle state not reported from Hardware in V1_0 SET(AccumulatedDeltaRangeMeters, measurement->accumulatedDeltaRangeM); SET(AccumulatedDeltaRangeUncertaintyMeters, measurement->accumulatedDeltaRangeUncertaintyM); @@ -883,26 +883,26 @@ jobjectArray GnssMeasurementCallback::translateGnssMeasurements(JNIEnv* env, const IGnssMeasurementCallback_V1_0::GnssMeasurement* measurements_v1_0, size_t count) { if (count == 0) { - return NULL; + return nullptr; } jclass gnssMeasurementClass = env->FindClass("android/location/GnssMeasurement"); jobjectArray gnssMeasurementArray = env->NewObjectArray( count, gnssMeasurementClass, - NULL /* initialElement */); + nullptr /* initialElement */); for (uint16_t i = 0; i < count; ++i) { JavaObject object(env, "android/location/GnssMeasurement"); - if (measurements_v1_1 != NULL) { - translateGnssMeasurement_V1_0(env, &(measurements_v1_1[i].v1_0), object); + if (measurements_v1_1 != nullptr) { + translateGnssMeasurement_V1_0(&(measurements_v1_1[i].v1_0), object); // Set the V1_1 flag, and mark that new field has valid information for Java Layer SET(AccumulatedDeltaRangeState, (static_cast<int32_t>(measurements_v1_1[i].accumulatedDeltaRangeState) | ADR_STATE_HALF_CYCLE_REPORTED)); } else { - translateGnssMeasurement_V1_0(env, &(measurements_v1_0[i]), object); + translateGnssMeasurement_V1_0(&(measurements_v1_0[i]), object); } env->SetObjectArrayElement(gnssMeasurementArray, i, object.get()); @@ -987,14 +987,12 @@ struct AGnssCallback : public IAGnssCallback { Return<void> AGnssCallback::agnssStatusIpV6Cb( const IAGnssCallback::AGnssStatusIpV6& agps_status) { JNIEnv* env = getJniEnv(); - jbyteArray byteArray = NULL; - bool isSupported = false; + jbyteArray byteArray = nullptr; byteArray = env->NewByteArray(16); - if (byteArray != NULL) { + if (byteArray != nullptr) { env->SetByteArrayRegion(byteArray, 0, 16, (const jbyte*)(agps_status.ipV6Addr.data())); - isSupported = true; } else { ALOGE("Unable to allocate byte array for IPv6 address."); } @@ -1006,7 +1004,7 @@ Return<void> AGnssCallback::agnssStatusIpV6Cb( ALOGD("AGPS IP is v6: %s", str); } - jsize byteArrayLength = byteArray != NULL ? env->GetArrayLength(byteArray) : 0; + jsize byteArrayLength = byteArray != nullptr ? env->GetArrayLength(byteArray) : 0; ALOGV("Passing AGPS IP addr: size %d", byteArrayLength); env->CallVoidMethod(mCallbacksObj, method_reportAGpsStatus, agps_status.type, agps_status.status, byteArray); @@ -1023,7 +1021,7 @@ Return<void> AGnssCallback::agnssStatusIpV6Cb( Return<void> AGnssCallback::agnssStatusIpV4Cb( const IAGnssCallback::AGnssStatusIpV4& agps_status) { JNIEnv* env = getJniEnv(); - jbyteArray byteArray = NULL; + jbyteArray byteArray = nullptr; uint32_t ipAddr = agps_status.ipV4Addr; byteArray = convertToIpV4(ipAddr); @@ -1039,7 +1037,7 @@ Return<void> AGnssCallback::agnssStatusIpV4Cb( } jsize byteArrayLength = - byteArray != NULL ? env->GetArrayLength(byteArray) : 0; + byteArray != nullptr ? env->GetArrayLength(byteArray) : 0; ALOGV("Passing AGPS IP addr: size %d", byteArrayLength); env->CallVoidMethod(mCallbacksObj, method_reportAGpsStatus, agps_status.type, agps_status.status, byteArray); @@ -1054,14 +1052,14 @@ Return<void> AGnssCallback::agnssStatusIpV4Cb( jbyteArray AGnssCallback::convertToIpV4(uint32_t ip) { if (INADDR_NONE == ip) { - return NULL; + return nullptr; } JNIEnv* env = getJniEnv(); jbyteArray byteArray = env->NewByteArray(4); - if (byteArray == NULL) { + if (byteArray == nullptr) { ALOGE("Unable to allocate byte array for IPv4 address"); - return NULL; + return nullptr; } jbyte ipv4[4]; @@ -1341,7 +1339,7 @@ static jboolean android_location_GnssLocationProvider_init(JNIEnv* env, jobject sp<IGnssXtraCallback> gnssXtraCbIface = new GnssXtraCallback(); if (gnssXtraIface == nullptr) { - ALOGE("Unable to initialize GNSS Xtra interface\n"); + ALOGI("Unable to initialize GNSS Xtra interface\n"); } else { result = gnssXtraIface->setCallback(gnssXtraCbIface); if (!result.isOk() || !result) { @@ -1483,7 +1481,7 @@ static void android_location_GnssLocationProvider_agps_set_id(JNIEnv *env, jobje return; } - const char *setid = env->GetStringUTFChars(setid_string, NULL); + const char *setid = env->GetStringUTFChars(setid_string, nullptr); agnssRilIface->setSetId((IAGnssRil::SetIDType)type, setid); env->ReleaseStringUTFChars(setid_string, setid); } @@ -1579,12 +1577,12 @@ static void android_location_GnssLocationProvider_agps_data_conn_open( ALOGE("no AGPS interface in agps_data_conn_open"); return; } - if (apn == NULL) { - jniThrowException(env, "java/lang/IllegalArgumentException", NULL); + if (apn == nullptr) { + jniThrowException(env, "java/lang/IllegalArgumentException", nullptr); return; } - const char *apnStr = env->GetStringUTFChars(apn, NULL); + const char *apnStr = env->GetStringUTFChars(apn, nullptr); auto result = agnssIface->dataConnOpen(apnStr, static_cast<IAGnss::ApnIpType>(apnIpType)); if (!result.isOk() || !result){ @@ -1626,7 +1624,7 @@ static void android_location_GnssLocationProvider_set_agps_server(JNIEnv* env, j return; } - const char *c_hostname = env->GetStringUTFChars(hostname, NULL); + const char *c_hostname = env->GetStringUTFChars(hostname, nullptr); auto result = agnssIface->setServer(static_cast<IAGnssCallback::AGnssType>(type), c_hostname, port); @@ -1649,7 +1647,7 @@ static void android_location_GnssLocationProvider_send_ni_response(JNIEnv* /* en static jstring android_location_GnssLocationProvider_get_internal_state(JNIEnv* env, jobject /* obj */) { - jstring result = NULL; + jstring result = nullptr; /* * TODO(b/33089503) : Create a jobject to represent GnssDebug. */ @@ -1736,7 +1734,7 @@ static void android_location_GnssLocationProvider_update_network_state(JNIEnv* e ALOGE("updateNetworkState failed"); } - const char *c_apn = env->GetStringUTFChars(apn, NULL); + const char *c_apn = env->GetStringUTFChars(apn, nullptr); result = agnssRilIface->updateNetworkAvailability(available, c_apn); if (!result.isOk() || !result) { ALOGE("updateNetworkAvailability failed"); @@ -2019,15 +2017,15 @@ static jboolean android_location_GnssLocationProvider_set_satellite_blacklist( } jint *constellation_array = env->GetIntArrayElements(constellations, 0); - if (NULL == constellation_array) { - ALOGI("GetIntArrayElements returns NULL."); + if (nullptr == constellation_array) { + ALOGI("GetIntArrayElements returns nullptr."); return JNI_FALSE; } jsize length = env->GetArrayLength(constellations); jint *sv_id_array = env->GetIntArrayElements(sv_ids, 0); - if (NULL == sv_id_array) { - ALOGI("GetIntArrayElements returns NULL."); + if (nullptr == sv_id_array) { + ALOGI("GetIntArrayElements returns nullptr."); return JNI_FALSE; } diff --git a/services/robotests/src/com/android/server/backup/encryption/chunking/ChunkEncryptorTest.java b/services/robotests/src/com/android/server/backup/encryption/chunking/ChunkEncryptorTest.java new file mode 100644 index 000000000000..2cc3ef85d1ef --- /dev/null +++ b/services/robotests/src/com/android/server/backup/encryption/chunking/ChunkEncryptorTest.java @@ -0,0 +1,154 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.server.backup.encryption.chunking; + +import static com.android.server.backup.testing.CryptoTestUtils.generateAesKey; +import static com.google.common.truth.Truth.assertThat; +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.mock; + +import android.platform.test.annotations.Presubmit; +import com.android.server.backup.encryption.chunk.ChunkHash; +import com.android.server.testing.FrameworkRobolectricTestRunner; +import com.android.server.testing.SystemLoaderPackages; +import java.security.SecureRandom; +import javax.crypto.Cipher; +import javax.crypto.Mac; +import javax.crypto.SecretKey; +import javax.crypto.spec.GCMParameterSpec; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; +import org.robolectric.annotation.Config; + +@RunWith(FrameworkRobolectricTestRunner.class) +@Config(manifest = Config.NONE, sdk = 26) +@SystemLoaderPackages({"com.android.server.backup"}) +@Presubmit +public class ChunkEncryptorTest { + private static final String MAC_ALGORITHM = "HmacSHA256"; + private static final String CIPHER_ALGORITHM = "AES/GCM/NoPadding"; + private static final int GCM_NONCE_LENGTH_BYTES = 12; + private static final int GCM_TAG_LENGTH_BYTES = 16; + private static final String CHUNK_PLAINTEXT = + "A little Learning is a dang'rous Thing;\n" + + "Drink deep, or taste not the Pierian Spring:\n" + + "There shallow Draughts intoxicate the Brain,\n" + + "And drinking largely sobers us again."; + private static final byte[] PLAINTEXT_BYTES = CHUNK_PLAINTEXT.getBytes(UTF_8); + private static final byte[] NONCE_1 = "0123456789abc".getBytes(UTF_8); + private static final byte[] NONCE_2 = "123456789abcd".getBytes(UTF_8); + + private static final byte[][] NONCES = new byte[][] {NONCE_1, NONCE_2}; + + @Mock private SecureRandom mSecureRandomMock; + private SecretKey mSecretKey; + private ChunkHash mPlaintextHash; + private ChunkEncryptor mChunkEncryptor; + + @Before + public void setUp() throws Exception { + mSecretKey = generateAesKey(); + ChunkHasher chunkHasher = new ChunkHasher(mSecretKey); + mPlaintextHash = chunkHasher.computeHash(PLAINTEXT_BYTES); + mSecureRandomMock = mock(SecureRandom.class); + mChunkEncryptor = new ChunkEncryptor(mSecretKey, mSecureRandomMock); + + // Return NONCE_1, then NONCE_2 for invocations of mSecureRandomMock.nextBytes(). + doAnswer( + new Answer<Void>() { + private int mInvocation = 0; + + @Override + public Void answer(InvocationOnMock invocation) { + byte[] nonceDestination = invocation.getArgument(0); + System.arraycopy( + NONCES[this.mInvocation], + 0, + nonceDestination, + 0, + GCM_NONCE_LENGTH_BYTES); + this.mInvocation++; + return null; + } + }) + .when(mSecureRandomMock) + .nextBytes(any(byte[].class)); + } + + @Test + public void encrypt_withHash_resultContainsHashAsKey() throws Exception { + EncryptedChunk chunk = mChunkEncryptor.encrypt(mPlaintextHash, PLAINTEXT_BYTES); + + assertThat(chunk.key()).isEqualTo(mPlaintextHash); + } + + @Test + public void encrypt_generatesHmacOfPlaintext() throws Exception { + EncryptedChunk chunk = mChunkEncryptor.encrypt(mPlaintextHash, PLAINTEXT_BYTES); + + byte[] generatedHash = chunk.key().getHash(); + Mac mac = Mac.getInstance(MAC_ALGORITHM); + mac.init(mSecretKey); + byte[] plaintextHmac = mac.doFinal(PLAINTEXT_BYTES); + assertThat(generatedHash).isEqualTo(plaintextHmac); + } + + @Test + public void encrypt_whenInvokedAgain_generatesNewNonce() throws Exception { + EncryptedChunk chunk1 = mChunkEncryptor.encrypt(mPlaintextHash, PLAINTEXT_BYTES); + + EncryptedChunk chunk2 = mChunkEncryptor.encrypt(mPlaintextHash, PLAINTEXT_BYTES); + + assertThat(chunk1.nonce()).isNotEqualTo(chunk2.nonce()); + } + + @Test + public void encrypt_whenInvokedAgain_generatesNewCiphertext() throws Exception { + EncryptedChunk chunk1 = mChunkEncryptor.encrypt(mPlaintextHash, PLAINTEXT_BYTES); + + EncryptedChunk chunk2 = mChunkEncryptor.encrypt(mPlaintextHash, PLAINTEXT_BYTES); + + assertThat(chunk1.encryptedBytes()).isNotEqualTo(chunk2.encryptedBytes()); + } + + @Test + public void encrypt_generates12ByteNonce() throws Exception { + EncryptedChunk encryptedChunk = mChunkEncryptor.encrypt(mPlaintextHash, PLAINTEXT_BYTES); + + byte[] nonce = encryptedChunk.nonce(); + assertThat(nonce).hasLength(GCM_NONCE_LENGTH_BYTES); + } + + @Test + public void encrypt_decryptedResultCorrespondsToPlaintext() throws Exception { + EncryptedChunk chunk = mChunkEncryptor.encrypt(mPlaintextHash, PLAINTEXT_BYTES); + + Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM); + cipher.init( + Cipher.DECRYPT_MODE, + mSecretKey, + new GCMParameterSpec(GCM_TAG_LENGTH_BYTES * 8, chunk.nonce())); + byte[] decrypted = cipher.doFinal(chunk.encryptedBytes()); + assertThat(decrypted).isEqualTo(PLAINTEXT_BYTES); + } +} diff --git a/services/robotests/src/com/android/server/backup/encryption/chunking/ChunkHasherTest.java b/services/robotests/src/com/android/server/backup/encryption/chunking/ChunkHasherTest.java new file mode 100644 index 000000000000..11796c01f37c --- /dev/null +++ b/services/robotests/src/com/android/server/backup/encryption/chunking/ChunkHasherTest.java @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.server.backup.encryption.chunking; + +import static com.google.common.truth.Truth.assertThat; + +import android.platform.test.annotations.Presubmit; +import com.android.server.backup.encryption.chunk.ChunkHash; +import com.android.server.testing.FrameworkRobolectricTestRunner; +import com.android.server.testing.SystemLoaderPackages; +import javax.crypto.Mac; +import javax.crypto.SecretKey; +import javax.crypto.spec.SecretKeySpec; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.annotation.Config; + +@RunWith(FrameworkRobolectricTestRunner.class) +@Config(manifest = Config.NONE, sdk = 26) +@SystemLoaderPackages({"com.android.server.backup"}) +@Presubmit +public class ChunkHasherTest { + private static final String KEY_ALGORITHM = "AES"; + private static final String MAC_ALGORITHM = "HmacSHA256"; + + private static final byte[] TEST_KEY = {100, 120}; + private static final byte[] TEST_DATA = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13}; + + private SecretKey mSecretKey; + private ChunkHasher mChunkHasher; + + @Before + public void setUp() throws Exception { + mSecretKey = new SecretKeySpec(TEST_KEY, KEY_ALGORITHM); + mChunkHasher = new ChunkHasher(mSecretKey); + } + + @Test + public void computeHash_returnsHmacForData() throws Exception { + ChunkHash chunkHash = mChunkHasher.computeHash(TEST_DATA); + + byte[] hash = chunkHash.getHash(); + Mac mac = Mac.getInstance(MAC_ALGORITHM); + mac.init(mSecretKey); + byte[] expectedHash = mac.doFinal(TEST_DATA); + assertThat(hash).isEqualTo(expectedHash); + } + + @Test + public void computeHash_generates256BitHmac() throws Exception { + int expectedLength = 256 / Byte.SIZE; + + byte[] hash = mChunkHasher.computeHash(TEST_DATA).getHash(); + + assertThat(hash).hasLength(expectedLength); + } +} diff --git a/services/robotests/src/com/android/server/backup/encryption/chunking/RawBackupWriterTest.java b/services/robotests/src/com/android/server/backup/encryption/chunking/RawBackupWriterTest.java new file mode 100644 index 000000000000..8b54e1e78246 --- /dev/null +++ b/services/robotests/src/com/android/server/backup/encryption/chunking/RawBackupWriterTest.java @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.server.backup.encryption.chunking; + +import static com.google.common.truth.Truth.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.testng.Assert.assertThrows; + +import android.platform.test.annotations.Presubmit; +import com.android.server.testing.FrameworkRobolectricTestRunner; +import com.android.server.testing.SystemLoaderPackages; +import com.google.common.primitives.Bytes; +import java.io.ByteArrayOutputStream; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.annotation.Config; + +@RunWith(FrameworkRobolectricTestRunner.class) +@Config(manifest = Config.NONE, sdk = 26) +@SystemLoaderPackages({"com.android.server.backup"}) +@Presubmit +public class RawBackupWriterTest { + private static final byte[] TEST_BYTES = {1, 2, 3, 4, 5, 6}; + + private BackupWriter mWriter; + private ByteArrayOutputStream mOutput; + + @Before + public void setUp() { + mOutput = new ByteArrayOutputStream(); + mWriter = new RawBackupWriter(mOutput); + } + + @Test + public void writeBytes_writesToOutputStream() throws Exception { + mWriter.writeBytes(TEST_BYTES); + + assertThat(mOutput.toByteArray()) + .asList() + .containsExactlyElementsIn(Bytes.asList(TEST_BYTES)) + .inOrder(); + } + + @Test + public void writeChunk_throwsUnsupportedOperationException() throws Exception { + assertThrows(UnsupportedOperationException.class, () -> mWriter.writeChunk(0, 0)); + } + + @Test + public void getBytesWritten_returnsTotalSum() throws Exception { + mWriter.writeBytes(TEST_BYTES); + mWriter.writeBytes(TEST_BYTES); + + long bytesWritten = mWriter.getBytesWritten(); + + assertThat(bytesWritten).isEqualTo(2 * TEST_BYTES.length); + } + + @Test + public void flush_flushesOutputStream() throws Exception { + mOutput = mock(ByteArrayOutputStream.class); + mWriter = new RawBackupWriter(mOutput); + + mWriter.flush(); + + verify(mOutput).flush(); + } +} diff --git a/services/robotests/src/com/android/server/backup/testing/CryptoTestUtils.java b/services/robotests/src/com/android/server/backup/testing/CryptoTestUtils.java new file mode 100644 index 000000000000..83e8461e12de --- /dev/null +++ b/services/robotests/src/com/android/server/backup/testing/CryptoTestUtils.java @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.server.backup.testing; + +import java.security.NoSuchAlgorithmException; +import javax.crypto.KeyGenerator; +import javax.crypto.SecretKey; + +/** Helpers for crypto code tests. */ +public class CryptoTestUtils { + private static final String KEY_ALGORITHM = "AES"; + private static final int KEY_SIZE_BITS = 256; + + private CryptoTestUtils() {} + + public static SecretKey generateAesKey() throws NoSuchAlgorithmException { + KeyGenerator keyGenerator = KeyGenerator.getInstance(KEY_ALGORITHM); + keyGenerator.init(KEY_SIZE_BITS); + return keyGenerator.generateKey(); + } +} diff --git a/services/tests/servicestests/src/com/android/server/am/ActivityDisplayTests.java b/services/tests/servicestests/src/com/android/server/am/ActivityDisplayTests.java index 0da574239666..59c40673f105 100644 --- a/services/tests/servicestests/src/com/android/server/am/ActivityDisplayTests.java +++ b/services/tests/servicestests/src/com/android/server/am/ActivityDisplayTests.java @@ -60,8 +60,7 @@ public class ActivityDisplayTests extends ActivityTestsBase { public void testLastFocusedStackIsUpdatedWhenMovingStack() { // Create a stack at bottom. final ActivityDisplay display = mSupervisor.getDefaultDisplay(); - final ActivityStack stack = display.createStack( - WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, !ON_TOP); + final ActivityStack stack = new StackBuilder(mSupervisor).setOnTop(!ON_TOP).build(); final ActivityStack prevFocusedStack = display.getFocusedStack(); stack.moveToFront("moveStackToFront"); @@ -140,16 +139,14 @@ public class ActivityDisplayTests extends ActivityTestsBase { */ @Test public void testTopRunningActivity() { - // Create stack to hold focus. final ActivityDisplay display = mSupervisor.getDefaultDisplay(); - final ActivityStack emptyStack = display.createStack(WINDOWING_MODE_FULLSCREEN, - ACTIVITY_TYPE_STANDARD, true /* onTop */); - final KeyguardController keyguard = mSupervisor.getKeyguardController(); - final ActivityStack stack = mSupervisor.getDefaultDisplay().createStack( - WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */); - final ActivityRecord activity = new ActivityBuilder(mService).setCreateTask(true) - .setStack(stack).build(); + final ActivityStack stack = new StackBuilder(mSupervisor).build(); + final ActivityRecord activity = stack.getTopActivity(); + + // Create empty stack on top. + final ActivityStack emptyStack = + new StackBuilder(mSupervisor).setCreateActivity(false).build(); // Make sure the top running activity is not affected when keyguard is not locked. assertTopRunningActivity(activity, display); @@ -159,8 +156,8 @@ public class ActivityDisplayTests extends ActivityTestsBase { assertEquals(activity, display.topRunningActivity()); assertNull(display.topRunningActivity(true /* considerKeyguardState */)); - // Change focus to stack with activity. - stack.moveToFront("focusChangeToTestStack"); + // Move stack with activity to top. + stack.moveToFront("testStackToFront"); assertEquals(stack, display.getFocusedStack()); assertEquals(activity, display.topRunningActivity()); assertNull(display.topRunningActivity(true /* considerKeyguardState */)); @@ -175,11 +172,10 @@ public class ActivityDisplayTests extends ActivityTestsBase { // Ensure the show when locked activity is returned. assertTopRunningActivity(showWhenLockedActivity, display); - // Change focus back to empty stack. - emptyStack.moveToFront("focusChangeToEmptyStack"); - assertEquals(emptyStack, display.getFocusedStack()); - // If there is no running activity in focused stack, the running activity in next focusable - // stack should be returned. + // Move empty stack to front. The running activity in focusable stack which below the + // empty stack should be returned. + emptyStack.moveToFront("emptyStackToFront"); + assertEquals(stack, display.getFocusedStack()); assertTopRunningActivity(showWhenLockedActivity, display); } diff --git a/services/tests/servicestests/src/com/android/server/am/ActivityRecordTests.java b/services/tests/servicestests/src/com/android/server/am/ActivityRecordTests.java index ffc7fa26d9a1..101500812265 100644 --- a/services/tests/servicestests/src/com/android/server/am/ActivityRecordTests.java +++ b/services/tests/servicestests/src/com/android/server/am/ActivityRecordTests.java @@ -79,10 +79,9 @@ public class ActivityRecordTests extends ActivityTestsBase { super.setUp(); setupActivityTaskManagerService(); - mStack = mSupervisor.getDefaultDisplay().createStack( - WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */); - mTask = new TaskBuilder(mSupervisor).setStack(mStack).build(); - mActivity = new ActivityBuilder(mService).setTask(mTask).build(); + mStack = new StackBuilder(mSupervisor).build(); + mTask = mStack.getChildAt(0); + mActivity = mTask.getTopActivity(); } @Test diff --git a/services/tests/servicestests/src/com/android/server/am/ActivityStackSupervisorTests.java b/services/tests/servicestests/src/com/android/server/am/ActivityStackSupervisorTests.java index 2c993d32c20c..8ab22105c3ae 100644 --- a/services/tests/servicestests/src/com/android/server/am/ActivityStackSupervisorTests.java +++ b/services/tests/servicestests/src/com/android/server/am/ActivityStackSupervisorTests.java @@ -26,16 +26,21 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY; import static android.content.pm.ActivityInfo.FLAG_ALWAYS_FOCUSABLE; +import static com.android.server.am.ActivityDisplay.POSITION_TOP; import static com.android.server.am.ActivityStack.REMOVE_TASK_MODE_DESTROYING; import static com.android.server.am.ActivityStackSupervisor.MATCH_TASK_IN_STACKS_OR_RECENT_TASKS_AND_RESTORE; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.contains; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; @@ -359,15 +364,12 @@ public class ActivityStackSupervisorTests extends ActivityTestsBase { public void testFindTaskToMoveToFrontWhenRecentsOnTop() throws Exception { // Create stack/task on default display. final ActivityDisplay display = mSupervisor.getDefaultDisplay(); - final ActivityStack targetStack = display.createStack(WINDOWING_MODE_FULLSCREEN, - ACTIVITY_TYPE_STANDARD, false /* onTop */); - final TaskRecord targetTask = new TaskBuilder(mSupervisor).setStack(targetStack).build(); + final TestActivityStack targetStack = new StackBuilder(mSupervisor).setOnTop(false).build(); + final TaskRecord targetTask = targetStack.getChildAt(0); // Create Recents on top of the display. - final ActivityStack stack = display.createStack(WINDOWING_MODE_FULLSCREEN, - ACTIVITY_TYPE_RECENTS, true /* onTop */); - final TaskRecord task = new TaskBuilder(mSupervisor).setStack(stack).build(); - new ActivityBuilder(mService).setTask(task).build(); + final ActivityStack stack = + new StackBuilder(mSupervisor).setActivityType(ACTIVITY_TYPE_RECENTS).build(); final String reason = "findTaskToMoveToFront"; mSupervisor.findTaskToMoveToFront(targetTask, 0, ActivityOptions.makeBasic(), reason, @@ -430,4 +432,32 @@ public class ActivityStackSupervisorTests extends ActivityTestsBase { verify(targetStack, times(1)).resumeTopActivityUncheckedLocked( eq(activity), eq(null /* targetOptions */)); } + + + /** + * Tests that home activities can be started on the displays that supports system decorations. + */ + @Test + public void testStartHomeOnAllDisplays() throws Exception { + // Create secondary displays. + final TestActivityDisplay secondDisplay = spy(createNewActivityDisplay()); + mSupervisor.addChild(secondDisplay, POSITION_TOP); + doReturn(true).when(secondDisplay).supportsSystemDecorations(); + + // Create mock tasks and other necessary mocks. + TaskBuilder taskBuilder = new TaskBuilder(mService.mStackSupervisor).setCreateStack(false); + final TaskRecord.TaskRecordFactory factory = mock(TaskRecord.TaskRecordFactory.class); + TaskRecord.setTaskRecordFactory(factory); + doAnswer(i -> taskBuilder.build()).when(factory) + .create(any(), anyInt(), any(), any(), any(), any()); + doReturn(true).when(mService.mStackSupervisor) + .ensureVisibilityAndConfig(any(), anyInt(), anyBoolean(), anyBoolean()); + doReturn(true).when(mSupervisor).canStartHomeOnDisplay(any(), anyInt()); + + mSupervisor.startHomeOnAllDisplays(0, "testStartHome"); + + assertTrue(mSupervisor.getDefaultDisplay().getTopStack().isActivityTypeHome()); + assertNotNull(secondDisplay.getTopStack()); + assertTrue(secondDisplay.getTopStack().isActivityTypeHome()); + } } diff --git a/services/tests/servicestests/src/com/android/server/am/ActivityStackTests.java b/services/tests/servicestests/src/com/android/server/am/ActivityStackTests.java index 53f67afb629e..839792d71bca 100644 --- a/services/tests/servicestests/src/com/android/server/am/ActivityStackTests.java +++ b/services/tests/servicestests/src/com/android/server/am/ActivityStackTests.java @@ -26,8 +26,8 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMAR import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY; import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; +import static com.android.server.am.ActivityStack.ActivityState.STOPPING; import static com.android.server.am.ActivityStack.ActivityState.DESTROYING; -import static com.android.server.am.ActivityStack.ActivityState.FINISHING; import static com.android.server.am.ActivityStack.ActivityState.PAUSED; import static com.android.server.am.ActivityStack.ActivityState.PAUSING; import static com.android.server.am.ActivityStack.ActivityState.RESUMED; @@ -38,9 +38,9 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; -import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; @@ -253,6 +253,26 @@ public class ActivityStackTests extends ActivityTestsBase { } @Test + public void testMoveStackToBackIncludingParent() { + final ActivityDisplay display = addNewActivityDisplayAt(ActivityDisplay.POSITION_TOP); + final ActivityStack stack1 = createStackForShouldBeVisibleTest(display, + WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */); + final ActivityStack stack2 = createStackForShouldBeVisibleTest(display, + WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */); + + // Do not move display to back because there is still another stack. + stack2.moveToBack("testMoveStackToBackIncludingParent", stack2.topTask()); + verify(stack2.getWindowContainerController()).positionChildAtBottom(any(), + eq(false) /* includingParents */); + + // Also move display to back because there is only one stack left. + display.removeChild(stack1); + stack2.moveToBack("testMoveStackToBackIncludingParent", stack2.topTask()); + verify(stack2.getWindowContainerController()).positionChildAtBottom(any(), + eq(true) /* includingParents */); + } + + @Test public void testShouldBeVisible_Fullscreen() throws Exception { final TestActivityStack homeStack = createStackForShouldBeVisibleTest(mDefaultDisplay, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, true /* onTop */); @@ -361,14 +381,12 @@ public class ActivityStackTests extends ActivityTestsBase { translucentStack.topRunningActivityLocked(); topRunningTranslucentActivity.finishing = true; - // Home shouldn't be visible since its activity is marked as finishing and it isn't the top - // of the stack list. - assertFalse(homeStack.shouldBeVisible(null /* starting */)); + // Home stack should be visible even there are no running activities. + assertTrue(homeStack.shouldBeVisible(null /* starting */)); // Home should be visible if we are starting an activity within it. assertTrue(homeStack.shouldBeVisible(topRunningHomeActivity /* starting */)); - // The translucent stack should be visible since it is the top of the stack list even though - // it has its activity marked as finishing. - assertTrue(translucentStack.shouldBeVisible(null /* starting */)); + // The translucent stack shouldn't be visible since its activity marked as finishing. + assertFalse(translucentStack.shouldBeVisible(null /* starting */)); } @Test @@ -672,9 +690,10 @@ public class ActivityStackTests extends ActivityTestsBase { // There is still an activity1 in stack1 so the activity2 should be added to finishing list // that will be destroyed until idle. + stack2.getTopActivity().visible = true; final ActivityRecord activity2 = finishCurrentActivity(stack2); - assertEquals(FINISHING, activity2.getState()); - assertTrue(mSupervisor.mFinishingActivities.contains(activity2)); + assertEquals(STOPPING, activity2.getState()); + assertTrue(mSupervisor.mStoppingActivities.contains(activity2)); // The display becomes empty. Since there is no next activity to be idle, the activity // should be destroyed immediately with updating configuration to restore original state. diff --git a/services/tests/servicestests/src/com/android/server/am/ActivityTestsBase.java b/services/tests/servicestests/src/com/android/server/am/ActivityTestsBase.java index 094241e98a8d..57960efee3c7 100644 --- a/services/tests/servicestests/src/com/android/server/am/ActivityTestsBase.java +++ b/services/tests/servicestests/src/com/android/server/am/ActivityTestsBase.java @@ -39,14 +39,6 @@ import static org.mockito.Mockito.spy; import android.app.ActivityManagerInternal; import android.app.ActivityOptions; -import android.content.pm.PackageManagerInternal; -import com.android.server.uri.UriGrantsManagerInternal; -import com.android.server.wm.ActivityTaskManagerInternal; -import com.android.server.wm.DisplayWindowController; - -import org.junit.Rule; -import org.mockito.invocation.InvocationOnMock; - import android.app.IApplicationThread; import android.content.ComponentName; import android.content.Context; @@ -54,6 +46,7 @@ import android.content.Intent; import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; import android.content.pm.IPackageManager; +import android.content.pm.PackageManagerInternal; import android.content.res.Configuration; import android.graphics.Rect; import android.hardware.display.DisplayManager; @@ -73,7 +66,10 @@ import com.android.internal.app.IVoiceInteractor; import com.android.server.AppOpsService; import com.android.server.AttributeCache; import com.android.server.ServiceThread; +import com.android.server.uri.UriGrantsManagerInternal; +import com.android.server.wm.ActivityTaskManagerInternal; import com.android.server.wm.AppWindowContainerController; +import com.android.server.wm.DisplayWindowController; import com.android.server.wm.PinnedStackWindowController; import com.android.server.wm.RootWindowContainerController; import com.android.server.wm.StackWindowController; @@ -83,7 +79,9 @@ import com.android.server.wm.WindowTestUtils; import org.junit.After; import org.junit.Before; +import org.junit.Rule; import org.mockito.MockitoAnnotations; +import org.mockito.invocation.InvocationOnMock; import java.io.File; import java.util.List; @@ -172,6 +170,7 @@ public class ActivityTestsBase { // Makes sure the supervisor is using with the spy object. atm.mStackSupervisor.setService(atm); doReturn(mock(IPackageManager.class)).when(am).getPackageManager(); + doReturn(mock(IPackageManager.class)).when(atm).getPackageManager(); PackageManagerInternal mockPackageManager = mock(PackageManagerInternal.class); doReturn(mockPackageManager).when(am).getPackageManagerInternalLocked(); doReturn(null).when(mockPackageManager).getDefaultHomeActivity(anyInt()); @@ -419,6 +418,10 @@ public class ActivityTestsBase { private ActivityTaskManagerInternal mInternal; private PackageManagerInternal mPmInternal; + // ActivityStackSupervisor may be created more than once while setting up AMS and ATMS. + // We keep the reference in order to prevent creating it twice. + private ActivityStackSupervisor mTestStackSupervisor; + TestActivityTaskManagerService(Context context) { super(context); mSupportsMultiWindow = true; @@ -449,24 +452,27 @@ public class ActivityTestsBase { @Override final protected ActivityStackSupervisor createStackSupervisor() { - final ActivityStackSupervisor supervisor = spy(createTestSupervisor()); - final KeyguardController keyguardController = mock(KeyguardController.class); - - // Invoked during {@link ActivityStack} creation. - doNothing().when(supervisor).updateUIDsPresentOnDisplay(); - // Always keep things awake. - doReturn(true).when(supervisor).hasAwakeDisplay(); - // Called when moving activity to pinned stack. - doNothing().when(supervisor).ensureActivitiesVisibleLocked(any(), anyInt(), anyBoolean()); - // Do not schedule idle timeouts - doNothing().when(supervisor).scheduleIdleTimeoutLocked(any()); - // unit test version does not handle launch wake lock - doNothing().when(supervisor).acquireLaunchWakelock(); - doReturn(keyguardController).when(supervisor).getKeyguardController(); - - supervisor.initialize(); - - return supervisor; + if (mTestStackSupervisor == null) { + final ActivityStackSupervisor supervisor = spy(createTestSupervisor()); + final KeyguardController keyguardController = mock(KeyguardController.class); + + // Invoked during {@link ActivityStack} creation. + doNothing().when(supervisor).updateUIDsPresentOnDisplay(); + // Always keep things awake. + doReturn(true).when(supervisor).hasAwakeDisplay(); + // Called when moving activity to pinned stack. + doNothing().when(supervisor).ensureActivitiesVisibleLocked(any(), anyInt(), + anyBoolean()); + // Do not schedule idle timeouts + doNothing().when(supervisor).scheduleIdleTimeoutLocked(any()); + // unit test version does not handle launch wake lock + doNothing().when(supervisor).acquireLaunchWakelock(); + doReturn(keyguardController).when(supervisor).getKeyguardController(); + + supervisor.initialize(); + mTestStackSupervisor = supervisor; + } + return mTestStackSupervisor; } protected ActivityStackSupervisor createTestSupervisor() { @@ -608,23 +614,9 @@ public class ActivityTestsBase { @Override <T extends ActivityStack> T createStackUnchecked(int windowingMode, int activityType, int stackId, boolean onTop) { - if (windowingMode == WINDOWING_MODE_PINNED) { - return (T) new PinnedActivityStack(this, stackId, mSupervisor, onTop) { - @Override - Rect getDefaultPictureInPictureBounds(float aspectRatio) { - return new Rect(50, 50, 100, 100); - } - - @Override - PinnedStackWindowController createStackWindowController(int displayId, - boolean onTop, Rect outBounds) { - return mock(PinnedStackWindowController.class); - } - }; - } else { - return (T) new TestActivityStack( - this, stackId, mSupervisor, windowingMode, activityType, onTop); - } + return new StackBuilder(mSupervisor).setDisplay(this) + .setWindowingMode(windowingMode).setActivityType(activityType) + .setStackId(stackId).setOnTop(onTop).setCreateActivity(false).build(); } @Override @@ -677,8 +669,19 @@ public class ActivityTestsBase { private int mSupportsSplitScreen = SUPPORTS_SPLIT_SCREEN_UNSET; TestActivityStack(ActivityDisplay display, int stackId, ActivityStackSupervisor supervisor, - int windowingMode, int activityType, boolean onTop) { + int windowingMode, int activityType, boolean onTop, boolean createActivity) { super(display, stackId, supervisor, windowingMode, activityType, onTop); + if (createActivity) { + new ActivityBuilder(mService).setCreateTask(true).setStack(this).build(); + if (onTop) { + // We move the task to front again in order to regain focus after activity + // added to the stack. Or {@link ActivityDisplay#mPreferredTopFocusableStack} + // could be other stacks (e.g. home stack). + moveToFront("createActivityStack"); + } else { + moveToBack("createActivityStack", null); + } + } } @Override @@ -750,4 +753,71 @@ public class ActivityTestsBase { ActivityOptions options) { } } + + protected static class StackBuilder { + private final ActivityStackSupervisor mSupervisor; + private ActivityDisplay mDisplay; + private int mStackId = -1; + private int mWindowingMode = WINDOWING_MODE_FULLSCREEN; + private int mActivityType = ACTIVITY_TYPE_STANDARD; + private boolean mOnTop = true; + private boolean mCreateActivity = true; + + StackBuilder(ActivityStackSupervisor supervisor) { + mSupervisor = supervisor; + mDisplay = mSupervisor.getDefaultDisplay(); + } + + StackBuilder setWindowingMode(int windowingMode) { + mWindowingMode = windowingMode; + return this; + } + + StackBuilder setActivityType(int activityType) { + mActivityType = activityType; + return this; + } + + StackBuilder setStackId(int stackId) { + mStackId = stackId; + return this; + } + + StackBuilder setDisplay(ActivityDisplay display) { + mDisplay = display; + return this; + } + + StackBuilder setOnTop(boolean onTop) { + mOnTop = onTop; + return this; + } + + StackBuilder setCreateActivity(boolean createActivity) { + mCreateActivity = createActivity; + return this; + } + + <T extends ActivityStack> T build() { + final int stackId = mStackId >= 0 ? mStackId : mDisplay.getNextStackId(); + if (mWindowingMode == WINDOWING_MODE_PINNED) { + return (T) new PinnedActivityStack(mDisplay, stackId, mSupervisor, mOnTop) { + @Override + Rect getDefaultPictureInPictureBounds(float aspectRatio) { + return new Rect(50, 50, 100, 100); + } + + @Override + PinnedStackWindowController createStackWindowController(int displayId, + boolean onTop, Rect outBounds) { + return mock(PinnedStackWindowController.class); + } + }; + } else { + return (T) new TestActivityStack(mDisplay, stackId, mSupervisor, mWindowingMode, + mActivityType, mOnTop, mCreateActivity); + } + } + + } } diff --git a/services/tests/servicestests/src/com/android/server/am/RecentTasksTest.java b/services/tests/servicestests/src/com/android/server/am/RecentTasksTest.java index 27e8c632c1bd..129b835f588c 100644 --- a/services/tests/servicestests/src/com/android/server/am/RecentTasksTest.java +++ b/services/tests/servicestests/src/com/android/server/am/RecentTasksTest.java @@ -883,7 +883,7 @@ public class RecentTasksTest extends ActivityTestsBase { MyTestActivityStack(ActivityDisplay display, ActivityStackSupervisor supervisor) { super(display, LAST_STACK_ID++, supervisor, WINDOWING_MODE_FULLSCREEN, - ACTIVITY_TYPE_STANDARD, true); + ACTIVITY_TYPE_STANDARD, true /* onTop */, false /* createActivity */); mDisplay = display; } diff --git a/services/tests/servicestests/src/com/android/server/am/RunningTasksTest.java b/services/tests/servicestests/src/com/android/server/am/RunningTasksTest.java index aa3046fb694c..849a41183672 100644 --- a/services/tests/servicestests/src/com/android/server/am/RunningTasksTest.java +++ b/services/tests/servicestests/src/com/android/server/am/RunningTasksTest.java @@ -16,9 +16,7 @@ package com.android.server.am; -import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED; -import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; import static android.view.Display.DEFAULT_DISPLAY; @@ -30,7 +28,6 @@ import android.app.ActivityManager.RunningTaskInfo; import android.content.ComponentName; import android.content.Context; import android.platform.test.annotations.Presubmit; -import android.util.SparseArray; import androidx.test.InstrumentationRegistry; import androidx.test.filters.MediumTest; @@ -72,8 +69,8 @@ public class RunningTasksTest extends ActivityTestsBase { final int numStacks = 2; for (int stackIndex = 0; stackIndex < numStacks; stackIndex++) { - final ActivityStack stack = new TestActivityStack(display, stackIndex, mSupervisor, - WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true); + final ActivityStack stack = + new StackBuilder(mSupervisor).setCreateActivity(false).build(); display.addChild(stack, POSITION_BOTTOM); } diff --git a/services/tests/servicestests/src/com/android/server/am/TaskLaunchParamsModifierTests.java b/services/tests/servicestests/src/com/android/server/am/TaskLaunchParamsModifierTests.java index 169204fe374a..b7f2b3581fb6 100644 --- a/services/tests/servicestests/src/com/android/server/am/TaskLaunchParamsModifierTests.java +++ b/services/tests/servicestests/src/com/android/server/am/TaskLaunchParamsModifierTests.java @@ -167,6 +167,18 @@ public class TaskLaunchParamsModifierTests extends ActivityTestsBase { assertEquals(fullscreenDisplay.mDisplayId, mResult.mPreferredDisplayId); } + @Test + public void testUsesTaskDisplayIdIfSet() { + final TestActivityDisplay freeformDisplay = createNewActivityDisplay( + WINDOWING_MODE_FREEFORM); + ActivityRecord source = createSourceActivity(freeformDisplay); + + assertEquals(RESULT_CONTINUE, mTarget.onCalculate(source.getTask(), null /* layout */, + null /* activity */, null /* source */, null /* options */, mCurrent, mResult)); + + assertEquals(freeformDisplay.mDisplayId, mResult.mPreferredDisplayId); + } + // ===================================== // Launch Windowing Mode Related Tests // ===================================== diff --git a/services/tests/servicestests/src/com/android/server/inputmethod/InputMethodUtilsTest.java b/services/tests/servicestests/src/com/android/server/inputmethod/InputMethodUtilsTest.java index 4d0278fe7a07..e4c9cc3c05d9 100644 --- a/services/tests/servicestests/src/com/android/server/inputmethod/InputMethodUtilsTest.java +++ b/services/tests/servicestests/src/com/android/server/inputmethod/InputMethodUtilsTest.java @@ -16,9 +16,6 @@ package com.android.server.inputmethod; -import static android.view.inputmethod.InputMethodManager.CONTROL_WINDOW_IS_TEXT_EDITOR; -import static android.view.inputmethod.InputMethodManager.CONTROL_WINDOW_VIEW_HAS_FOCUS; - import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.in; import static org.hamcrest.Matchers.not; @@ -45,6 +42,8 @@ import androidx.test.InstrumentationRegistry; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; +import com.android.internal.inputmethod.StartInputFlags; + import org.junit.Test; import org.junit.runner.RunWith; @@ -1090,21 +1089,19 @@ public class InputMethodUtilsTest { assertTrue(InputMethodUtils.isSoftInputModeStateVisibleAllowed( Build.VERSION_CODES.O_MR1, 0)); assertTrue(InputMethodUtils.isSoftInputModeStateVisibleAllowed( - Build.VERSION_CODES.O_MR1, CONTROL_WINDOW_VIEW_HAS_FOCUS)); + Build.VERSION_CODES.O_MR1, StartInputFlags.VIEW_HAS_FOCUS)); assertTrue(InputMethodUtils.isSoftInputModeStateVisibleAllowed( Build.VERSION_CODES.O_MR1, - CONTROL_WINDOW_VIEW_HAS_FOCUS | CONTROL_WINDOW_IS_TEXT_EDITOR)); + StartInputFlags.VIEW_HAS_FOCUS | StartInputFlags.IS_TEXT_EDITOR)); // On P+ devices, SOFT_INPUT_STATE_VISIBLE/SOFT_INPUT_STATE_ALWAYS_VISIBLE are allowed only // when there is a focused View and its View#onCheckIsTextEditor() returns true. assertFalse(InputMethodUtils.isSoftInputModeStateVisibleAllowed( Build.VERSION_CODES.P, 0)); assertFalse(InputMethodUtils.isSoftInputModeStateVisibleAllowed( - Build.VERSION_CODES.P, CONTROL_WINDOW_VIEW_HAS_FOCUS)); + Build.VERSION_CODES.P, StartInputFlags.VIEW_HAS_FOCUS)); assertTrue(InputMethodUtils.isSoftInputModeStateVisibleAllowed( Build.VERSION_CODES.P, - CONTROL_WINDOW_VIEW_HAS_FOCUS | CONTROL_WINDOW_IS_TEXT_EDITOR)); - + StartInputFlags.VIEW_HAS_FOCUS | StartInputFlags.IS_TEXT_EDITOR)); } - } diff --git a/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java b/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java index 543f51cba41f..78751a1e2ad5 100644 --- a/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java +++ b/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java @@ -27,6 +27,7 @@ import android.util.Log; import android.util.Pair; import androidx.test.InstrumentationRegistry; +import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; import com.android.internal.util.HexDump; @@ -53,6 +54,7 @@ import java.util.concurrent.TimeUnit; * atest $ANDROID_BUILD_TOP/frameworks/base/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java */ @RunWith(AndroidJUnit4.class) +@SmallTest public class JobStoreTest { private static final String TAG = "TaskStoreTest"; private static final String TEST_PREFIX = "_test_"; diff --git a/services/tests/servicestests/src/com/android/server/usage/AppTimeLimitControllerTests.java b/services/tests/servicestests/src/com/android/server/usage/AppTimeLimitControllerTests.java index 047adddd794d..793d6b0639a8 100644 --- a/services/tests/servicestests/src/com/android/server/usage/AppTimeLimitControllerTests.java +++ b/services/tests/servicestests/src/com/android/server/usage/AppTimeLimitControllerTests.java @@ -61,6 +61,7 @@ public class AppTimeLimitControllerTests { private static final long TIME_30_MIN = 30 * 60_000L; private static final long TIME_10_MIN = 10 * 60_000L; + private static final long TIME_1_MIN = 10 * 60_000L; private static final long MAX_OBSERVER_PER_UID = 10; private static final long MIN_TIME_LIMIT = 4_000L; @@ -77,7 +78,8 @@ public class AppTimeLimitControllerTests { PKG_GAME1, PKG_GAME2 }; - private final CountDownLatch mCountDownLatch = new CountDownLatch(1); + private CountDownLatch mLimitReachedLatch = new CountDownLatch(1); + private CountDownLatch mSessionEndLatch = new CountDownLatch(1); private AppTimeLimitController mController; @@ -85,18 +87,24 @@ public class AppTimeLimitControllerTests { private long mUptimeMillis; - AppTimeLimitController.OnLimitReachedListener mListener - = new AppTimeLimitController.OnLimitReachedListener() { - - @Override - public void onLimitReached(int observerId, int userId, long timeLimit, long timeElapsed, - PendingIntent callbackIntent) { - mCountDownLatch.countDown(); - } - }; + AppTimeLimitController.TimeLimitCallbackListener mListener = + new AppTimeLimitController.TimeLimitCallbackListener() { + @Override + public void onLimitReached(int observerId, int userId, long timeLimit, + long timeElapsed, + PendingIntent callbackIntent) { + mLimitReachedLatch.countDown(); + } + + @Override + public void onSessionEnd(int observerId, int userId, long timeElapsed, + PendingIntent callbackIntent) { + mSessionEndLatch.countDown(); + } + }; class MyAppTimeLimitController extends AppTimeLimitController { - MyAppTimeLimitController(AppTimeLimitController.OnLimitReachedListener listener, + MyAppTimeLimitController(AppTimeLimitController.TimeLimitCallbackListener listener, Looper looper) { super(listener, looper); } @@ -107,7 +115,12 @@ public class AppTimeLimitControllerTests { } @Override - protected long getObserverPerUidLimit() { + protected long getAppUsageObserverPerUidLimit() { + return MAX_OBSERVER_PER_UID; + } + + @Override + protected long getUsageSessionObserverPerUidLimit() { return MAX_OBSERVER_PER_UID; } @@ -129,188 +142,551 @@ public class AppTimeLimitControllerTests { mThread.quit(); } - /** Verify observer is added */ + /** Verify app usage observer is added */ + @Test + public void testAppUsageObserver_AddObserver() { + addAppUsageObserver(OBS_ID1, GROUP1, TIME_30_MIN); + assertTrue("Observer wasn't added", hasAppUsageObserver(UID, OBS_ID1)); + addAppUsageObserver(OBS_ID2, GROUP_GAME, TIME_30_MIN); + assertTrue("Observer wasn't added", hasAppUsageObserver(UID, OBS_ID2)); + assertTrue("Observer wasn't added", hasAppUsageObserver(UID, OBS_ID1)); + } + + /** Verify usage session observer is added */ + @Test + public void testUsageSessionObserver_AddObserver() { + addUsageSessionObserver(OBS_ID1, GROUP1, TIME_30_MIN, TIME_1_MIN); + assertTrue("Observer wasn't added", hasUsageSessionObserver(UID, OBS_ID1)); + addUsageSessionObserver(OBS_ID2, GROUP_GAME, TIME_30_MIN, TIME_1_MIN); + assertTrue("Observer wasn't added", hasUsageSessionObserver(UID, OBS_ID2)); + assertTrue("Observer wasn't added", hasUsageSessionObserver(UID, OBS_ID1)); + } + + /** Verify app usage observer is removed */ @Test - public void testAddObserver() { - addObserver(OBS_ID1, GROUP1, TIME_30_MIN); - assertTrue("Observer wasn't added", hasObserver(OBS_ID1)); - addObserver(OBS_ID2, GROUP_GAME, TIME_30_MIN); - assertTrue("Observer wasn't added", hasObserver(OBS_ID2)); - assertTrue("Observer wasn't added", hasObserver(OBS_ID1)); + public void testAppUsageObserver_RemoveObserver() { + addAppUsageObserver(OBS_ID1, GROUP1, TIME_30_MIN); + assertTrue("Observer wasn't added", hasAppUsageObserver(UID, OBS_ID1)); + mController.removeAppUsageObserver(UID, OBS_ID1, USER_ID); + assertFalse("Observer wasn't removed", hasAppUsageObserver(UID, OBS_ID1)); } - /** Verify observer is removed */ + /** Verify usage session observer is removed */ @Test - public void testRemoveObserver() { - addObserver(OBS_ID1, GROUP1, TIME_30_MIN); - assertTrue("Observer wasn't added", hasObserver(OBS_ID1)); - mController.removeObserver(UID, OBS_ID1, USER_ID); - assertFalse("Observer wasn't removed", hasObserver(OBS_ID1)); + public void testUsageSessionObserver_RemoveObserver() { + addUsageSessionObserver(OBS_ID1, GROUP1, TIME_30_MIN, TIME_1_MIN); + assertTrue("Observer wasn't added", hasUsageSessionObserver(UID, OBS_ID1)); + mController.removeUsageSessionObserver(UID, OBS_ID1, USER_ID); + assertFalse("Observer wasn't removed", hasUsageSessionObserver(UID, OBS_ID1)); } /** Re-adding an observer should result in only one copy */ @Test - public void testObserverReAdd() { - addObserver(OBS_ID1, GROUP1, TIME_30_MIN); - assertTrue("Observer wasn't added", hasObserver(OBS_ID1)); - addObserver(OBS_ID1, GROUP1, TIME_10_MIN); + public void testAppUsageObserver_ObserverReAdd() { + addAppUsageObserver(OBS_ID1, GROUP1, TIME_30_MIN); + assertTrue("Observer wasn't added", hasAppUsageObserver(UID, OBS_ID1)); + addAppUsageObserver(OBS_ID1, GROUP1, TIME_10_MIN); assertTrue("Observer wasn't added", - mController.getObserverGroup(OBS_ID1, USER_ID).timeLimit == TIME_10_MIN); - mController.removeObserver(UID, OBS_ID1, USER_ID); - assertFalse("Observer wasn't removed", hasObserver(OBS_ID1)); + mController.getAppUsageGroup(UID, OBS_ID1).getTimeLimitMs() == TIME_10_MIN); + mController.removeAppUsageObserver(UID, OBS_ID1, USER_ID); + assertFalse("Observer wasn't removed", hasAppUsageObserver(UID, OBS_ID1)); + } + + /** Re-adding an observer should result in only one copy */ + @Test + public void testUsageSessionObserver_ObserverReAdd() { + addUsageSessionObserver(OBS_ID1, GROUP1, TIME_30_MIN, TIME_1_MIN); + assertTrue("Observer wasn't added", hasUsageSessionObserver(UID, OBS_ID1)); + addUsageSessionObserver(OBS_ID1, GROUP1, TIME_10_MIN, TIME_1_MIN); + assertTrue("Observer wasn't added", + mController.getSessionUsageGroup(UID, OBS_ID1).getTimeLimitMs() == TIME_10_MIN); + mController.removeUsageSessionObserver(UID, OBS_ID1, USER_ID); + assertFalse("Observer wasn't removed", hasUsageSessionObserver(UID, OBS_ID1)); + } + + /** Different type observers can be registered to the same observerId value */ + @Test + public void testAllObservers_ExclusiveObserverIds() { + addAppUsageObserver(OBS_ID1, GROUP1, TIME_10_MIN); + addUsageSessionObserver(OBS_ID1, GROUP1, TIME_30_MIN, TIME_1_MIN); + assertTrue("Observer wasn't added", hasAppUsageObserver(UID, OBS_ID1)); + assertTrue("Observer wasn't added", hasUsageSessionObserver(UID, OBS_ID1)); + + AppTimeLimitController.UsageGroup appUsageGroup = mController.getAppUsageGroup(UID, + OBS_ID1); + AppTimeLimitController.UsageGroup sessionUsageGroup = mController.getSessionUsageGroup(UID, + OBS_ID1); + + // Verify data still intact + assertEquals(TIME_10_MIN, appUsageGroup.getTimeLimitMs()); + assertEquals(TIME_30_MIN, sessionUsageGroup.getTimeLimitMs()); + } + + /** Verify that usage across different apps within a group are added up */ + @Test + public void testAppUsageObserver_Accumulation() throws Exception { + setTime(0L); + addAppUsageObserver(OBS_ID1, GROUP1, TIME_30_MIN); + startUsage(PKG_SOC1); + // Add 10 mins + setTime(TIME_10_MIN); + stopUsage(PKG_SOC1); + + AppTimeLimitController.UsageGroup group = mController.getAppUsageGroup(UID, OBS_ID1); + + long timeRemaining = group.getTimeLimitMs() - group.getUsageTimeMs(); + assertEquals(TIME_10_MIN * 2, timeRemaining); + + startUsage(PKG_SOC1); + setTime(TIME_10_MIN * 2); + stopUsage(PKG_SOC1); + + timeRemaining = group.getTimeLimitMs() - group.getUsageTimeMs(); + assertEquals(TIME_10_MIN, timeRemaining); + + setTime(TIME_30_MIN); + + assertFalse(mLimitReachedLatch.await(100L, TimeUnit.MILLISECONDS)); + + // Add a different package in the group + startUsage(PKG_GAME1); + setTime(TIME_30_MIN + TIME_10_MIN); + stopUsage(PKG_GAME1); + + assertEquals(0, group.getTimeLimitMs() - group.getUsageTimeMs()); + assertTrue(mLimitReachedLatch.await(100L, TimeUnit.MILLISECONDS)); } /** Verify that usage across different apps within a group are added up */ @Test - public void testAccumulation() throws Exception { + public void testUsageSessionObserver_Accumulation() throws Exception { setTime(0L); - addObserver(OBS_ID1, GROUP1, TIME_30_MIN); - moveToForeground(PKG_SOC1); + addUsageSessionObserver(OBS_ID1, GROUP1, TIME_30_MIN, TIME_1_MIN); + startUsage(PKG_SOC1); // Add 10 mins setTime(TIME_10_MIN); - moveToBackground(PKG_SOC1); + stopUsage(PKG_SOC1); + + AppTimeLimitController.UsageGroup group = mController.getSessionUsageGroup(UID, OBS_ID1); - long timeRemaining = mController.getObserverGroup(OBS_ID1, USER_ID).timeRemaining; + long timeRemaining = group.getTimeLimitMs() - group.getUsageTimeMs(); assertEquals(TIME_10_MIN * 2, timeRemaining); - moveToForeground(PKG_SOC1); + startUsage(PKG_SOC1); setTime(TIME_10_MIN * 2); - moveToBackground(PKG_SOC1); + stopUsage(PKG_SOC1); - timeRemaining = mController.getObserverGroup(OBS_ID1, USER_ID).timeRemaining; + timeRemaining = group.getTimeLimitMs() - group.getUsageTimeMs(); assertEquals(TIME_10_MIN, timeRemaining); setTime(TIME_30_MIN); - assertFalse(mCountDownLatch.await(100L, TimeUnit.MILLISECONDS)); + assertFalse(mLimitReachedLatch.await(100L, TimeUnit.MILLISECONDS)); // Add a different package in the group - moveToForeground(PKG_GAME1); + startUsage(PKG_GAME1); setTime(TIME_30_MIN + TIME_10_MIN); - moveToBackground(PKG_GAME1); + stopUsage(PKG_GAME1); - assertEquals(0, mController.getObserverGroup(OBS_ID1, USER_ID).timeRemaining); - assertTrue(mCountDownLatch.await(100L, TimeUnit.MILLISECONDS)); + assertEquals(0, group.getTimeLimitMs() - group.getUsageTimeMs()); + assertTrue(mLimitReachedLatch.await(100L, TimeUnit.MILLISECONDS)); } /** Verify that time limit does not get triggered due to a different app */ @Test - public void testTimeoutOtherApp() throws Exception { + public void testAppUsageObserver_TimeoutOtherApp() throws Exception { setTime(0L); - addObserver(OBS_ID1, GROUP1, 4_000L); - moveToForeground(PKG_SOC2); - assertFalse(mCountDownLatch.await(6_000L, TimeUnit.MILLISECONDS)); + addAppUsageObserver(OBS_ID1, GROUP1, 4_000L); + startUsage(PKG_SOC2); + assertFalse(mLimitReachedLatch.await(6_000L, TimeUnit.MILLISECONDS)); setTime(6_000L); - moveToBackground(PKG_SOC2); - assertFalse(mCountDownLatch.await(100L, TimeUnit.MILLISECONDS)); + stopUsage(PKG_SOC2); + assertFalse(mLimitReachedLatch.await(100L, TimeUnit.MILLISECONDS)); + } + + /** Verify that time limit does not get triggered due to a different app */ + @Test + public void testUsageSessionObserver_TimeoutOtherApp() throws Exception { + setTime(0L); + addUsageSessionObserver(OBS_ID1, GROUP1, 4_000L, 1_000L); + startUsage(PKG_SOC2); + assertFalse(mLimitReachedLatch.await(6_000L, TimeUnit.MILLISECONDS)); + setTime(6_000L); + stopUsage(PKG_SOC2); + assertFalse(mLimitReachedLatch.await(100L, TimeUnit.MILLISECONDS)); + } /** Verify the timeout message is delivered at the right time */ @Test - public void testTimeout() throws Exception { + public void testAppUsageObserver_Timeout() throws Exception { setTime(0L); - addObserver(OBS_ID1, GROUP1, 4_000L); - moveToForeground(PKG_SOC1); + addAppUsageObserver(OBS_ID1, GROUP1, 4_000L); + startUsage(PKG_SOC1); setTime(6_000L); - assertTrue(mCountDownLatch.await(6_000L, TimeUnit.MILLISECONDS)); - moveToBackground(PKG_SOC1); + assertTrue(mLimitReachedLatch.await(6_000L, TimeUnit.MILLISECONDS)); + stopUsage(PKG_SOC1); // Verify that the observer was removed - assertFalse(hasObserver(OBS_ID1)); + assertFalse(hasAppUsageObserver(UID, OBS_ID1)); + } + + /** Verify the timeout message is delivered at the right time */ + @Test + public void testUsageSessionObserver_Timeout() throws Exception { + setTime(0L); + addUsageSessionObserver(OBS_ID1, GROUP1, 4_000L, 1_000L); + startUsage(PKG_SOC1); + setTime(6_000L); + assertTrue(mLimitReachedLatch.await(6_000L, TimeUnit.MILLISECONDS)); + stopUsage(PKG_SOC1); + // Usage has stopped, Session should end in a second. Verify session end occurs in a second + // (+/- 100ms, which is hopefully not too slim a margin) + assertFalse(mSessionEndLatch.await(900L, TimeUnit.MILLISECONDS)); + assertTrue(mSessionEndLatch.await(200L, TimeUnit.MILLISECONDS)); + // Verify that the observer was not removed + assertTrue(hasUsageSessionObserver(UID, OBS_ID1)); } /** If an app was already running, make sure it is partially counted towards the time limit */ @Test - public void testAlreadyRunning() throws Exception { + public void testAppUsageObserver_AlreadyRunning() throws Exception { setTime(TIME_10_MIN); - moveToForeground(PKG_GAME1); + startUsage(PKG_GAME1); setTime(TIME_30_MIN); - addObserver(OBS_ID2, GROUP_GAME, TIME_30_MIN); + addAppUsageObserver(OBS_ID2, GROUP_GAME, TIME_30_MIN); setTime(TIME_30_MIN + TIME_10_MIN); - moveToBackground(PKG_GAME1); - assertFalse(mCountDownLatch.await(1000L, TimeUnit.MILLISECONDS)); + stopUsage(PKG_GAME1); + assertFalse(mLimitReachedLatch.await(1_000L, TimeUnit.MILLISECONDS)); - moveToForeground(PKG_GAME2); + startUsage(PKG_GAME2); setTime(TIME_30_MIN + TIME_30_MIN); - moveToBackground(PKG_GAME2); - assertTrue(mCountDownLatch.await(1000L, TimeUnit.MILLISECONDS)); + stopUsage(PKG_GAME2); + assertTrue(mLimitReachedLatch.await(1_000L, TimeUnit.MILLISECONDS)); + // Verify that the observer was removed + assertFalse(hasAppUsageObserver(UID, OBS_ID2)); + } + + /** If an app was already running, make sure it is partially counted towards the time limit */ + @Test + public void testUsageSessionObserver_AlreadyRunning() throws Exception { + setTime(TIME_10_MIN); + startUsage(PKG_GAME1); + setTime(TIME_30_MIN); + addUsageSessionObserver(OBS_ID2, GROUP_GAME, TIME_30_MIN, TIME_1_MIN); + setTime(TIME_30_MIN + TIME_10_MIN); + stopUsage(PKG_GAME1); + assertFalse(mLimitReachedLatch.await(1_000L, TimeUnit.MILLISECONDS)); + + startUsage(PKG_GAME2); + setTime(TIME_30_MIN + TIME_30_MIN); + stopUsage(PKG_GAME2); + assertTrue(mLimitReachedLatch.await(1_000L, TimeUnit.MILLISECONDS)); + // Verify that the observer was removed + assertTrue(hasUsageSessionObserver(UID, OBS_ID2)); + } + + /** If watched app is already running, verify the timeout callback happens at the right time */ + @Test + public void testAppUsageObserver_AlreadyRunningTimeout() throws Exception { + setTime(0); + startUsage(PKG_SOC1); + setTime(TIME_10_MIN); + // 10 second time limit + addAppUsageObserver(OBS_ID1, GROUP_SOC, 10_000L); + setTime(TIME_10_MIN + 5_000L); + // Shouldn't call back in 6 seconds + assertFalse(mLimitReachedLatch.await(6_000L, TimeUnit.MILLISECONDS)); + setTime(TIME_10_MIN + 10_000L); + // Should call back by 11 seconds (6 earlier + 5 now) + assertTrue(mLimitReachedLatch.await(5_000L, TimeUnit.MILLISECONDS)); // Verify that the observer was removed - assertFalse(hasObserver(OBS_ID2)); + assertFalse(hasAppUsageObserver(UID, OBS_ID1)); } /** If watched app is already running, verify the timeout callback happens at the right time */ @Test - public void testAlreadyRunningTimeout() throws Exception { + public void testUsageSessionObserver_AlreadyRunningTimeout() throws Exception { setTime(0); - moveToForeground(PKG_SOC1); + startUsage(PKG_SOC1); setTime(TIME_10_MIN); // 10 second time limit - addObserver(OBS_ID1, GROUP_SOC, 10_000L); + addUsageSessionObserver(OBS_ID1, GROUP_SOC, 10_000L, 1_000L); setTime(TIME_10_MIN + 5_000L); // Shouldn't call back in 6 seconds - assertFalse(mCountDownLatch.await(6_000L, TimeUnit.MILLISECONDS)); + assertFalse(mLimitReachedLatch.await(6_000L, TimeUnit.MILLISECONDS)); setTime(TIME_10_MIN + 10_000L); // Should call back by 11 seconds (6 earlier + 5 now) - assertTrue(mCountDownLatch.await(5_000L, TimeUnit.MILLISECONDS)); + assertTrue(mLimitReachedLatch.await(5_000L, TimeUnit.MILLISECONDS)); + stopUsage(PKG_SOC1); + // Usage has stopped, Session should end in a second. Verify session end occurs in a second + // (+/- 100ms, which is hopefully not too slim a margin) + assertFalse(mSessionEndLatch.await(900L, TimeUnit.MILLISECONDS)); + assertTrue(mSessionEndLatch.await(200L, TimeUnit.MILLISECONDS)); // Verify that the observer was removed - assertFalse(hasObserver(OBS_ID1)); + assertTrue(hasUsageSessionObserver(UID, OBS_ID1)); } - /** Verify that App Time Limit Controller will limit the number of observerIds */ + /** + * Verify that App Time Limit Controller will limit the number of observerIds for app usage + * observers + */ @Test - public void testMaxObserverLimit() throws Exception { + public void testAppUsageObserver_MaxObserverLimit() throws Exception { boolean receivedException = false; int ANOTHER_UID = UID + 1; - addObserver(OBS_ID1, GROUP1, TIME_30_MIN); - addObserver(OBS_ID2, GROUP1, TIME_30_MIN); - addObserver(OBS_ID3, GROUP1, TIME_30_MIN); - addObserver(OBS_ID4, GROUP1, TIME_30_MIN); - addObserver(OBS_ID5, GROUP1, TIME_30_MIN); - addObserver(OBS_ID6, GROUP1, TIME_30_MIN); - addObserver(OBS_ID7, GROUP1, TIME_30_MIN); - addObserver(OBS_ID8, GROUP1, TIME_30_MIN); - addObserver(OBS_ID9, GROUP1, TIME_30_MIN); - addObserver(OBS_ID10, GROUP1, TIME_30_MIN); + addAppUsageObserver(OBS_ID1, GROUP1, TIME_30_MIN); + addAppUsageObserver(OBS_ID2, GROUP1, TIME_30_MIN); + addAppUsageObserver(OBS_ID3, GROUP1, TIME_30_MIN); + addAppUsageObserver(OBS_ID4, GROUP1, TIME_30_MIN); + addAppUsageObserver(OBS_ID5, GROUP1, TIME_30_MIN); + addAppUsageObserver(OBS_ID6, GROUP1, TIME_30_MIN); + addAppUsageObserver(OBS_ID7, GROUP1, TIME_30_MIN); + addAppUsageObserver(OBS_ID8, GROUP1, TIME_30_MIN); + addAppUsageObserver(OBS_ID9, GROUP1, TIME_30_MIN); + addAppUsageObserver(OBS_ID10, GROUP1, TIME_30_MIN); // Readding an observer should not cause an IllegalStateException - addObserver(OBS_ID5, GROUP1, TIME_30_MIN); + addAppUsageObserver(OBS_ID5, GROUP1, TIME_30_MIN); // Adding an observer for a different uid shouldn't cause an IllegalStateException - mController.addObserver(ANOTHER_UID, OBS_ID11, GROUP1, TIME_30_MIN, null, USER_ID); + mController.addAppUsageObserver(ANOTHER_UID, OBS_ID11, GROUP1, TIME_30_MIN, null, USER_ID); try { - addObserver(OBS_ID11, GROUP1, TIME_30_MIN); + addAppUsageObserver(OBS_ID11, GROUP1, TIME_30_MIN); } catch (IllegalStateException ise) { receivedException = true; } assertTrue("Should have caused an IllegalStateException", receivedException); } - /** Verify that addObserver minimum time limit is one minute */ + /** + * Verify that App Time Limit Controller will limit the number of observerIds for usage session + * observers + */ @Test - public void testMinimumTimeLimit() throws Exception { + public void testUsageSessionObserver_MaxObserverLimit() throws Exception { + addUsageSessionObserver(OBS_ID1, GROUP1, TIME_30_MIN, TIME_1_MIN); + boolean receivedException = false; + int ANOTHER_UID = UID + 1; + addUsageSessionObserver(OBS_ID2, GROUP1, TIME_30_MIN, TIME_1_MIN); + addUsageSessionObserver(OBS_ID3, GROUP1, TIME_30_MIN, TIME_1_MIN); + addUsageSessionObserver(OBS_ID4, GROUP1, TIME_30_MIN, TIME_1_MIN); + addUsageSessionObserver(OBS_ID5, GROUP1, TIME_30_MIN, TIME_1_MIN); + addUsageSessionObserver(OBS_ID6, GROUP1, TIME_30_MIN, TIME_1_MIN); + addUsageSessionObserver(OBS_ID7, GROUP1, TIME_30_MIN, TIME_1_MIN); + addUsageSessionObserver(OBS_ID8, GROUP1, TIME_30_MIN, TIME_1_MIN); + addUsageSessionObserver(OBS_ID9, GROUP1, TIME_30_MIN, TIME_1_MIN); + addUsageSessionObserver(OBS_ID10, GROUP1, TIME_30_MIN, TIME_1_MIN); + // Readding an observer should not cause an IllegalStateException + addUsageSessionObserver(OBS_ID5, GROUP1, TIME_30_MIN, TIME_1_MIN); + // Adding an observer for a different uid shouldn't cause an IllegalStateException + mController.addUsageSessionObserver(ANOTHER_UID, OBS_ID11, GROUP1, TIME_30_MIN, TIME_1_MIN, + null, null, USER_ID); + try { + addUsageSessionObserver(OBS_ID11, GROUP1, TIME_30_MIN, TIME_1_MIN); + } catch (IllegalStateException ise) { + receivedException = true; + } + assertTrue("Should have caused an IllegalStateException", receivedException); + } + + /** Verify that addAppUsageObserver minimum time limit is one minute */ + @Test + public void testAppUsageObserver_MinimumTimeLimit() throws Exception { boolean receivedException = false; // adding an observer with a one minute time limit should not cause an exception - addObserver(OBS_ID1, GROUP1, MIN_TIME_LIMIT); + addAppUsageObserver(OBS_ID1, GROUP1, MIN_TIME_LIMIT); try { - addObserver(OBS_ID1, GROUP1, MIN_TIME_LIMIT - 1); + addAppUsageObserver(OBS_ID1, GROUP1, MIN_TIME_LIMIT - 1); } catch (IllegalArgumentException iae) { receivedException = true; } assertTrue("Should have caused an IllegalArgumentException", receivedException); } - private void moveToForeground(String packageName) { - mController.moveToForeground(packageName, "class", USER_ID); + /** Verify that addUsageSessionObserver minimum time limit is one minute */ + @Test + public void testUsageSessionObserver_MinimumTimeLimit() throws Exception { + boolean receivedException = false; + // test also for session observers + addUsageSessionObserver(OBS_ID10, GROUP1, MIN_TIME_LIMIT, TIME_1_MIN); + try { + addUsageSessionObserver(OBS_ID10, GROUP1, MIN_TIME_LIMIT - 1, TIME_1_MIN); + } catch (IllegalArgumentException iae) { + receivedException = true; + } + assertTrue("Should have caused an IllegalArgumentException", receivedException); + } + + /** Verify that concurrent usage from multiple apps in the same group will counted correctly */ + @Test + public void testAppUsageObserver_ConcurrentUsage() throws Exception { + setTime(0L); + addAppUsageObserver(OBS_ID1, GROUP1, TIME_30_MIN); + AppTimeLimitController.UsageGroup group = mController.getAppUsageGroup(UID, OBS_ID1); + startUsage(PKG_SOC1); + // Add 10 mins + setTime(TIME_10_MIN); + + // Add a different package in the group will first package is still in use + startUsage(PKG_GAME1); + setTime(TIME_10_MIN * 2); + // Stop first package usage + stopUsage(PKG_SOC1); + + setTime(TIME_30_MIN); + stopUsage(PKG_GAME1); + + assertEquals(TIME_30_MIN, group.getUsageTimeMs()); + assertTrue(mLimitReachedLatch.await(100L, TimeUnit.MILLISECONDS)); + } + + /** Verify that concurrent usage from multiple apps in the same group will counted correctly */ + @Test + public void testUsageSessionObserver_ConcurrentUsage() throws Exception { + setTime(0L); + addUsageSessionObserver(OBS_ID1, GROUP1, TIME_30_MIN, TIME_1_MIN); + AppTimeLimitController.UsageGroup group = mController.getSessionUsageGroup(UID, OBS_ID1); + startUsage(PKG_SOC1); + // Add 10 mins + setTime(TIME_10_MIN); + + // Add a different package in the group will first package is still in use + startUsage(PKG_GAME1); + setTime(TIME_10_MIN * 2); + // Stop first package usage + stopUsage(PKG_SOC1); + + setTime(TIME_30_MIN); + stopUsage(PKG_GAME1); + + assertEquals(TIME_30_MIN, group.getUsageTimeMs()); + assertTrue(mLimitReachedLatch.await(100L, TimeUnit.MILLISECONDS)); + } + + /** Verify that a session will continue if usage starts again within the session threshold */ + @Test + public void testUsageSessionObserver_ContinueSession() throws Exception { + setTime(0L); + addUsageSessionObserver(OBS_ID1, GROUP1, 10_000L, 2_000L); + startUsage(PKG_SOC1); + setTime(6_000L); + stopUsage(PKG_SOC1); + // Wait momentarily, Session should not end + assertFalse(mSessionEndLatch.await(1_000L, TimeUnit.MILLISECONDS)); + + setTime(7_000L); + startUsage(PKG_SOC1); + setTime(10_500L); + stopUsage(PKG_SOC1); + // Total usage time has not reached the limit. Time limit callback should not fire yet + assertFalse(mLimitReachedLatch.await(100L, TimeUnit.MILLISECONDS)); + + setTime(10_600L); + startUsage(PKG_SOC1); + setTime(12_000L); + assertTrue(mLimitReachedLatch.await(1_000L, TimeUnit.MILLISECONDS)); + stopUsage(PKG_SOC1); + // Usage has stopped, Session should end in 2 seconds. Verify session end occurs + // (+/- 100ms, which is hopefully not too slim a margin) + assertFalse(mSessionEndLatch.await(1_900L, TimeUnit.MILLISECONDS)); + assertTrue(mSessionEndLatch.await(200L, TimeUnit.MILLISECONDS)); + // Verify that the observer was not removed + assertTrue(hasUsageSessionObserver(UID, OBS_ID1)); + } + + /** Verify that a new session will start if next usage starts after the session threshold */ + @Test + public void testUsageSessionObserver_NewSession() throws Exception { + setTime(0L); + addUsageSessionObserver(OBS_ID1, GROUP1, 10_000L, 1_000L); + startUsage(PKG_SOC1); + setTime(6_000L); + stopUsage(PKG_SOC1); + // Wait for longer than the session threshold. Session end callback should not be triggered + // because the usage timelimit hasn't been triggered. + assertFalse(mSessionEndLatch.await(1_500L, TimeUnit.MILLISECONDS)); + + setTime(7_500L); + // This should be the start of a new session + startUsage(PKG_SOC1); + setTime(16_000L); + stopUsage(PKG_SOC1); + // Total usage has exceed the timelimit, but current session time has not + assertFalse(mLimitReachedLatch.await(100L, TimeUnit.MILLISECONDS)); + + setTime(16_100L); + startUsage(PKG_SOC1); + setTime(18_000L); + assertTrue(mLimitReachedLatch.await(2000L, TimeUnit.MILLISECONDS)); + stopUsage(PKG_SOC1); + // Usage has stopped, Session should end in 2 seconds. Verify session end occurs + // (+/- 100ms, which is hopefully not too slim a margin) + assertFalse(mSessionEndLatch.await(900L, TimeUnit.MILLISECONDS)); + assertTrue(mSessionEndLatch.await(200L, TimeUnit.MILLISECONDS)); + // Verify that the observer was not removed + assertTrue(hasUsageSessionObserver(UID, OBS_ID1)); + } + + /** Verify that the callbacks will be triggered for multiple sessions */ + @Test + public void testUsageSessionObserver_RepeatSessions() throws Exception { + setTime(0L); + addUsageSessionObserver(OBS_ID1, GROUP1, 10_000L, 1_000L); + startUsage(PKG_SOC1); + setTime(9_000L); + stopUsage(PKG_SOC1); + // Stutter usage here, to reduce real world time needed trigger limit reached callback + startUsage(PKG_SOC1); + setTime(11_000L); + assertTrue(mLimitReachedLatch.await(2_000L, TimeUnit.MILLISECONDS)); + stopUsage(PKG_SOC1); + // Usage has stopped, Session should end in 1 seconds. Verify session end occurs + // (+/- 100ms, which is hopefully not too slim a margin) + assertFalse(mSessionEndLatch.await(900L, TimeUnit.MILLISECONDS)); + assertTrue(mSessionEndLatch.await(200L, TimeUnit.MILLISECONDS)); + + // Rearm the countdown latches + mLimitReachedLatch = new CountDownLatch(1); + mSessionEndLatch = new CountDownLatch(1); + + // New session start + setTime(20_000L); + startUsage(PKG_SOC1); + setTime(29_000L); + stopUsage(PKG_SOC1); + startUsage(PKG_SOC1); + setTime(31_000L); + assertTrue(mLimitReachedLatch.await(2_000L, TimeUnit.MILLISECONDS)); + stopUsage(PKG_SOC1); + assertFalse(mSessionEndLatch.await(900L, TimeUnit.MILLISECONDS)); + assertTrue(mSessionEndLatch.await(200L, TimeUnit.MILLISECONDS)); + assertTrue(hasUsageSessionObserver(UID, OBS_ID1)); + } + + private void startUsage(String packageName) { + mController.noteUsageStart(packageName, USER_ID); + } + + private void stopUsage(String packageName) { + mController.noteUsageStop(packageName, USER_ID); + } + + private void addAppUsageObserver(int observerId, String[] packages, long timeLimit) { + mController.addAppUsageObserver(UID, observerId, packages, timeLimit, null, USER_ID); } - private void moveToBackground(String packageName) { - mController.moveToBackground(packageName, "class", USER_ID); + private void addUsageSessionObserver(int observerId, String[] packages, long timeLimit, + long sessionThreshold) { + mController.addUsageSessionObserver(UID, observerId, packages, timeLimit, sessionThreshold, + null, null, USER_ID); } - private void addObserver(int observerId, String[] packages, long timeLimit) { - mController.addObserver(UID, observerId, packages, timeLimit, null, USER_ID); + /** Is there still an app usage observer by that id */ + private boolean hasAppUsageObserver(int uid, int observerId) { + return mController.getAppUsageGroup(uid, observerId) != null; } - /** Is there still an observer by that id */ - private boolean hasObserver(int observerId) { - return mController.getObserverGroup(observerId, USER_ID) != null; + /** Is there still an usage session observer by that id */ + private boolean hasUsageSessionObserver(int uid, int observerId) { + return mController.getSessionUsageGroup(uid, observerId) != null; } private void setTime(long time) { diff --git a/services/tests/servicestests/src/com/android/server/wm/DisplaySettingsTests.java b/services/tests/servicestests/src/com/android/server/wm/DisplaySettingsTests.java index a028d5eeb02e..3be125815d21 100644 --- a/services/tests/servicestests/src/com/android/server/wm/DisplaySettingsTests.java +++ b/services/tests/servicestests/src/com/android/server/wm/DisplaySettingsTests.java @@ -21,6 +21,9 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.doAnswer; import android.app.WindowConfiguration; import android.platform.test.annotations.Presubmit; @@ -68,10 +71,9 @@ public class DisplaySettingsTests extends WindowTestsBase { sWm.setIsPc(false); mTarget = new DisplaySettings(sWm, mTestFolder); - mTarget.readSettingsLocked(); mPrimaryDisplay = sWm.getDefaultDisplayContentLocked(); - mSecondaryDisplay = createNewDisplay(); + mSecondaryDisplay = mDisplayContent; assertNotEquals(Display.DEFAULT_DISPLAY, mSecondaryDisplay.getDisplayId()); } @@ -134,6 +136,65 @@ public class DisplaySettingsTests extends WindowTestsBase { } @Test + public void testDefaultToOriginalMetrics() { + final int originalWidth = mSecondaryDisplay.mBaseDisplayWidth; + final int originalHeight = mSecondaryDisplay.mBaseDisplayHeight; + final int originalDensity = mSecondaryDisplay.mBaseDisplayDensity; + final boolean originalScalingDisabled = mSecondaryDisplay.mDisplayScalingDisabled; + + mTarget.applySettingsToDisplayLocked(mSecondaryDisplay); + + assertEquals(originalWidth, mSecondaryDisplay.mBaseDisplayWidth); + assertEquals(originalHeight, mSecondaryDisplay.mBaseDisplayHeight); + assertEquals(originalDensity, mSecondaryDisplay.mBaseDisplayDensity); + assertEquals(originalScalingDisabled, mSecondaryDisplay.mDisplayScalingDisabled); + } + + @Test + public void testSetForcedSize() { + final DisplayInfo originalInfo = new DisplayInfo(mSecondaryDisplay.getDisplayInfo()); + // Provides the orginal display info to avoid changing initial display size. + doAnswer(invocation -> { + ((DisplayInfo) invocation.getArguments()[1]).copyFrom(originalInfo); + return null; + }).when(sWm.mDisplayManagerInternal).getNonOverrideDisplayInfo(anyInt(), any()); + + mTarget.setForcedSize(mSecondaryDisplay, 1000 /* width */, 2000 /* height */); + applySettingsToDisplayByNewInstance(mSecondaryDisplay); + + assertEquals(1000 /* width */, mSecondaryDisplay.mBaseDisplayWidth); + assertEquals(2000 /* height */, mSecondaryDisplay.mBaseDisplayHeight); + + sWm.clearForcedDisplaySize(mSecondaryDisplay.getDisplayId()); + assertEquals(mSecondaryDisplay.mInitialDisplayWidth, mSecondaryDisplay.mBaseDisplayWidth); + assertEquals(mSecondaryDisplay.mInitialDisplayHeight, mSecondaryDisplay.mBaseDisplayHeight); + } + + @Test + public void testSetForcedDensity() { + mTarget.setForcedDensity(mSecondaryDisplay, 600 /* density */, 0 /* userId */); + applySettingsToDisplayByNewInstance(mSecondaryDisplay); + + assertEquals(600 /* density */, mSecondaryDisplay.mBaseDisplayDensity); + + sWm.clearForcedDisplayDensityForUser(mSecondaryDisplay.getDisplayId(), 0 /* userId */); + assertEquals(mSecondaryDisplay.mInitialDisplayDensity, + mSecondaryDisplay.mBaseDisplayDensity); + } + + @Test + public void testSetForcedScalingMode() { + mTarget.setForcedScalingMode(mSecondaryDisplay, DisplayContent.FORCE_SCALING_MODE_DISABLED); + applySettingsToDisplayByNewInstance(mSecondaryDisplay); + + assertTrue(mSecondaryDisplay.mDisplayScalingDisabled); + + sWm.setForcedDisplayScalingMode(mSecondaryDisplay.getDisplayId(), + DisplayContent.FORCE_SCALING_MODE_AUTO); + assertFalse(mSecondaryDisplay.mDisplayScalingDisabled); + } + + @Test public void testDefaultToZeroOverscan() { mTarget.applySettingsToDisplayLocked(mPrimaryDisplay); @@ -143,8 +204,7 @@ public class DisplaySettingsTests extends WindowTestsBase { @Test public void testPersistOverscanInSameInstance() { final DisplayInfo info = mPrimaryDisplay.getDisplayInfo(); - mTarget.setOverscanLocked(info.uniqueId, info.name, 1 /* left */, 2 /* top */, - 3 /* right */, 4 /* bottom */); + mTarget.setOverscanLocked(info, 1 /* left */, 2 /* top */, 3 /* right */, 4 /* bottom */); mTarget.applySettingsToDisplayLocked(mPrimaryDisplay); @@ -154,14 +214,9 @@ public class DisplaySettingsTests extends WindowTestsBase { @Test public void testPersistOverscanAcrossInstances() { final DisplayInfo info = mPrimaryDisplay.getDisplayInfo(); - mTarget.setOverscanLocked(info.uniqueId, info.name, 1 /* left */, 2 /* top */, - 3 /* right */, 4 /* bottom */); - mTarget.writeSettingsLocked(); - - DisplaySettings target = new DisplaySettings(sWm, mTestFolder); - target.readSettingsLocked(); + mTarget.setOverscanLocked(info, 1 /* left */, 2 /* top */, 3 /* right */, 4 /* bottom */); - target.applySettingsToDisplayLocked(mPrimaryDisplay); + applySettingsToDisplayByNewInstance(mPrimaryDisplay); assertOverscan(mPrimaryDisplay, 1 /* left */, 2 /* top */, 3 /* right */, 4 /* bottom */); } @@ -208,12 +263,8 @@ public class DisplaySettingsTests extends WindowTestsBase { public void testPersistUserRotationModeAcrossInstances() { mTarget.setUserRotation(mSecondaryDisplay, WindowManagerPolicy.USER_ROTATION_LOCKED, Surface.ROTATION_270); - mTarget.writeSettingsLocked(); - - DisplaySettings target = new DisplaySettings(sWm, mTestFolder); - target.readSettingsLocked(); - target.applySettingsToDisplayLocked(mSecondaryDisplay); + applySettingsToDisplayByNewInstance(mSecondaryDisplay); final DisplayRotation rotation = mSecondaryDisplay.getDisplayRotation(); assertEquals(WindowManagerPolicy.USER_ROTATION_LOCKED, rotation.getUserRotationMode()); @@ -225,7 +276,7 @@ public class DisplaySettingsTests extends WindowTestsBase { mTarget.setUserRotation(mSecondaryDisplay, WindowManagerPolicy.USER_ROTATION_LOCKED, Surface.ROTATION_270); - mTarget.applySettingsToDisplayLocked(mSecondaryDisplay); + applySettingsToDisplayByNewInstance(mSecondaryDisplay); assertEquals(Surface.ROTATION_270, mSecondaryDisplay.getDisplayRotation().getUserRotation()); @@ -241,6 +292,15 @@ public class DisplaySettingsTests extends WindowTestsBase { assertEquals(bottom, info.overscanBottom); } + /** + * This method helps to ensure read and write persistent settings successfully because the + * constructor of {@link DisplaySettings} should read the persistent file from the given path + * that also means the previous state must be written correctly. + */ + private void applySettingsToDisplayByNewInstance(DisplayContent display) { + new DisplaySettings(sWm, mTestFolder).applySettingsToDisplayLocked(display); + } + private static boolean deleteRecursively(File file) { if (file.isFile()) { return file.delete(); diff --git a/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java index cf67d786056c..d0a81b2ddfb2 100644 --- a/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java +++ b/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java @@ -75,6 +75,7 @@ class WindowTestsBase { private static int sNextDisplayId = DEFAULT_DISPLAY + 1; static int sNextStackId = 1000; + /** Non-default display. */ DisplayContent mDisplayContent; DisplayInfo mDisplayInfo = new DisplayInfo(); WindowState mWallpaperWindow; diff --git a/services/usage/java/com/android/server/usage/AppTimeLimitController.java b/services/usage/java/com/android/server/usage/AppTimeLimitController.java index 5916b04c079a..eaaf9b2210db 100644 --- a/services/usage/java/com/android/server/usage/AppTimeLimitController.java +++ b/services/usage/java/com/android/server/usage/AppTimeLimitController.java @@ -22,17 +22,16 @@ import android.os.Handler; import android.os.Looper; import android.os.Message; import android.os.SystemClock; -import android.text.TextUtils; import android.util.ArrayMap; +import android.util.ArraySet; import android.util.Slog; import android.util.SparseArray; -import android.util.SparseIntArray; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.util.ArrayUtils; import java.io.PrintWriter; +import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.Arrays; @@ -57,72 +56,432 @@ public class AppTimeLimitController { private final MyHandler mHandler; - private OnLimitReachedListener mListener; + private TimeLimitCallbackListener mListener; private static final long MAX_OBSERVER_PER_UID = 1000; private static final long ONE_MINUTE = 60_000L; + /** Collection of data for each user that has reported usage */ @GuardedBy("mLock") private final SparseArray<UserData> mUsers = new SparseArray<>(); - private static class UserData { + /** + * Collection of data for each app that is registering observers + * WARNING: Entries are currently not removed, based on the assumption there are a small + * fixed number of apps on device that can register observers. + */ + @GuardedBy("mLock") + private final SparseArray<ObserverAppData> mObserverApps = new SparseArray<>(); + + private class UserData { /** userId of the user */ - private @UserIdInt int userId; + private @UserIdInt + int userId; + + /** Set of the currently active entities */ + private final ArraySet<String> currentlyActive = new ArraySet<>(); - /** The app that is currently in the foreground */ - private String currentForegroundedPackage; + /** Map from entity name for quick lookup */ + private final ArrayMap<String, ArrayList<UsageGroup>> observedMap = new ArrayMap<>(); + + private UserData(@UserIdInt int userId) { + this.userId = userId; + } - /** The time when the current app came to the foreground */ - private long currentForegroundedTime; + @GuardedBy("mLock") + boolean isActive(String[] entities) { + // TODO: Consider using a bloom filter here if number of actives becomes large + final int size = entities.length; + for (int i = 0; i < size; i++) { + if (currentlyActive.contains(entities[i])) { + return true; + } + } + return false; + } - /** Map from package name for quick lookup */ - private ArrayMap<String, ArrayList<TimeLimitGroup>> packageMap = new ArrayMap<>(); + @GuardedBy("mLock") + void addUsageGroup(UsageGroup group) { + final int size = group.mObserved.length; + for (int i = 0; i < size; i++) { + ArrayList<UsageGroup> list = observedMap.get(group.mObserved[i]); + if (list == null) { + list = new ArrayList<>(); + observedMap.put(group.mObserved[i], list); + } + list.add(group); + } + } + + @GuardedBy("mLock") + void removeUsageGroup(UsageGroup group) { + final int size = group.mObserved.length; + for (int i = 0; i < size; i++) { + final ArrayList<UsageGroup> list = observedMap.get(group.mObserved[i]); + if (list != null) { + list.remove(group); + } + } + } + + @GuardedBy("mLock") + void dump(PrintWriter pw) { + pw.print(" userId="); + pw.println(userId); + pw.print(" Currently Active:"); + final int nActive = currentlyActive.size(); + for (int i = 0; i < nActive; i++) { + pw.print(currentlyActive.valueAt(i)); + pw.print(", "); + } + pw.println(); + pw.print(" Observed Entities:"); + final int nEntities = currentlyActive.size(); + for (int i = 0; i < nEntities; i++) { + pw.print(observedMap.keyAt(i)); + pw.print(", "); + } + pw.println(); + } + } + + + private class ObserverAppData { + /** uid of the observing app */ + private int uid; /** Map of observerId to details of the time limit group */ - private SparseArray<TimeLimitGroup> groups = new SparseArray<>(); + SparseArray<AppUsageGroup> appUsageGroups = new SparseArray<>(); - /** Map of the number of observerIds registered by uid */ - private SparseIntArray observerIdCounts = new SparseIntArray(); + /** Map of observerId to details of the time limit group */ + SparseArray<SessionUsageGroup> sessionUsageGroups = new SparseArray<>(); - private UserData(@UserIdInt int userId) { - this.userId = userId; + private ObserverAppData(int uid) { + this.uid = uid; + } + + @GuardedBy("mLock") + void removeAppUsageGroup(int observerId) { + appUsageGroups.remove(observerId); + } + + @GuardedBy("mLock") + void removeSessionUsageGroup(int observerId) { + sessionUsageGroups.remove(observerId); + } + + + @GuardedBy("mLock") + void dump(PrintWriter pw) { + pw.print(" uid="); + pw.println(uid); + pw.println(" App Usage Groups:"); + final int nAppUsageGroups = appUsageGroups.size(); + for (int i = 0; i < nAppUsageGroups; i++) { + appUsageGroups.valueAt(i).dump(pw); + pw.println(); + } + pw.println(" Session Usage Groups:"); + final int nSessionUsageGroups = appUsageGroups.size(); + for (int i = 0; i < nSessionUsageGroups; i++) { + sessionUsageGroups.valueAt(i).dump(pw); + pw.println(); + } } } /** * Listener interface for being informed when an app group's time limit is reached. */ - public interface OnLimitReachedListener { + public interface TimeLimitCallbackListener { /** * Time limit for a group, keyed by the observerId, has been reached. - * @param observerId The observerId of the group whose limit was reached - * @param userId The userId - * @param timeLimit The original time limit in milliseconds - * @param timeElapsed How much time was actually spent on apps in the group, in milliseconds + * + * @param observerId The observerId of the group whose limit was reached + * @param userId The userId + * @param timeLimit The original time limit in milliseconds + * @param timeElapsed How much time was actually spent on apps in the group, in + * milliseconds * @param callbackIntent The PendingIntent to send when the limit is reached */ public void onLimitReached(int observerId, @UserIdInt int userId, long timeLimit, long timeElapsed, PendingIntent callbackIntent); + + /** + * Session ended for a group, keyed by the observerId, after limit was reached. + * + * @param observerId The observerId of the group whose limit was reached + * @param userId The userId + * @param timeElapsed How much time was actually spent on apps in the group, in + * milliseconds + * @param callbackIntent The PendingIntent to send when the limit is reached + */ + public void onSessionEnd(int observerId, @UserIdInt int userId, long timeElapsed, + PendingIntent callbackIntent); } - static class TimeLimitGroup { - int requestingUid; - int observerId; - String[] packages; - long timeLimit; - long timeRequested; - long timeRemaining; - PendingIntent callbackIntent; - String currentPackage; - long timeCurrentPackageStarted; - int userId; + abstract class UsageGroup { + protected int mObserverId; + protected String[] mObserved; + protected long mTimeLimitMs; + protected long mUsageTimeMs; + protected int mActives; + protected long mLastKnownUsageTimeMs; + protected WeakReference<UserData> mUserRef; + protected WeakReference<ObserverAppData> mObserverAppRef; + protected PendingIntent mLimitReachedCallback; + + UsageGroup(UserData user, ObserverAppData observerApp, int observerId, String[] observed, + long timeLimitMs, PendingIntent limitReachedCallback) { + mUserRef = new WeakReference<>(user); + mObserverAppRef = new WeakReference<>(observerApp); + mObserverId = observerId; + mObserved = observed; + mTimeLimitMs = timeLimitMs; + mLimitReachedCallback = limitReachedCallback; + } + + @GuardedBy("mLock") + public long getTimeLimitMs() { return mTimeLimitMs; } + + @GuardedBy("mLock") + public long getUsageTimeMs() { return mUsageTimeMs; } + + @GuardedBy("mLock") + public void remove() { + UserData user = mUserRef.get(); + if (user != null) { + user.removeUsageGroup(this); + } + // Clear the callback, so any racy inflight message will do nothing + mLimitReachedCallback = null; + } + + @GuardedBy("mLock") + void noteUsageStart(long startTimeMs) { + noteUsageStart(startTimeMs, startTimeMs); + } + + @GuardedBy("mLock") + void noteUsageStart(long startTimeMs, long currentTimeMs) { + if (mActives++ == 0) { + mLastKnownUsageTimeMs = startTimeMs; + final long timeRemaining = + mTimeLimitMs - mUsageTimeMs + currentTimeMs - startTimeMs; + if (timeRemaining > 0) { + if (DEBUG) { + Slog.d(TAG, "Posting timeout for " + mObserverId + " for " + + timeRemaining + "ms"); + } + postCheckTimeoutLocked(this, timeRemaining); + } + } else { + if (mActives > mObserved.length) { + // Try to get to a sane state and log the issue + mActives = mObserved.length; + final UserData user = mUserRef.get(); + if (user == null) return; + final Object[] array = user.currentlyActive.toArray(); + Slog.e(TAG, + "Too many noted usage starts! Observed entities: " + Arrays.toString( + mObserved) + " Active Entities: " + Arrays.toString(array)); + } + } + } + + @GuardedBy("mLock") + void noteUsageStop(long stopTimeMs) { + if (--mActives == 0) { + final boolean limitNotCrossed = mUsageTimeMs < mTimeLimitMs; + mUsageTimeMs += stopTimeMs - mLastKnownUsageTimeMs; + if (limitNotCrossed && mUsageTimeMs >= mTimeLimitMs) { + // Crossed the limit + if (DEBUG) Slog.d(TAG, "MTB informing group obs=" + mObserverId); + postInformLimitReachedListenerLocked(this); + } + cancelCheckTimeoutLocked(this); + } else { + if (mActives < 0) { + // Try to get to a sane state and log the issue + mActives = 0; + final UserData user = mUserRef.get(); + if (user == null) return; + final Object[] array = user.currentlyActive.toArray(); + Slog.e(TAG, + "Too many noted usage stops! Observed entities: " + Arrays.toString( + mObserved) + " Active Entities: " + Arrays.toString(array)); + } + } + } + + @GuardedBy("mLock") + void checkTimeout(long currentTimeMs) { + final UserData user = mUserRef.get(); + if (user == null) return; + + long timeRemainingMs = mTimeLimitMs - mUsageTimeMs; + + if (DEBUG) Slog.d(TAG, "checkTimeout timeRemaining=" + timeRemainingMs); + + // Already reached the limit, no need to report again + if (timeRemainingMs <= 0) return; + + if (DEBUG) { + Slog.d(TAG, "checkTimeout"); + } + + // Double check that at least one entity in this group is currently active + if (user.isActive(mObserved)) { + if (DEBUG) { + Slog.d(TAG, "checkTimeout group is active"); + } + final long timeUsedMs = currentTimeMs - mLastKnownUsageTimeMs; + if (timeRemainingMs <= timeUsedMs) { + if (DEBUG) Slog.d(TAG, "checkTimeout : Time limit reached"); + // Hit the limit, set timeRemaining to zero to avoid checking again + mUsageTimeMs += timeUsedMs; + mLastKnownUsageTimeMs = currentTimeMs; + AppTimeLimitController.this.postInformLimitReachedListenerLocked(this); + } else { + if (DEBUG) Slog.d(TAG, "checkTimeout : Some more time remaining"); + AppTimeLimitController.this.postCheckTimeoutLocked(this, + timeRemainingMs - timeUsedMs); + } + } + } + + @GuardedBy("mLock") + public void onLimitReached() { + UserData user = mUserRef.get(); + if (user == null) return; + if (mListener != null) { + mListener.onLimitReached(mObserverId, user.userId, mTimeLimitMs, mUsageTimeMs, + mLimitReachedCallback); + } + } + + @GuardedBy("mLock") + void dump(PrintWriter pw) { + pw.print(" Group id="); + pw.print(mObserverId); + pw.print(" timeLimit="); + pw.print(mTimeLimitMs); + pw.print(" used="); + pw.print(mUsageTimeMs); + pw.print(" lastKnownUsage="); + pw.print(mLastKnownUsageTimeMs); + pw.print(" mActives="); + pw.print(mActives); + pw.print(" observed="); + pw.print(Arrays.toString(mObserved)); + } } - private class MyHandler extends Handler { + class AppUsageGroup extends UsageGroup { + public AppUsageGroup(UserData user, ObserverAppData observerApp, int observerId, + String[] observed, long timeLimitMs, PendingIntent limitReachedCallback) { + super(user, observerApp, observerId, observed, timeLimitMs, limitReachedCallback); + } + @Override + @GuardedBy("mLock") + public void remove() { + super.remove(); + ObserverAppData observerApp = mObserverAppRef.get(); + if (observerApp != null) { + observerApp.removeAppUsageGroup(mObserverId); + } + } + + @Override + @GuardedBy("mLock") + public void onLimitReached() { + super.onLimitReached(); + // Unregister since the limit has been met and observer was informed. + remove(); + } + } + + class SessionUsageGroup extends UsageGroup { + private long mLastUsageEndTimeMs; + private long mNewSessionThresholdMs; + private PendingIntent mSessionEndCallback; + + public SessionUsageGroup(UserData user, ObserverAppData observerApp, int observerId, + String[] observed, long timeLimitMs, PendingIntent limitReachedCallback, + long newSessionThresholdMs, PendingIntent sessionEndCallback) { + super(user, observerApp, observerId, observed, timeLimitMs, limitReachedCallback); + this.mNewSessionThresholdMs = newSessionThresholdMs; + this.mSessionEndCallback = sessionEndCallback; + } + + @Override + @GuardedBy("mLock") + public void remove() { + super.remove(); + ObserverAppData observerApp = mObserverAppRef.get(); + if (observerApp != null) { + observerApp.removeSessionUsageGroup(mObserverId); + } + // Clear the callback, so any racy inflight messages will do nothing + mSessionEndCallback = null; + } + + @Override + @GuardedBy("mLock") + public void noteUsageStart(long startTimeMs, long currentTimeMs) { + if (mActives == 0) { + if (startTimeMs - mLastUsageEndTimeMs > mNewSessionThresholdMs) { + // New session has started, clear usage time. + mUsageTimeMs = 0; + } + AppTimeLimitController.this.cancelInformSessionEndListener(this); + } + super.noteUsageStart(startTimeMs, currentTimeMs); + } + + @Override + @GuardedBy("mLock") + public void noteUsageStop(long stopTimeMs) { + super.noteUsageStop(stopTimeMs); + if (mActives == 0) { + mLastUsageEndTimeMs = stopTimeMs; + if (mUsageTimeMs >= mTimeLimitMs) { + // Usage has ended. Schedule the session end callback to be triggered once + // the new session threshold has been reached + AppTimeLimitController.this.postInformSessionEndListenerLocked(this, + mNewSessionThresholdMs); + } + + } + } + + @GuardedBy("mLock") + public void onSessionEnd() { + UserData user = mUserRef.get(); + if (user == null) return; + if (mListener != null) { + mListener.onSessionEnd(mObserverId, user.userId, mUsageTimeMs, mSessionEndCallback); + } + } + + @Override + @GuardedBy("mLock") + void dump(PrintWriter pw) { + super.dump(pw); + pw.print(" lastUsageEndTime="); + pw.print(mLastUsageEndTimeMs); + pw.print(" newSessionThreshold="); + pw.print(mNewSessionThresholdMs); + } + } + + + private class MyHandler extends Handler { static final int MSG_CHECK_TIMEOUT = 1; - static final int MSG_INFORM_LISTENER = 2; + static final int MSG_INFORM_LIMIT_REACHED_LISTENER = 2; + static final int MSG_INFORM_SESSION_END = 3; MyHandler(Looper looper) { super(looper); @@ -132,10 +491,19 @@ public class AppTimeLimitController { public void handleMessage(Message msg) { switch (msg.what) { case MSG_CHECK_TIMEOUT: - checkTimeout((TimeLimitGroup) msg.obj); + synchronized (mLock) { + ((UsageGroup) msg.obj).checkTimeout(getUptimeMillis()); + } break; - case MSG_INFORM_LISTENER: - informListener((TimeLimitGroup) msg.obj); + case MSG_INFORM_LIMIT_REACHED_LISTENER: + synchronized (mLock) { + ((UsageGroup) msg.obj).onLimitReached(); + } + break; + case MSG_INFORM_SESSION_END: + synchronized (mLock) { + ((SessionUsageGroup) msg.obj).onSessionEnd(); + } break; default: super.handleMessage(msg); @@ -144,7 +512,7 @@ public class AppTimeLimitController { } } - public AppTimeLimitController(OnLimitReachedListener listener, Looper looper) { + public AppTimeLimitController(TimeLimitCallbackListener listener, Looper looper) { mHandler = new MyHandler(looper); mListener = listener; } @@ -157,7 +525,13 @@ public class AppTimeLimitController { /** Overrideable for testing purposes */ @VisibleForTesting - protected long getObserverPerUidLimit() { + protected long getAppUsageObserverPerUidLimit() { + return MAX_OBSERVER_PER_UID; + } + + /** Overrideable for testing purposes */ + @VisibleForTesting + protected long getUsageSessionObserverPerUidLimit() { return MAX_OBSERVER_PER_UID; } @@ -167,6 +541,21 @@ public class AppTimeLimitController { return ONE_MINUTE; } + @VisibleForTesting + AppUsageGroup getAppUsageGroup(int observerAppUid, int observerId) { + synchronized (mLock) { + return getOrCreateObserverAppDataLocked(observerAppUid).appUsageGroups.get(observerId); + } + } + + @VisibleForTesting + SessionUsageGroup getSessionUsageGroup(int observerAppUid, int observerId) { + synchronized (mLock) { + return getOrCreateObserverAppDataLocked(observerAppUid).sessionUsageGroups.get( + observerId); + } + } + /** Returns an existing UserData object for the given userId, or creates one */ @GuardedBy("mLock") private UserData getOrCreateUserDataLocked(int userId) { @@ -178,6 +567,17 @@ public class AppTimeLimitController { return userData; } + /** Returns an existing ObserverAppData object for the given uid, or creates one */ + @GuardedBy("mLock") + private ObserverAppData getOrCreateObserverAppDataLocked(int uid) { + ObserverAppData appData = mObserverApps.get(uid); + if (appData == null) { + appData = new ObserverAppData(uid); + mObserverApps.put(uid, appData); + } + return appData; + } + /** Clean up data if user is removed */ public void onUserRemoved(int userId) { synchronized (mLock) { @@ -187,300 +587,219 @@ public class AppTimeLimitController { } /** - * Registers an observer with the given details. Existing observer with the same observerId - * is removed. + * Check if group has any currently active entities. */ - public void addObserver(int requestingUid, int observerId, String[] packages, long timeLimit, - PendingIntent callbackIntent, @UserIdInt int userId) { + @GuardedBy("mLock") + private void noteActiveLocked(UserData user, UsageGroup group, long currentTimeMs) { + // TODO: Consider using a bloom filter here if number of actives becomes large + final int size = group.mObserved.length; + for (int i = 0; i < size; i++) { + if (user.currentlyActive.contains(group.mObserved[i])) { + // Entity is currently active. Start group's usage. + group.noteUsageStart(currentTimeMs); + } + } + } + /** + * Registers an app usage observer with the given details. + * Existing app usage observer with the same observerId will be removed. + */ + public void addAppUsageObserver(int requestingUid, int observerId, String[] observed, + long timeLimit, PendingIntent callbackIntent, @UserIdInt int userId) { if (timeLimit < getMinTimeLimit()) { throw new IllegalArgumentException("Time limit must be >= " + getMinTimeLimit()); } synchronized (mLock) { UserData user = getOrCreateUserDataLocked(userId); - removeObserverLocked(user, requestingUid, observerId, /*readding =*/ true); + ObserverAppData observerApp = getOrCreateObserverAppDataLocked(requestingUid); + AppUsageGroup group = observerApp.appUsageGroups.get(observerId); + if (group != null) { + // Remove previous app usage group associated with observerId + observerApp.appUsageGroups.get(observerId).remove(); + } - final int observerIdCount = user.observerIdCounts.get(requestingUid, 0); - if (observerIdCount >= getObserverPerUidLimit()) { + final int observerIdCount = observerApp.appUsageGroups.size(); + if (observerIdCount >= getAppUsageObserverPerUidLimit()) { throw new IllegalStateException( - "Too many observers added by uid " + requestingUid); + "Too many app usage observers added by uid " + requestingUid); } - user.observerIdCounts.put(requestingUid, observerIdCount + 1); - - TimeLimitGroup group = new TimeLimitGroup(); - group.observerId = observerId; - group.callbackIntent = callbackIntent; - group.packages = packages; - group.timeLimit = timeLimit; - group.timeRemaining = group.timeLimit; - group.timeRequested = getUptimeMillis(); - group.requestingUid = requestingUid; - group.timeCurrentPackageStarted = -1L; - group.userId = userId; - - user.groups.append(observerId, group); - - addGroupToPackageMapLocked(user, packages, group); + group = new AppUsageGroup(user, observerApp, observerId, observed, timeLimit, + callbackIntent); + observerApp.appUsageGroups.append(observerId, group); if (DEBUG) { - Slog.d(TAG, "addObserver " + packages + " for " + timeLimit); - } - // Handle the case where a target package is already in the foreground when observer - // is added. - if (user.currentForegroundedPackage != null && inPackageList(group.packages, - user.currentForegroundedPackage)) { - group.timeCurrentPackageStarted = group.timeRequested; - group.currentPackage = user.currentForegroundedPackage; - if (group.timeRemaining > 0) { - postCheckTimeoutLocked(group, group.timeRemaining); - } + Slog.d(TAG, "addObserver " + observed + " for " + timeLimit); } + + user.addUsageGroup(group); + noteActiveLocked(user, group, getUptimeMillis()); } } /** * Remove a registered observer by observerId and calling uid. + * * @param requestingUid The calling uid - * @param observerId The unique observer id for this user - * @param userId The user id of the observer + * @param observerId The unique observer id for this user + * @param userId The user id of the observer */ - public void removeObserver(int requestingUid, int observerId, @UserIdInt int userId) { + public void removeAppUsageObserver(int requestingUid, int observerId, @UserIdInt int userId) { synchronized (mLock) { - UserData user = getOrCreateUserDataLocked(userId); - removeObserverLocked(user, requestingUid, observerId, /*readding =*/ false); + ObserverAppData observerApp = getOrCreateObserverAppDataLocked(requestingUid); + observerApp.appUsageGroups.get(observerId).remove(); } } - @VisibleForTesting - TimeLimitGroup getObserverGroup(int observerId, int userId) { - synchronized (mLock) { - return getOrCreateUserDataLocked(userId).groups.get(observerId); - } - } - private static boolean inPackageList(String[] packages, String packageName) { - return ArrayUtils.contains(packages, packageName); - } + /** + * Registers a usage session observer with the given details. + * Existing usage session observer with the same observerId will be removed. + */ + public void addUsageSessionObserver(int requestingUid, int observerId, String[] observed, + long timeLimit, long sessionThresholdTime, + PendingIntent limitReachedCallbackIntent, PendingIntent sessionEndCallbackIntent, + @UserIdInt int userId) { + if (timeLimit < getMinTimeLimit()) { + throw new IllegalArgumentException("Time limit must be >= " + getMinTimeLimit()); + } + synchronized (mLock) { + UserData user = getOrCreateUserDataLocked(userId); + ObserverAppData observerApp = getOrCreateObserverAppDataLocked(requestingUid); + SessionUsageGroup group = observerApp.sessionUsageGroups.get(observerId); + if (group != null) { + // Remove previous app usage group associated with observerId + observerApp.sessionUsageGroups.get(observerId).remove(); + } - @GuardedBy("mLock") - private void removeObserverLocked(UserData user, int requestingUid, int observerId, - boolean readding) { - TimeLimitGroup group = user.groups.get(observerId); - if (group != null && group.requestingUid == requestingUid) { - removeGroupFromPackageMapLocked(user, group); - user.groups.remove(observerId); - mHandler.removeMessages(MyHandler.MSG_CHECK_TIMEOUT, group); - final int observerIdCount = user.observerIdCounts.get(requestingUid); - if (observerIdCount <= 1 && !readding) { - user.observerIdCounts.delete(requestingUid); - } else { - user.observerIdCounts.put(requestingUid, observerIdCount - 1); + final int observerIdCount = observerApp.sessionUsageGroups.size(); + if (observerIdCount >= getUsageSessionObserverPerUidLimit()) { + throw new IllegalStateException( + "Too many app usage observers added by uid " + requestingUid); } + group = new SessionUsageGroup(user, observerApp, observerId, observed, timeLimit, + limitReachedCallbackIntent, sessionThresholdTime, sessionEndCallbackIntent); + observerApp.sessionUsageGroups.append(observerId, group); + + user.addUsageGroup(group); + noteActiveLocked(user, group, getUptimeMillis()); } } /** - * Called when an app has moved to the foreground. - * @param packageName The app that is foregrounded - * @param className The className of the activity - * @param userId The user + * Remove a registered observer by observerId and calling uid. + * + * @param requestingUid The calling uid + * @param observerId The unique observer id for this user + * @param userId The user id of the observer */ - public void moveToForeground(String packageName, String className, int userId) { + public void removeUsageSessionObserver(int requestingUid, int observerId, + @UserIdInt int userId) { synchronized (mLock) { - UserData user = getOrCreateUserDataLocked(userId); - if (DEBUG) Slog.d(TAG, "Setting mCurrentForegroundedPackage to " + packageName); - // Note the current foreground package - user.currentForegroundedPackage = packageName; - user.currentForegroundedTime = getUptimeMillis(); - - // Check if any of the groups need to watch for this package - maybeWatchForPackageLocked(user, packageName, user.currentForegroundedTime); + ObserverAppData observerApp = getOrCreateObserverAppDataLocked(requestingUid); + observerApp.sessionUsageGroups.get(observerId).remove(); } } /** - * Called when an app is sent to the background. + * Called when an entity becomes active. * - * @param packageName - * @param className - * @param userId + * @param name The entity that became active + * @param userId The user */ - public void moveToBackground(String packageName, String className, int userId) { + public void noteUsageStart(String name, int userId) throws IllegalArgumentException { synchronized (mLock) { UserData user = getOrCreateUserDataLocked(userId); - if (!TextUtils.equals(user.currentForegroundedPackage, packageName)) { - Slog.w(TAG, "Eh? Last foregrounded package = " + user.currentForegroundedPackage - + " and now backgrounded = " + packageName); - return; + if (DEBUG) Slog.d(TAG, "Usage entity " + name + " became active"); + if (user.currentlyActive.contains(name)) { + throw new IllegalArgumentException( + "Unable to start usage for " + name + ", already in use"); } - final long stopTime = getUptimeMillis(); - - // Add up the usage time to all groups that contain the package - ArrayList<TimeLimitGroup> groups = user.packageMap.get(packageName); - if (groups != null) { - final int size = groups.size(); - for (int i = 0; i < size; i++) { - final TimeLimitGroup group = groups.get(i); - // Don't continue to send - if (group.timeRemaining <= 0) continue; - - final long startTime = Math.max(user.currentForegroundedTime, - group.timeRequested); - long diff = stopTime - startTime; - group.timeRemaining -= diff; - if (group.timeRemaining <= 0) { - if (DEBUG) Slog.d(TAG, "MTB informing group obs=" + group.observerId); - postInformListenerLocked(group); - } - // Reset indicators that observer was added when package was already fg - group.currentPackage = null; - group.timeCurrentPackageStarted = -1L; - mHandler.removeMessages(MyHandler.MSG_CHECK_TIMEOUT, group); - } + final long currentTime = getUptimeMillis(); + + // Add to the list of active entities + user.currentlyActive.add(name); + + ArrayList<UsageGroup> groups = user.observedMap.get(name); + if (groups == null) return; + + final int size = groups.size(); + for (int i = 0; i < size; i++) { + UsageGroup group = groups.get(i); + group.noteUsageStart(currentTime); } - user.currentForegroundedPackage = null; } } - private void postInformListenerLocked(TimeLimitGroup group) { - mHandler.sendMessage(mHandler.obtainMessage(MyHandler.MSG_INFORM_LISTENER, - group)); - } - /** - * Inform the observer and unregister it, as the limit has been reached. - * @param group the observed group + * Called when an entity becomes inactive. + * + * @param name The entity that became inactive + * @param userId The user */ - private void informListener(TimeLimitGroup group) { - if (mListener != null) { - mListener.onLimitReached(group.observerId, group.userId, group.timeLimit, - group.timeLimit - group.timeRemaining, group.callbackIntent); - } - // Unregister since the limit has been met and observer was informed. + public void noteUsageStop(String name, int userId) throws IllegalArgumentException { synchronized (mLock) { - UserData user = getOrCreateUserDataLocked(group.userId); - removeObserverLocked(user, group.requestingUid, group.observerId, false); - } - } + UserData user = getOrCreateUserDataLocked(userId); + if (DEBUG) Slog.d(TAG, "Usage entity " + name + " became inactive"); + if (!user.currentlyActive.remove(name)) { + throw new IllegalArgumentException( + "Unable to stop usage for " + name + ", not in use"); + } + final long currentTime = getUptimeMillis(); - /** Check if any of the groups care about this package and set up delayed messages */ - @GuardedBy("mLock") - private void maybeWatchForPackageLocked(UserData user, String packageName, long uptimeMillis) { - ArrayList<TimeLimitGroup> groups = user.packageMap.get(packageName); - if (groups == null) return; + // Check if any of the groups need to watch for this entity + ArrayList<UsageGroup> groups = user.observedMap.get(name); + if (groups == null) return; - final int size = groups.size(); - for (int i = 0; i < size; i++) { - TimeLimitGroup group = groups.get(i); - if (group.timeRemaining > 0) { - group.timeCurrentPackageStarted = uptimeMillis; - group.currentPackage = packageName; - if (DEBUG) { - Slog.d(TAG, "Posting timeout for " + packageName + " for " - + group.timeRemaining + "ms"); - } - postCheckTimeoutLocked(group, group.timeRemaining); + final int size = groups.size(); + for (int i = 0; i < size; i++) { + UsageGroup group = groups.get(i); + group.noteUsageStop(currentTime); } } } - private void addGroupToPackageMapLocked(UserData user, String[] packages, - TimeLimitGroup group) { - for (int i = 0; i < packages.length; i++) { - ArrayList<TimeLimitGroup> list = user.packageMap.get(packages[i]); - if (list == null) { - list = new ArrayList<>(); - user.packageMap.put(packages[i], list); - } - list.add(group); - } + @GuardedBy("mLock") + private void postInformLimitReachedListenerLocked(UsageGroup group) { + mHandler.sendMessage(mHandler.obtainMessage(MyHandler.MSG_INFORM_LIMIT_REACHED_LISTENER, + group)); } - /** - * Remove the group reference from the package to group mapping, which is 1 to many. - * @param group The group to remove from the package map. - */ - private void removeGroupFromPackageMapLocked(UserData user, TimeLimitGroup group) { - final int mapSize = user.packageMap.size(); - for (int i = 0; i < mapSize; i++) { - ArrayList<TimeLimitGroup> list = user.packageMap.valueAt(i); - list.remove(group); - } + @GuardedBy("mLock") + private void postInformSessionEndListenerLocked(SessionUsageGroup group, long timeout) { + mHandler.sendMessageDelayed(mHandler.obtainMessage(MyHandler.MSG_INFORM_SESSION_END, group), + timeout); + } + + @GuardedBy("mLock") + private void cancelInformSessionEndListener(SessionUsageGroup group) { + mHandler.removeMessages(MyHandler.MSG_INFORM_SESSION_END, group); } - private void postCheckTimeoutLocked(TimeLimitGroup group, long timeout) { + @GuardedBy("mLock") + private void postCheckTimeoutLocked(UsageGroup group, long timeout) { mHandler.sendMessageDelayed(mHandler.obtainMessage(MyHandler.MSG_CHECK_TIMEOUT, group), timeout); } - /** - * See if the given group has reached the timeout if the current foreground app is included - * and it exceeds the time remaining. - * @param group the group of packages to check - */ - void checkTimeout(TimeLimitGroup group) { - // For each package in the group, check if any of the currently foregrounded apps are adding - // up to hit the limit and inform the observer - synchronized (mLock) { - UserData user = getOrCreateUserDataLocked(group.userId); - // This group doesn't exist anymore, nothing to see here. - if (user.groups.get(group.observerId) != group) return; - - if (DEBUG) Slog.d(TAG, "checkTimeout timeRemaining=" + group.timeRemaining); - - // Already reached the limit, no need to report again - if (group.timeRemaining <= 0) return; - - if (DEBUG) { - Slog.d(TAG, "checkTimeout foregroundedPackage=" - + user.currentForegroundedPackage); - } - - if (inPackageList(group.packages, user.currentForegroundedPackage)) { - if (DEBUG) { - Slog.d(TAG, "checkTimeout package in foreground=" - + user.currentForegroundedPackage); - } - if (group.timeCurrentPackageStarted < 0) { - Slog.w(TAG, "startTime was not set correctly for " + group); - } - final long timeInForeground = getUptimeMillis() - group.timeCurrentPackageStarted; - if (group.timeRemaining <= timeInForeground) { - if (DEBUG) Slog.d(TAG, "checkTimeout : Time limit reached"); - // Hit the limit, set timeRemaining to zero to avoid checking again - group.timeRemaining -= timeInForeground; - postInformListenerLocked(group); - // Reset - group.timeCurrentPackageStarted = -1L; - group.currentPackage = null; - } else { - if (DEBUG) Slog.d(TAG, "checkTimeout : Some more time remaining"); - postCheckTimeoutLocked(group, group.timeRemaining - timeInForeground); - } - } - } + @GuardedBy("mLock") + private void cancelCheckTimeoutLocked(UsageGroup group) { + mHandler.removeMessages(MyHandler.MSG_CHECK_TIMEOUT, group); } void dump(PrintWriter pw) { synchronized (mLock) { pw.println("\n App Time Limits"); - int nUsers = mUsers.size(); + final int nUsers = mUsers.size(); for (int i = 0; i < nUsers; i++) { - UserData user = mUsers.valueAt(i); - pw.print(" User "); pw.println(user.userId); - int nGroups = user.groups.size(); - for (int j = 0; j < nGroups; j++) { - TimeLimitGroup group = user.groups.valueAt(j); - pw.print(" Group id="); pw.print(group.observerId); - pw.print(" timeLimit="); pw.print(group.timeLimit); - pw.print(" remaining="); pw.print(group.timeRemaining); - pw.print(" currentPackage="); pw.print(group.currentPackage); - pw.print(" timeCurrentPkgStarted="); pw.print(group.timeCurrentPackageStarted); - pw.print(" packages="); pw.println(Arrays.toString(group.packages)); - } - pw.println(); - pw.print(" currentForegroundedPackage="); - pw.println(user.currentForegroundedPackage); + pw.print(" User "); + mUsers.valueAt(i).dump(pw); + } + pw.println(); + final int nObserverApps = mObserverApps.size(); + for (int i = 0; i < nObserverApps; i++) { + pw.print(" Observer App "); + mObserverApps.valueAt(i).dump(pw); } } } diff --git a/services/usage/java/com/android/server/usage/StorageStatsService.java b/services/usage/java/com/android/server/usage/StorageStatsService.java index d12eda75ff32..faf6ee23e208 100644 --- a/services/usage/java/com/android/server/usage/StorageStatsService.java +++ b/services/usage/java/com/android/server/usage/StorageStatsService.java @@ -33,6 +33,7 @@ import android.content.pm.PackageStats; import android.content.pm.UserInfo; import android.net.Uri; import android.os.Binder; +import android.os.Build; import android.os.Environment; import android.os.FileUtils; import android.os.Handler; @@ -165,7 +166,8 @@ public class StorageStatsService extends IStorageStatsManager.Stub { @Override public boolean isReservedSupported(String volumeUuid, String callingPackage) { if (volumeUuid == StorageManager.UUID_PRIVATE_INTERNAL) { - return SystemProperties.getBoolean(StorageManager.PROP_HAS_RESERVED, false); + return SystemProperties.getBoolean(StorageManager.PROP_HAS_RESERVED, false) + || Build.IS_CONTAINER; } else { return false; } diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java index dd1ddfaf7342..262125212c14 100644 --- a/services/usage/java/com/android/server/usage/UsageStatsService.java +++ b/services/usage/java/com/android/server/usage/UsageStatsService.java @@ -165,16 +165,36 @@ public class UsageStatsService extends SystemService implements mAppStandby = new AppStandbyController(getContext(), BackgroundThread.get().getLooper()); mAppTimeLimit = new AppTimeLimitController( - (observerId, userId, timeLimit, timeElapsed, callbackIntent) -> { - Intent intent = new Intent(); - intent.putExtra(UsageStatsManager.EXTRA_OBSERVER_ID, observerId); - intent.putExtra(UsageStatsManager.EXTRA_TIME_LIMIT, timeLimit); - intent.putExtra(UsageStatsManager.EXTRA_TIME_USED, timeElapsed); - try { - callbackIntent.send(getContext(), 0, intent); - } catch (PendingIntent.CanceledException e) { - Slog.w(TAG, "Couldn't deliver callback: " - + callbackIntent); + new AppTimeLimitController.TimeLimitCallbackListener() { + @Override + public void onLimitReached(int observerId, int userId, long timeLimit, + long timeElapsed, PendingIntent callbackIntent) { + if (callbackIntent == null) return; + Intent intent = new Intent(); + intent.putExtra(UsageStatsManager.EXTRA_OBSERVER_ID, observerId); + intent.putExtra(UsageStatsManager.EXTRA_TIME_LIMIT, timeLimit); + intent.putExtra(UsageStatsManager.EXTRA_TIME_USED, timeElapsed); + try { + callbackIntent.send(getContext(), 0, intent); + } catch (PendingIntent.CanceledException e) { + Slog.w(TAG, "Couldn't deliver callback: " + + callbackIntent); + } + } + + @Override + public void onSessionEnd(int observerId, int userId, long timeElapsed, + PendingIntent callbackIntent) { + if (callbackIntent == null) return; + Intent intent = new Intent(); + intent.putExtra(UsageStatsManager.EXTRA_OBSERVER_ID, observerId); + intent.putExtra(UsageStatsManager.EXTRA_TIME_USED, timeElapsed); + try { + callbackIntent.send(getContext(), 0, intent); + } catch (PendingIntent.CanceledException e) { + Slog.w(TAG, "Couldn't deliver callback: " + + callbackIntent); + } } }, mHandler.getLooper()); @@ -412,12 +432,18 @@ public class UsageStatsService extends SystemService implements mAppStandby.reportEvent(event, elapsedRealtime, userId); switch (event.mEventType) { case Event.MOVE_TO_FOREGROUND: - mAppTimeLimit.moveToForeground(event.getPackageName(), event.getClassName(), - userId); + try { + mAppTimeLimit.noteUsageStart(event.getPackageName(), userId); + } catch (IllegalArgumentException iae) { + Slog.e(TAG, "Failed to note usage start", iae); + } break; case Event.MOVE_TO_BACKGROUND: - mAppTimeLimit.moveToBackground(event.getPackageName(), event.getClassName(), - userId); + try { + mAppTimeLimit.noteUsageStop(event.getPackageName(), userId); + } catch (IllegalArgumentException iae) { + Slog.e(TAG, "Failed to note usage stop", iae); + } break; } } @@ -1151,16 +1177,70 @@ public class UsageStatsService extends SystemService implements Binder.restoreCallingIdentity(token); } } + + @Override + public void registerUsageSessionObserver(int sessionObserverId, String[] observed, + long timeLimitMs, long sessionThresholdTimeMs, + PendingIntent limitReachedCallbackIntent, PendingIntent sessionEndCallbackIntent, + String callingPackage) { + if (!hasObserverPermission(callingPackage)) { + throw new SecurityException("Caller doesn't have OBSERVE_APP_USAGE permission"); + } + + if (observed == null || observed.length == 0) { + throw new IllegalArgumentException("Must specify at least one observed entity"); + } + if (limitReachedCallbackIntent == null) { + throw new NullPointerException("limitReachedCallbackIntent can't be null"); + } + final int callingUid = Binder.getCallingUid(); + final int userId = UserHandle.getUserId(callingUid); + final long token = Binder.clearCallingIdentity(); + try { + UsageStatsService.this.registerUsageSessionObserver(callingUid, sessionObserverId, + observed, timeLimitMs, sessionThresholdTimeMs, limitReachedCallbackIntent, + sessionEndCallbackIntent, userId); + } finally { + Binder.restoreCallingIdentity(token); + } + } + + @Override + public void unregisterUsageSessionObserver(int sessionObserverId, String callingPackage) { + if (!hasObserverPermission(callingPackage)) { + throw new SecurityException("Caller doesn't have OBSERVE_APP_USAGE permission"); + } + + final int callingUid = Binder.getCallingUid(); + final int userId = UserHandle.getUserId(callingUid); + final long token = Binder.clearCallingIdentity(); + try { + UsageStatsService.this.unregisterUsageSessionObserver(callingUid, sessionObserverId, userId); + } finally { + Binder.restoreCallingIdentity(token); + } + } } void registerAppUsageObserver(int callingUid, int observerId, String[] packages, long timeLimitMs, PendingIntent callbackIntent, int userId) { - mAppTimeLimit.addObserver(callingUid, observerId, packages, timeLimitMs, callbackIntent, + mAppTimeLimit.addAppUsageObserver(callingUid, observerId, packages, timeLimitMs, callbackIntent, userId); } void unregisterAppUsageObserver(int callingUid, int observerId, int userId) { - mAppTimeLimit.removeObserver(callingUid, observerId, userId); + mAppTimeLimit.removeAppUsageObserver(callingUid, observerId, userId); + } + + void registerUsageSessionObserver(int callingUid, int observerId, String[] observed, + long timeLimitMs, long sessionThresholdTime, PendingIntent limitReachedCallbackIntent, + PendingIntent sessionEndCallbackIntent, int userId) { + mAppTimeLimit.addUsageSessionObserver(callingUid, observerId, observed, timeLimitMs, + sessionThresholdTime, limitReachedCallbackIntent, sessionEndCallbackIntent, userId); + } + + void unregisterUsageSessionObserver(int callingUid, int sessionObserverId, int userId) { + mAppTimeLimit.removeUsageSessionObserver(callingUid, sessionObserverId, userId); } /** diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java index 66918eb7c416..5b886639079e 100644 --- a/telephony/java/android/telephony/CarrierConfigManager.java +++ b/telephony/java/android/telephony/CarrierConfigManager.java @@ -2231,6 +2231,20 @@ public class CarrierConfigManager { public static final String KEY_CALL_WAITING_OVER_UT_WARNING_BOOL = "call_waiting_over_ut_warning_bool"; + /** + * Flag indicating whether to support "Network default" option in Caller ID settings for Calling + * Line Identification Restriction (CLIR). + */ + public static final String KEY_SUPPORT_CLIR_NETWORK_DEFAULT_BOOL = + "support_clir_network_default_bool"; + + /** + * Determines whether the carrier want to support emergency dialer shortcut. + * @hide + */ + public static final String KEY_SUPPORT_EMERGENCY_DIALER_SHORTCUT_BOOL = + "support_emergency_dialer_shortcut_bool"; + /** The default value for every variable. */ private final static PersistableBundle sDefaults; @@ -2579,6 +2593,8 @@ public class CarrierConfigManager { sDefaults.putBoolean(KEY_CALL_BARRING_OVER_UT_WARNING_BOOL, false); sDefaults.putBoolean(KEY_CALLER_ID_OVER_UT_WARNING_BOOL, false); sDefaults.putBoolean(KEY_CALL_WAITING_OVER_UT_WARNING_BOOL, false); + sDefaults.putBoolean(KEY_SUPPORT_CLIR_NETWORK_DEFAULT_BOOL, true); + sDefaults.putBoolean(KEY_SUPPORT_EMERGENCY_DIALER_SHORTCUT_BOOL, true); } /** diff --git a/telephony/java/android/telephony/CellIdentityCdma.java b/telephony/java/android/telephony/CellIdentityCdma.java index 9218bdc31fa8..598f56769ca3 100644 --- a/telephony/java/android/telephony/CellIdentityCdma.java +++ b/telephony/java/android/telephony/CellIdentityCdma.java @@ -16,7 +16,6 @@ package android.telephony; -import android.annotation.Nullable; import android.annotation.UnsupportedAppUsage; import android.os.Parcel; import android.telephony.cdma.CdmaCellLocation; @@ -56,11 +55,11 @@ public final class CellIdentityCdma extends CellIdentity { */ public CellIdentityCdma() { super(TAG, CellInfo.TYPE_CDMA, null, null, null, null); - mNetworkId = Integer.MAX_VALUE; - mSystemId = Integer.MAX_VALUE; - mBasestationId = Integer.MAX_VALUE; - mLongitude = Integer.MAX_VALUE; - mLatitude = Integer.MAX_VALUE; + mNetworkId = CellInfo.UNAVAILABLE; + mSystemId = CellInfo.UNAVAILABLE; + mBasestationId = CellInfo.UNAVAILABLE; + mLongitude = CellInfo.UNAVAILABLE; + mLatitude = CellInfo.UNAVAILABLE; } /** @@ -104,7 +103,7 @@ public final class CellIdentityCdma extends CellIdentity { mLongitude = lon; mLatitude = lat; } else { - mLongitude = mLatitude = Integer.MAX_VALUE; + mLongitude = mLatitude = CellInfo.UNAVAILABLE; } } @@ -130,21 +129,24 @@ public final class CellIdentityCdma extends CellIdentity { } /** - * @return Network Id 0..65535, Integer.MAX_VALUE if unknown + * @return Network Id 0..65535, {@link android.telephony.CellInfo#UNAVAILABLE UNAVAILABLE} + * if unavailable. */ public int getNetworkId() { return mNetworkId; } /** - * @return System Id 0..32767, Integer.MAX_VALUE if unknown + * @return System Id 0..32767, {@link android.telephony.CellInfo#UNAVAILABLE UNAVAILABLE} + * if unavailable. */ public int getSystemId() { return mSystemId; } /** - * @return Base Station Id 0..65535, Integer.MAX_VALUE if unknown + * @return Base Station Id 0..65535, {@link android.telephony.CellInfo#UNAVAILABLE UNAVAILABLE} + * if unavailable. */ public int getBasestationId() { return mBasestationId; @@ -155,7 +157,7 @@ public final class CellIdentityCdma extends CellIdentity { * specified in 3GPP2 C.S0005-A v6.0. It is represented in units * of 0.25 seconds and ranges from -2592000 to 2592000, both * values inclusive (corresponding to a range of -180 - * to +180 degrees). Integer.MAX_VALUE if unknown. + * to +180 degrees). {@link android.telephony.CellInfo#UNAVAILABLE UNAVAILABLE} if unavailable. */ public int getLongitude() { return mLongitude; @@ -166,7 +168,7 @@ public final class CellIdentityCdma extends CellIdentity { * specified in 3GPP2 C.S0005-A v6.0. It is represented in units * of 0.25 seconds and ranges from -1296000 to 1296000, both * values inclusive (corresponding to a range of -90 - * to +90 degrees). Integer.MAX_VALUE if unknown. + * to +90 degrees). {@link android.telephony.CellInfo#UNAVAILABLE UNAVAILABLE} if unavailable. */ public int getLatitude() { return mLatitude; @@ -182,10 +184,10 @@ public final class CellIdentityCdma extends CellIdentity { @Override public CdmaCellLocation asCellLocation() { CdmaCellLocation cl = new CdmaCellLocation(); - int bsid = mBasestationId != Integer.MAX_VALUE ? mBasestationId : -1; - int sid = mSystemId != Integer.MAX_VALUE ? mSystemId : -1; - int nid = mNetworkId != Integer.MAX_VALUE ? mNetworkId : -1; - // lat and long already use Integer.MAX_VALUE for invalid/unknown + int bsid = mBasestationId != CellInfo.UNAVAILABLE ? mBasestationId : -1; + int sid = mSystemId != CellInfo.UNAVAILABLE ? mSystemId : -1; + int nid = mNetworkId != CellInfo.UNAVAILABLE ? mNetworkId : -1; + // lat and long already use CellInfo.UNAVAILABLE for invalid/unknown cl.setCellLocationData(bsid, mLatitude, mLongitude, sid, nid); return cl; } diff --git a/telephony/java/android/telephony/CellIdentityGsm.java b/telephony/java/android/telephony/CellIdentityGsm.java index cb9dbf369d7e..04c28e5211c8 100644 --- a/telephony/java/android/telephony/CellIdentityGsm.java +++ b/telephony/java/android/telephony/CellIdentityGsm.java @@ -16,7 +16,6 @@ package android.telephony; -import android.annotation.Nullable; import android.annotation.UnsupportedAppUsage; import android.os.Parcel; import android.telephony.gsm.GsmCellLocation; @@ -48,10 +47,10 @@ public final class CellIdentityGsm extends CellIdentity { @UnsupportedAppUsage public CellIdentityGsm() { super(TAG, CellInfo.TYPE_GSM, null, null, null, null); - mLac = Integer.MAX_VALUE; - mCid = Integer.MAX_VALUE; - mArfcn = Integer.MAX_VALUE; - mBsic = Integer.MAX_VALUE; + mLac = CellInfo.UNAVAILABLE; + mCid = CellInfo.UNAVAILABLE; + mArfcn = CellInfo.UNAVAILABLE; + mBsic = CellInfo.UNAVAILABLE; } /** * public constructor @@ -63,7 +62,7 @@ public final class CellIdentityGsm extends CellIdentity { * @hide */ public CellIdentityGsm(int mcc, int mnc, int lac, int cid) { - this(lac, cid, Integer.MAX_VALUE, Integer.MAX_VALUE, + this(lac, cid, CellInfo.UNAVAILABLE, CellInfo.UNAVAILABLE, String.valueOf(mcc), String.valueOf(mnc), null, null); } @@ -103,7 +102,7 @@ public final class CellIdentityGsm extends CellIdentity { mArfcn = arfcn; // In RIL BSIC is a UINT8, so 0xFF is the 'INVALID' designator // for inbound parcels - mBsic = (bsic == 0xFF) ? Integer.MAX_VALUE : bsic; + mBsic = (bsic == 0xFF) ? CellInfo.UNAVAILABLE : bsic; } private CellIdentityGsm(CellIdentityGsm cid) { @@ -116,69 +115,73 @@ public final class CellIdentityGsm extends CellIdentity { } /** - * @return 3-digit Mobile Country Code, 0..999, Integer.MAX_VALUE if unknown + * @return 3-digit Mobile Country Code, 0..999, + * {@link android.telephony.CellInfo#UNAVAILABLE UNAVAILABLE} if unavailable. * @deprecated Use {@link #getMccString} instead. */ @Deprecated public int getMcc() { - return (mMccStr != null) ? Integer.valueOf(mMccStr) : Integer.MAX_VALUE; + return (mMccStr != null) ? Integer.valueOf(mMccStr) : CellInfo.UNAVAILABLE; } /** - * @return 2 or 3-digit Mobile Network Code, 0..999, Integer.MAX_VALUE if unknown + * @return 2 or 3-digit Mobile Network Code, 0..999, + * {@link android.telephony.CellInfo#UNAVAILABLE UNAVAILABLE} if unavailable. * @deprecated Use {@link #getMncString} instead. */ @Deprecated public int getMnc() { - return (mMncStr != null) ? Integer.valueOf(mMncStr) : Integer.MAX_VALUE; + return (mMncStr != null) ? Integer.valueOf(mMncStr) : CellInfo.UNAVAILABLE; } /** - * @return 16-bit Location Area Code, 0..65535, Integer.MAX_VALUE if unknown + * @return 16-bit Location Area Code, 0..65535, + * {@link android.telephony.CellInfo#UNAVAILABLE UNAVAILABLE} if unavailable. */ public int getLac() { return mLac; } /** - * @return CID - * Either 16-bit GSM Cell Identity described - * in TS 27.007, 0..65535, Integer.MAX_VALUE if unknown + * @return 16-bit GSM Cell Identity described in TS 27.007, 0..65535, + * {@link android.telephony.CellInfo#UNAVAILABLE UNAVAILABLE} if unavailable. */ public int getCid() { return mCid; } /** - * @return 16-bit GSM Absolute RF Channel Number, Integer.MAX_VALUE if unknown + * @return 16-bit GSM Absolute RF Channel Number, + * {@link android.telephony.CellInfo#UNAVAILABLE UNAVAILABLE} if unavailable. */ public int getArfcn() { return mArfcn; } /** - * @return 6-bit Base Station Identity Code, Integer.MAX_VALUE if unknown + * @return 6-bit Base Station Identity Code, + * {@link android.telephony.CellInfo#UNAVAILABLE UNAVAILABLE} if unavailable. */ public int getBsic() { return mBsic; } /** - * @return a 5 or 6 character string (MCC+MNC), null if any field is unknown + * @return a 5 or 6 character string (MCC+MNC), null if any field is unknown. */ public String getMobileNetworkOperator() { return (mMccStr == null || mMncStr == null) ? null : mMccStr + mMncStr; } /** - * @return Mobile Country Code in string format, null if unknown + * @return Mobile Country Code in string format, null if unavailable. */ public String getMccString() { return mMccStr; } /** - * @return Mobile Network Code in string format, null if unknown + * @return Mobile Network Code in string format, null if unavailable. */ public String getMncString() { return mMncStr; @@ -192,19 +195,19 @@ public final class CellIdentityGsm extends CellIdentity { /** * @deprecated Primary Scrambling Code is not applicable to GSM. - * @return Integer.MAX_VALUE, undefined for GSM + * @return {@link android.telephony.CellInfo#UNAVAILABLE UNAVAILABLE} - undefined for GSM */ @Deprecated public int getPsc() { - return Integer.MAX_VALUE; + return CellInfo.UNAVAILABLE; } /** @hide */ @Override public GsmCellLocation asCellLocation() { GsmCellLocation cl = new GsmCellLocation(); - int lac = mLac != Integer.MAX_VALUE ? mLac : -1; - int cid = mCid != Integer.MAX_VALUE ? mCid : -1; + int lac = mLac != CellInfo.UNAVAILABLE ? mLac : -1; + int cid = mCid != CellInfo.UNAVAILABLE ? mCid : -1; cl.setLacAndCid(lac, cid); cl.setPsc(-1); return cl; diff --git a/telephony/java/android/telephony/CellIdentityLte.java b/telephony/java/android/telephony/CellIdentityLte.java index b44e891fa870..04b6a6ca7fea 100644 --- a/telephony/java/android/telephony/CellIdentityLte.java +++ b/telephony/java/android/telephony/CellIdentityLte.java @@ -16,7 +16,6 @@ package android.telephony; -import android.annotation.Nullable; import android.annotation.UnsupportedAppUsage; import android.os.Parcel; import android.telephony.gsm.GsmCellLocation; @@ -49,11 +48,11 @@ public final class CellIdentityLte extends CellIdentity { @UnsupportedAppUsage public CellIdentityLte() { super(TAG, CellInfo.TYPE_LTE, null, null, null, null); - mCi = Integer.MAX_VALUE; - mPci = Integer.MAX_VALUE; - mTac = Integer.MAX_VALUE; - mEarfcn = Integer.MAX_VALUE; - mBandwidth = Integer.MAX_VALUE; + mCi = CellInfo.UNAVAILABLE; + mPci = CellInfo.UNAVAILABLE; + mTac = CellInfo.UNAVAILABLE; + mEarfcn = CellInfo.UNAVAILABLE; + mBandwidth = CellInfo.UNAVAILABLE; } /** @@ -68,7 +67,7 @@ public final class CellIdentityLte extends CellIdentity { */ @UnsupportedAppUsage public CellIdentityLte(int mcc, int mnc, int ci, int pci, int tac) { - this(ci, pci, tac, Integer.MAX_VALUE, Integer.MAX_VALUE, String.valueOf(mcc), + this(ci, pci, tac, CellInfo.UNAVAILABLE, CellInfo.UNAVAILABLE, String.valueOf(mcc), String.valueOf(mnc), null, null); } @@ -84,7 +83,7 @@ public final class CellIdentityLte extends CellIdentity { * @hide */ public CellIdentityLte(int mcc, int mnc, int ci, int pci, int tac, int earfcn) { - this(ci, pci, tac, earfcn, Integer.MAX_VALUE, String.valueOf(mcc), String.valueOf(mnc), + this(ci, pci, tac, earfcn, CellInfo.UNAVAILABLE, String.valueOf(mcc), String.valueOf(mnc), null, null); } @@ -122,74 +121,81 @@ public final class CellIdentityLte extends CellIdentity { } /** - * @return 3-digit Mobile Country Code, 0..999, Integer.MAX_VALUE if unknown + * @return 3-digit Mobile Country Code, 0..999, + * {@link android.telephony.CellInfo#UNAVAILABLE UNAVAILABLE} if unavailable. * @deprecated Use {@link #getMccString} instead. */ @Deprecated public int getMcc() { - return (mMccStr != null) ? Integer.valueOf(mMccStr) : Integer.MAX_VALUE; + return (mMccStr != null) ? Integer.valueOf(mMccStr) : CellInfo.UNAVAILABLE; } /** - * @return 2 or 3-digit Mobile Network Code, 0..999, Integer.MAX_VALUE if unknown + * @return 2 or 3-digit Mobile Network Code, 0..999, + * {@link android.telephony.CellInfo#UNAVAILABLE UNAVAILABLE} if unavailable. * @deprecated Use {@link #getMncString} instead. */ @Deprecated public int getMnc() { - return (mMncStr != null) ? Integer.valueOf(mMncStr) : Integer.MAX_VALUE; + return (mMncStr != null) ? Integer.valueOf(mMncStr) : CellInfo.UNAVAILABLE; } /** - * @return 28-bit Cell Identity, Integer.MAX_VALUE if unknown + * @return 28-bit Cell Identity, + * {@link android.telephony.CellInfo#UNAVAILABLE UNAVAILABLE} if unavailable. */ public int getCi() { return mCi; } /** - * @return Physical Cell Id 0..503, Integer.MAX_VALUE if unknown + * @return Physical Cell Id 0..503, + * {@link android.telephony.CellInfo#UNAVAILABLE UNAVAILABLE} if unavailable. */ public int getPci() { return mPci; } /** - * @return 16-bit Tracking Area Code, Integer.MAX_VALUE if unknown + * @return 16-bit Tracking Area Code, + * {@link android.telephony.CellInfo#UNAVAILABLE UNAVAILABLE} if unavailable. */ public int getTac() { return mTac; } /** - * @return 18-bit Absolute RF Channel Number, Integer.MAX_VALUE if unknown + * @return 18-bit Absolute RF Channel Number, + * {@link android.telephony.CellInfo#UNAVAILABLE UNAVAILABLE} if unavailable. */ public int getEarfcn() { return mEarfcn; } /** - * @return Cell bandwidth in kHz, Integer.MAX_VALUE if unknown + * @return Cell bandwidth in kHz, + * {@link android.telephony.CellInfo#UNAVAILABLE UNAVAILABLE} if unavailable. */ public int getBandwidth() { return mBandwidth; } /** - * @return Mobile Country Code in string format, null if unknown + * @return Mobile Country Code in string format, null if unavailable. */ public String getMccString() { return mMccStr; } /** - * @return Mobile Network Code in string format, null if unknown + * @return Mobile Network Code in string format, null if unavailable. */ public String getMncString() { return mMncStr; } /** - * @return a 5 or 6 character string (MCC+MNC), null if any field is unknown + * @return a 5 or 6 character string (MCC+MNC), null if any field is unknown. */ public String getMobileNetworkOperator() { return (mMccStr == null || mMncStr == null) ? null : mMccStr + mMncStr; @@ -216,8 +222,8 @@ public final class CellIdentityLte extends CellIdentity { @Override public GsmCellLocation asCellLocation() { GsmCellLocation cl = new GsmCellLocation(); - int tac = mTac != Integer.MAX_VALUE ? mTac : -1; - int cid = mCi != Integer.MAX_VALUE ? mCi : -1; + int tac = mTac != CellInfo.UNAVAILABLE ? mTac : -1; + int cid = mCi != CellInfo.UNAVAILABLE ? mCi : -1; cl.setLacAndCid(tac, cid); cl.setPsc(0); return cl; diff --git a/telephony/java/android/telephony/CellIdentityTdscdma.java b/telephony/java/android/telephony/CellIdentityTdscdma.java index bc83de190347..8b1c1b9f024c 100644 --- a/telephony/java/android/telephony/CellIdentityTdscdma.java +++ b/telephony/java/android/telephony/CellIdentityTdscdma.java @@ -28,11 +28,12 @@ public final class CellIdentityTdscdma extends CellIdentity { private static final String TAG = CellIdentityTdscdma.class.getSimpleName(); private static final boolean DBG = false; - // 16-bit Location Area Code, 0..65535, INT_MAX if unknown. + // 16-bit Location Area Code, 0..65535, CellInfo.UNAVAILABLE if unknown. private final int mLac; - // 28-bit UMTS Cell Identity described in TS 25.331, 0..268435455, INT_MAX if unknown. + // 28-bit UMTS Cell Identity described in TS 25.331, 0..268435455, CellInfo.UNAVAILABLE + // if unknown. private final int mCid; - // 8-bit Cell Parameters ID described in TS 25.331, 0..127, INT_MAX if unknown. + // 8-bit Cell Parameters ID described in TS 25.331, 0..127, CellInfo.UNAVAILABLE if unknown. private final int mCpid; // 16-bit UMTS Absolute RF Channel Number described in TS 25.101 sec. 5.4.3 private final int mUarfcn; @@ -42,18 +43,20 @@ public final class CellIdentityTdscdma extends CellIdentity { */ public CellIdentityTdscdma() { super(TAG, CellInfo.TYPE_TDSCDMA, null, null, null, null); - mLac = Integer.MAX_VALUE; - mCid = Integer.MAX_VALUE; - mCpid = Integer.MAX_VALUE; - mUarfcn = Integer.MAX_VALUE; + mLac = CellInfo.UNAVAILABLE; + mCid = CellInfo.UNAVAILABLE; + mCpid = CellInfo.UNAVAILABLE; + mUarfcn = CellInfo.UNAVAILABLE; } /** * @param mcc 3-digit Mobile Country Code, 0..999 * @param mnc 2 or 3-digit Mobile Network Code, 0..999 - * @param lac 16-bit Location Area Code, 0..65535, INT_MAX if unknown - * @param cid 28-bit UMTS Cell Identity described in TS 25.331, 0..268435455, INT_MAX if unknown - * @param cpid 8-bit Cell Parameters ID described in TS 25.331, 0..127, INT_MAX if unknown + * @param lac 16-bit Location Area Code, 0..65535, CellInfo.UNAVAILABLE if unknown + * @param cid 28-bit UMTS Cell Identity described in TS 25.331, 0..268435455, CellInfo. + * UNAVAILABLE if unknown + * @param cpid 8-bit Cell Parameters ID described in TS 25.331, 0..127, CellInfo.UNAVAILABLE + * if unknown * @param uarfcn 16-bit UMTS Absolute RF Channel Number described in TS 25.101 sec. 5.4.3 * * @hide @@ -65,9 +68,11 @@ public final class CellIdentityTdscdma extends CellIdentity { /** * @param mcc 3-digit Mobile Country Code in string format * @param mnc 2 or 3-digit Mobile Network Code in string format - * @param lac 16-bit Location Area Code, 0..65535, INT_MAX if unknown - * @param cid 28-bit UMTS Cell Identity described in TS 25.331, 0..268435455, INT_MAX if unknown - * @param cpid 8-bit Cell Parameters ID described in TS 25.331, 0..127, INT_MAX if unknown + * @param lac 16-bit Location Area Code, 0..65535, CellInfo.UNAVAILABLE if unknown + * @param cid 28-bit UMTS Cell Identity described in TS 25.331, 0..268435455, + * CellInfo.UNAVAILABLE if unknown + * @param cpid 8-bit Cell Parameters ID described in TS 25.331, 0..127, + * CellInfo.UNAVAILABLE if unknown * @param uarfcn 16-bit UMTS Absolute RF Channel Number described in TS 25.101 sec. 5.4.3 * @param alphal long alpha Operator Name String or Enhanced Operator Name String * @param alphas short alpha Operator Name String or Enhanced Operator Name String @@ -116,21 +121,24 @@ public final class CellIdentityTdscdma extends CellIdentity { } /** - * @return 16-bit Location Area Code, 0..65535, INT_MAX if unknown + * @return 16-bit Location Area Code, 0..65535, + * {@link android.telephony.CellInfo#UNAVAILABLE UNAVAILABLE} if unavailable. */ public int getLac() { return mLac; } /** - * @return 28-bit UMTS Cell Identity described in TS 25.331, 0..268435455, INT_MAX if unknown + * @return 28-bit UMTS Cell Identity described in TS 25.331, 0..268435455, + * {@link android.telephony.CellInfo#UNAVAILABLE UNAVAILABLE} if unavailable. */ public int getCid() { return mCid; } /** - * @return 8-bit Cell Parameters ID described in TS 25.331, 0..127, INT_MAX if unknown + * @return 8-bit Cell Parameters ID described in TS 25.331, 0..127, + * {@link android.telephony.CellInfo#UNAVAILABLE UNAVAILABLE} if unavailable. */ public int getCpid() { return mCpid; @@ -146,8 +154,8 @@ public final class CellIdentityTdscdma extends CellIdentity { @Override public GsmCellLocation asCellLocation() { GsmCellLocation cl = new GsmCellLocation(); - int lac = mLac != Integer.MAX_VALUE ? mLac : -1; - int cid = mCid != Integer.MAX_VALUE ? mCid : -1; + int lac = mLac != CellInfo.UNAVAILABLE ? mLac : -1; + int cid = mCid != CellInfo.UNAVAILABLE ? mCid : -1; cl.setLacAndCid(lac, cid); cl.setPsc(-1); // There is no PSC for TD-SCDMA; not using this for CPI to stem shenanigans return cl; diff --git a/telephony/java/android/telephony/CellIdentityWcdma.java b/telephony/java/android/telephony/CellIdentityWcdma.java index 727d9908b9b1..3416ffe0b8f4 100644 --- a/telephony/java/android/telephony/CellIdentityWcdma.java +++ b/telephony/java/android/telephony/CellIdentityWcdma.java @@ -16,7 +16,6 @@ package android.telephony; -import android.annotation.Nullable; import android.annotation.UnsupportedAppUsage; import android.os.Parcel; import android.telephony.gsm.GsmCellLocation; @@ -46,10 +45,10 @@ public final class CellIdentityWcdma extends CellIdentity { */ public CellIdentityWcdma() { super(TAG, CellInfo.TYPE_WCDMA, null, null, null, null); - mLac = Integer.MAX_VALUE; - mCid = Integer.MAX_VALUE; - mPsc = Integer.MAX_VALUE; - mUarfcn = Integer.MAX_VALUE; + mLac = CellInfo.UNAVAILABLE; + mCid = CellInfo.UNAVAILABLE; + mPsc = CellInfo.UNAVAILABLE; + mUarfcn = CellInfo.UNAVAILABLE; } /** * public constructor @@ -62,7 +61,7 @@ public final class CellIdentityWcdma extends CellIdentity { * @hide */ public CellIdentityWcdma (int mcc, int mnc, int lac, int cid, int psc) { - this(lac, cid, psc, Integer.MAX_VALUE, String.valueOf(mcc), String.valueOf(mnc), + this(lac, cid, psc, CellInfo.UNAVAILABLE, String.valueOf(mcc), String.valueOf(mnc), null, null); } @@ -113,25 +112,28 @@ public final class CellIdentityWcdma extends CellIdentity { } /** - * @return 3-digit Mobile Country Code, 0..999, Integer.MAX_VALUE if unknown + * @return 3-digit Mobile Country Code, 0..999, + * {@link android.telephony.CellInfo#UNAVAILABLE UNAVAILABLE} if unavailable. * @deprecated Use {@link #getMccString} instead. */ @Deprecated public int getMcc() { - return (mMccStr != null) ? Integer.valueOf(mMccStr) : Integer.MAX_VALUE; + return (mMccStr != null) ? Integer.valueOf(mMccStr) : CellInfo.UNAVAILABLE; } /** - * @return 2 or 3-digit Mobile Network Code, 0..999, Integer.MAX_VALUE if unknown + * @return 2 or 3-digit Mobile Network Code, 0..999, + * {@link android.telephony.CellInfo#UNAVAILABLE UNAVAILABLE} if unavailable. * @deprecated Use {@link #getMncString} instead. */ @Deprecated public int getMnc() { - return (mMncStr != null) ? Integer.valueOf(mMncStr) : Integer.MAX_VALUE; + return (mMncStr != null) ? Integer.valueOf(mMncStr) : CellInfo.UNAVAILABLE; } /** - * @return 16-bit Location Area Code, 0..65535, Integer.MAX_VALUE if unknown + * @return 16-bit Location Area Code, 0..65535, + * {@link android.telephony.CellInfo#UNAVAILABLE UNAVAILABLE} if unavailable. */ public int getLac() { return mLac; @@ -139,29 +141,30 @@ public final class CellIdentityWcdma extends CellIdentity { /** * @return CID - * 28-bit UMTS Cell Identity described in TS 25.331, 0..268435455, Integer.MAX_VALUE if unknown + * 28-bit UMTS Cell Identity described in TS 25.331, 0..268435455, + * {@link android.telephony.CellInfo#UNAVAILABLE UNAVAILABLE} if unavailable. */ public int getCid() { return mCid; } /** - * @return 9-bit UMTS Primary Scrambling Code described in TS 25.331, 0..511, Integer.MAX_VALUE - * if unknown + * @return 9-bit UMTS Primary Scrambling Code described in TS 25.331, 0..511, + * {@link android.telephony.CellInfo#UNAVAILABLE UNAVAILABLE} if unavailable. */ public int getPsc() { return mPsc; } /** - * @return Mobile Country Code in string version, null if unknown + * @return Mobile Country Code in string version, null if unavailable. */ public String getMccString() { return mMccStr; } /** - * @return Mobile Network Code in string version, null if unknown + * @return Mobile Network Code in string version, null if unavailable. */ public String getMncString() { return mMncStr; @@ -180,7 +183,8 @@ public final class CellIdentityWcdma extends CellIdentity { } /** - * @return 16-bit UMTS Absolute RF Channel Number, Integer.MAX_VALUE if unknown + * @return 16-bit UMTS Absolute RF Channel Number, + * {@link android.telephony.CellInfo#UNAVAILABLE UNAVAILABLE} if unavailable. */ public int getUarfcn() { return mUarfcn; @@ -196,9 +200,9 @@ public final class CellIdentityWcdma extends CellIdentity { @Override public GsmCellLocation asCellLocation() { GsmCellLocation cl = new GsmCellLocation(); - int lac = mLac != Integer.MAX_VALUE ? mLac : -1; - int cid = mCid != Integer.MAX_VALUE ? mCid : -1; - int psc = mPsc != Integer.MAX_VALUE ? mPsc : -1; + int lac = mLac != CellInfo.UNAVAILABLE ? mLac : -1; + int cid = mCid != CellInfo.UNAVAILABLE ? mCid : -1; + int psc = mPsc != CellInfo.UNAVAILABLE ? mPsc : -1; cl.setLacAndCid(lac, cid); cl.setPsc(psc); @@ -280,4 +284,4 @@ public final class CellIdentityWcdma extends CellIdentity { protected static CellIdentityWcdma createFromParcelBody(Parcel in) { return new CellIdentityWcdma(in); } -}
\ No newline at end of file +} diff --git a/telephony/java/android/telephony/CellInfo.java b/telephony/java/android/telephony/CellInfo.java index 94e4293806e6..1c63e8205454 100644 --- a/telephony/java/android/telephony/CellInfo.java +++ b/telephony/java/android/telephony/CellInfo.java @@ -33,6 +33,11 @@ import java.lang.annotation.RetentionPolicy; public abstract class CellInfo implements Parcelable { /** + * This value indicates that the integer field is unreported. + */ + public static final int UNAVAILABLE = Integer.MAX_VALUE; + + /** * Cell identity type * @hide */ diff --git a/telephony/java/android/telephony/CellSignalStrengthCdma.java b/telephony/java/android/telephony/CellSignalStrengthCdma.java index aa6b207d2f31..5123052cb78b 100644 --- a/telephony/java/android/telephony/CellSignalStrengthCdma.java +++ b/telephony/java/android/telephony/CellSignalStrengthCdma.java @@ -51,8 +51,9 @@ public final class CellSignalStrengthCdma extends CellSignalStrength implements * <p>Note that this HAL is inconsistent with UMTS-based radio techs as the value indicating * that a field is unreported is negative, rather than a large(r) positive number. * <p>Also note that to keep the public-facing methods of this class consistent with others, - * unreported values are coerced to Integer.MAX_VALUE rather than left as -1, which is - * a departure from SignalStrength, which is stuck with the values it currently reports. + * unreported values are coerced to {@link android.telephony.CellInfo#UNAVAILABLE UNAVAILABLE} + * rather than left as -1, which is a departure from SignalStrength, which is stuck with the + * values it currently reports. * * @param cdmaDbm negative of the CDMA signal strength value or -1 if invalid. * @param cdmaEcio negative of the CDMA pilot/noise ratio or -1 if invalid. @@ -65,12 +66,12 @@ public final class CellSignalStrengthCdma extends CellSignalStrength implements int evdoSnr) { // The values here were lifted from SignalStrength.validateInput() // FIXME: Combine all checking and setting logic between this and SignalStrength. - mCdmaDbm = ((cdmaDbm > 0) && (cdmaDbm < 120)) ? -cdmaDbm : Integer.MAX_VALUE; - mCdmaEcio = ((cdmaEcio > 0) && (cdmaEcio < 160)) ? -cdmaEcio : Integer.MAX_VALUE; + mCdmaDbm = ((cdmaDbm > 0) && (cdmaDbm < 120)) ? -cdmaDbm : CellInfo.UNAVAILABLE; + mCdmaEcio = ((cdmaEcio > 0) && (cdmaEcio < 160)) ? -cdmaEcio : CellInfo.UNAVAILABLE; - mEvdoDbm = ((evdoDbm > 0) && (evdoDbm < 120)) ? -evdoDbm : Integer.MAX_VALUE; - mEvdoEcio = ((evdoEcio > 0) && (evdoEcio < 160)) ? -evdoEcio : Integer.MAX_VALUE; - mEvdoSnr = ((evdoSnr > 0) && (evdoSnr <= 8)) ? evdoSnr : Integer.MAX_VALUE; + mEvdoDbm = ((evdoDbm > 0) && (evdoDbm < 120)) ? -evdoDbm : CellInfo.UNAVAILABLE; + mEvdoEcio = ((evdoEcio > 0) && (evdoEcio < 160)) ? -evdoEcio : CellInfo.UNAVAILABLE; + mEvdoSnr = ((evdoSnr > 0) && (evdoSnr <= 8)) ? evdoSnr : CellInfo.UNAVAILABLE; } /** @hide */ @@ -96,11 +97,11 @@ public final class CellSignalStrengthCdma extends CellSignalStrength implements /** @hide */ @Override public void setDefaultValues() { - mCdmaDbm = Integer.MAX_VALUE; - mCdmaEcio = Integer.MAX_VALUE; - mEvdoDbm = Integer.MAX_VALUE; - mEvdoEcio = Integer.MAX_VALUE; - mEvdoSnr = Integer.MAX_VALUE; + mCdmaDbm = CellInfo.UNAVAILABLE; + mCdmaEcio = CellInfo.UNAVAILABLE; + mEvdoDbm = CellInfo.UNAVAILABLE; + mEvdoEcio = CellInfo.UNAVAILABLE; + mEvdoSnr = CellInfo.UNAVAILABLE; } /** @@ -139,7 +140,7 @@ public final class CellSignalStrengthCdma extends CellSignalStrength implements int cdmaAsuLevel; int ecioAsuLevel; - if (cdmaDbm == Integer.MAX_VALUE) cdmaAsuLevel = 99; + if (cdmaDbm == CellInfo.UNAVAILABLE) cdmaAsuLevel = 99; else if (cdmaDbm >= -75) cdmaAsuLevel = 16; else if (cdmaDbm >= -82) cdmaAsuLevel = 8; else if (cdmaDbm >= -90) cdmaAsuLevel = 4; @@ -148,7 +149,7 @@ public final class CellSignalStrengthCdma extends CellSignalStrength implements else cdmaAsuLevel = 99; // Ec/Io are in dB*10 - if (cdmaEcio == Integer.MAX_VALUE) ecioAsuLevel = 99; + if (cdmaEcio == CellInfo.UNAVAILABLE) ecioAsuLevel = 99; else if (cdmaEcio >= -90) ecioAsuLevel = 16; else if (cdmaEcio >= -100) ecioAsuLevel = 8; else if (cdmaEcio >= -115) ecioAsuLevel = 4; @@ -170,7 +171,7 @@ public final class CellSignalStrengthCdma extends CellSignalStrength implements int levelDbm; int levelEcio; - if (cdmaDbm == Integer.MAX_VALUE) levelDbm = SIGNAL_STRENGTH_NONE_OR_UNKNOWN; + if (cdmaDbm == CellInfo.UNAVAILABLE) levelDbm = SIGNAL_STRENGTH_NONE_OR_UNKNOWN; else if (cdmaDbm >= -75) levelDbm = SIGNAL_STRENGTH_GREAT; else if (cdmaDbm >= -85) levelDbm = SIGNAL_STRENGTH_GOOD; else if (cdmaDbm >= -95) levelDbm = SIGNAL_STRENGTH_MODERATE; @@ -178,7 +179,7 @@ public final class CellSignalStrengthCdma extends CellSignalStrength implements else levelDbm = SIGNAL_STRENGTH_NONE_OR_UNKNOWN; // Ec/Io are in dB*10 - if (cdmaEcio == Integer.MAX_VALUE) levelEcio = SIGNAL_STRENGTH_NONE_OR_UNKNOWN; + if (cdmaEcio == CellInfo.UNAVAILABLE) levelEcio = SIGNAL_STRENGTH_NONE_OR_UNKNOWN; else if (cdmaEcio >= -90) levelEcio = SIGNAL_STRENGTH_GREAT; else if (cdmaEcio >= -110) levelEcio = SIGNAL_STRENGTH_GOOD; else if (cdmaEcio >= -130) levelEcio = SIGNAL_STRENGTH_MODERATE; @@ -199,14 +200,14 @@ public final class CellSignalStrengthCdma extends CellSignalStrength implements int levelEvdoDbm; int levelEvdoSnr; - if (evdoDbm == Integer.MAX_VALUE) levelEvdoDbm = SIGNAL_STRENGTH_NONE_OR_UNKNOWN; + if (evdoDbm == CellInfo.UNAVAILABLE) levelEvdoDbm = SIGNAL_STRENGTH_NONE_OR_UNKNOWN; else if (evdoDbm >= -65) levelEvdoDbm = SIGNAL_STRENGTH_GREAT; else if (evdoDbm >= -75) levelEvdoDbm = SIGNAL_STRENGTH_GOOD; else if (evdoDbm >= -90) levelEvdoDbm = SIGNAL_STRENGTH_MODERATE; else if (evdoDbm >= -105) levelEvdoDbm = SIGNAL_STRENGTH_POOR; else levelEvdoDbm = SIGNAL_STRENGTH_NONE_OR_UNKNOWN; - if (evdoSnr == Integer.MAX_VALUE) levelEvdoSnr = SIGNAL_STRENGTH_NONE_OR_UNKNOWN; + if (evdoSnr == CellInfo.UNAVAILABLE) levelEvdoSnr = SIGNAL_STRENGTH_NONE_OR_UNKNOWN; else if (evdoSnr >= 7) levelEvdoSnr = SIGNAL_STRENGTH_GREAT; else if (evdoSnr >= 5) levelEvdoSnr = SIGNAL_STRENGTH_GOOD; else if (evdoSnr >= 3) levelEvdoSnr = SIGNAL_STRENGTH_MODERATE; diff --git a/telephony/java/android/telephony/CellSignalStrengthGsm.java b/telephony/java/android/telephony/CellSignalStrengthGsm.java index 1e8d119c96c7..e906f460024a 100644 --- a/telephony/java/android/telephony/CellSignalStrengthGsm.java +++ b/telephony/java/android/telephony/CellSignalStrengthGsm.java @@ -40,7 +40,7 @@ public final class CellSignalStrengthGsm extends CellSignalStrength implements P @UnsupportedAppUsage private int mBitErrorRate; // bit error rate (0-7, 99) as defined in TS 27.007 8.5 @UnsupportedAppUsage - private int mTimingAdvance; // range from 0-219 or Integer.MAX_VALUE if unknown + private int mTimingAdvance; // range from 0-219 or CellInfo.UNAVAILABLE if unknown /** @hide */ @UnsupportedAppUsage @@ -50,7 +50,7 @@ public final class CellSignalStrengthGsm extends CellSignalStrength implements P /** @hide */ public CellSignalStrengthGsm(int ss, int ber) { - this(ss, ber, Integer.MAX_VALUE); + this(ss, ber, CellInfo.UNAVAILABLE); } /** @hide */ @@ -81,9 +81,9 @@ public final class CellSignalStrengthGsm extends CellSignalStrength implements P /** @hide */ @Override public void setDefaultValues() { - mSignalStrength = Integer.MAX_VALUE; - mBitErrorRate = Integer.MAX_VALUE; - mTimingAdvance = Integer.MAX_VALUE; + mSignalStrength = CellInfo.UNAVAILABLE; + mBitErrorRate = CellInfo.UNAVAILABLE; + mTimingAdvance = CellInfo.UNAVAILABLE; } /** @@ -112,8 +112,9 @@ public final class CellSignalStrengthGsm extends CellSignalStrength implements P /** * Get the GSM timing advance between 0..219 symbols (normally 0..63). - * Integer.MAX_VALUE is reported when there is no RR connection. - * Refer to 3GPP 45.010 Sec 5.8 + * <p>{@link android.telephony.CellInfo#UNAVAILABLE UNAVAILABLE} is reported when there is no RR + * connection. Refer to 3GPP 45.010 Sec 5.8. + * * @return the current GSM timing advance, if available. */ public int getTimingAdvance() { @@ -128,11 +129,11 @@ public final class CellSignalStrengthGsm extends CellSignalStrength implements P int dBm; int level = mSignalStrength; - int asu = (level == 99 ? Integer.MAX_VALUE : level); - if (asu != Integer.MAX_VALUE) { + int asu = (level == 99 ? CellInfo.UNAVAILABLE : level); + if (asu != CellInfo.UNAVAILABLE) { dBm = -113 + (2 * asu); } else { - dBm = Integer.MAX_VALUE; + dBm = CellInfo.UNAVAILABLE; } if (DBG) log("getDbm=" + dBm); return dBm; diff --git a/telephony/java/android/telephony/CellSignalStrengthLte.java b/telephony/java/android/telephony/CellSignalStrengthLte.java index ed7d4b2331da..d6856b397a00 100644 --- a/telephony/java/android/telephony/CellSignalStrengthLte.java +++ b/telephony/java/android/telephony/CellSignalStrengthLte.java @@ -85,12 +85,12 @@ public final class CellSignalStrengthLte extends CellSignalStrength implements P /** @hide */ @Override public void setDefaultValues() { - mSignalStrength = Integer.MAX_VALUE; - mRsrp = Integer.MAX_VALUE; - mRsrq = Integer.MAX_VALUE; - mRssnr = Integer.MAX_VALUE; - mCqi = Integer.MAX_VALUE; - mTimingAdvance = Integer.MAX_VALUE; + mSignalStrength = CellInfo.UNAVAILABLE; + mRsrp = CellInfo.UNAVAILABLE; + mRsrq = CellInfo.UNAVAILABLE; + mRssnr = CellInfo.UNAVAILABLE; + mCqi = CellInfo.UNAVAILABLE; + mTimingAdvance = CellInfo.UNAVAILABLE; } /** @@ -104,26 +104,27 @@ public final class CellSignalStrengthLte extends CellSignalStrength implements P int levelRsrp = 0; int levelRssnr = 0; - if (mRsrp == Integer.MAX_VALUE) levelRsrp = 0; + if (mRsrp == CellInfo.UNAVAILABLE) levelRsrp = 0; else if (mRsrp >= -95) levelRsrp = SIGNAL_STRENGTH_GREAT; else if (mRsrp >= -105) levelRsrp = SIGNAL_STRENGTH_GOOD; else if (mRsrp >= -115) levelRsrp = SIGNAL_STRENGTH_MODERATE; else levelRsrp = SIGNAL_STRENGTH_POOR; // See RIL_LTE_SignalStrength in ril.h - if (mRssnr == Integer.MAX_VALUE) levelRssnr = 0; + if (mRssnr == CellInfo.UNAVAILABLE) levelRssnr = 0; else if (mRssnr >= 45) levelRssnr = SIGNAL_STRENGTH_GREAT; else if (mRssnr >= 10) levelRssnr = SIGNAL_STRENGTH_GOOD; else if (mRssnr >= -30) levelRssnr = SIGNAL_STRENGTH_MODERATE; else levelRssnr = SIGNAL_STRENGTH_POOR; int level; - if (mRsrp == Integer.MAX_VALUE) + if (mRsrp == CellInfo.UNAVAILABLE) { level = levelRssnr; - else if (mRssnr == Integer.MAX_VALUE) + } else if (mRssnr == CellInfo.UNAVAILABLE) { level = levelRsrp; - else + } else { level = (levelRssnr < levelRsrp) ? levelRssnr : levelRsrp; + } if (DBG) log("Lte rsrp level: " + levelRsrp + " snr level: " + levelRssnr + " level: " + level); @@ -133,7 +134,8 @@ public final class CellSignalStrengthLte extends CellSignalStrength implements P /** * Get reference signal received quality * - * @return the RSRQ if available or Integer.MAX_VALUE if unavailable. + * @return the RSRQ if available or + * {@link android.telephony.CellInfo#UNAVAILABLE UNAVAILABLE} if unavailable. */ public int getRsrq() { return mRsrq; @@ -142,7 +144,8 @@ public final class CellSignalStrengthLte extends CellSignalStrength implements P /** * Get reference signal signal-to-noise ratio * - * @return the RSSNR if available or Integer.MAX_VALUE if unavailable. + * @return the RSSNR if available or + * {@link android.telephony.CellInfo#UNAVAILABLE UNAVAILABLE} if unavailable. */ public int getRssnr() { return mRssnr; @@ -160,7 +163,8 @@ public final class CellSignalStrengthLte extends CellSignalStrength implements P /** * Get channel quality indicator * - * @return the CQI if available or Integer.MAX_VALUE if unavailable. + * @return the CQI if available or + * {@link android.telephony.CellInfo#UNAVAILABLE UNAVAILABLE} if unavailable. */ public int getCqi() { return mCqi; @@ -184,7 +188,7 @@ public final class CellSignalStrengthLte extends CellSignalStrength implements P public int getAsuLevel() { int lteAsuLevel = 99; int lteDbm = getDbm(); - if (lteDbm == Integer.MAX_VALUE) lteAsuLevel = 99; + if (lteDbm == CellInfo.UNAVAILABLE) lteAsuLevel = 99; else if (lteDbm <= -140) lteAsuLevel = 0; else if (lteDbm >= -43) lteAsuLevel = 97; else lteAsuLevel = lteDbm + 140; @@ -194,10 +198,11 @@ public final class CellSignalStrengthLte extends CellSignalStrength implements P /** * Get the timing advance value for LTE, as a value in range of 0..1282. - * Integer.MAX_VALUE is reported when there is no active RRC - * connection. Refer to 3GPP 36.213 Sec 4.2.3 + * {@link android.telephony.CellInfo#UNAVAILABLE UNAVAILABLE} is reported when there is no + * active RRC connection. Refer to 3GPP 36.213 Sec 4.2.3 * - * @return the LTE timing advance if available or Integer.MAX_VALUE if unavailable. + * @return the LTE timing advance if available or + * {@link android.telephony.CellInfo#UNAVAILABLE UNAVAILABLE} if unavailable. */ public int getTimingAdvance() { return mTimingAdvance; @@ -252,8 +257,8 @@ public final class CellSignalStrengthLte extends CellSignalStrength implements P // Need to multiply rsrp and rsrq by -1 // to ensure consistency when reading values written here // unless the values are invalid - dest.writeInt(mRsrp * (mRsrp != Integer.MAX_VALUE ? -1 : 1)); - dest.writeInt(mRsrq * (mRsrq != Integer.MAX_VALUE ? -1 : 1)); + dest.writeInt(mRsrp * (mRsrp != CellInfo.UNAVAILABLE ? -1 : 1)); + dest.writeInt(mRsrq * (mRsrq != CellInfo.UNAVAILABLE ? -1 : 1)); dest.writeInt(mRssnr); dest.writeInt(mCqi); dest.writeInt(mTimingAdvance); @@ -268,9 +273,9 @@ public final class CellSignalStrengthLte extends CellSignalStrength implements P // rsrp and rsrq are written into the parcel as positive values. // Need to convert into negative values unless the values are invalid mRsrp = in.readInt(); - if (mRsrp != Integer.MAX_VALUE) mRsrp *= -1; + if (mRsrp != CellInfo.UNAVAILABLE) mRsrp *= -1; mRsrq = in.readInt(); - if (mRsrq != Integer.MAX_VALUE) mRsrq *= -1; + if (mRsrq != CellInfo.UNAVAILABLE) mRsrq *= -1; mRssnr = in.readInt(); mCqi = in.readInt(); mTimingAdvance = in.readInt(); diff --git a/telephony/java/android/telephony/CellSignalStrengthTdscdma.java b/telephony/java/android/telephony/CellSignalStrengthTdscdma.java index 41859a3e96d9..4d040cca5fff 100644 --- a/telephony/java/android/telephony/CellSignalStrengthTdscdma.java +++ b/telephony/java/android/telephony/CellSignalStrengthTdscdma.java @@ -36,11 +36,11 @@ public final class CellSignalStrengthTdscdma extends CellSignalStrength implemen private static final int TDSCDMA_SIGNAL_STRENGTH_MODERATE = 5; private int mSignalStrength; // in ASU; Valid values are (0-31, 99) as defined in TS 27.007 8.5 - // or Integer.MAX_VALUE if unknown + // or CellInfo.UNAVAILABLE if unknown private int mBitErrorRate; // bit error rate (0-7, 99) as defined in TS 27.007 8.5 or - // Integer.MAX_VALUE if unknown - private int mRscp; // Pilot power (0-96, 255) as defined in TS 27.007 8.69 or Integer.MAX_VALUE - // if unknown + // CellInfo.UNAVAILABLE if unknown + private int mRscp; // Pilot power (0-96, 255) as defined in TS 27.007 8.69 or + // CellInfo.UNAVAILABLE if unknown /** @hide */ public CellSignalStrengthTdscdma() { @@ -75,9 +75,9 @@ public final class CellSignalStrengthTdscdma extends CellSignalStrength implemen /** @hide */ @Override public void setDefaultValues() { - mSignalStrength = Integer.MAX_VALUE; - mBitErrorRate = Integer.MAX_VALUE; - mRscp = Integer.MAX_VALUE; + mSignalStrength = CellInfo.UNAVAILABLE; + mBitErrorRate = CellInfo.UNAVAILABLE; + mRscp = CellInfo.UNAVAILABLE; } /** @@ -118,11 +118,11 @@ public final class CellSignalStrengthTdscdma extends CellSignalStrength implemen int dBm; int level = mSignalStrength; - int asu = (level == 99 ? Integer.MAX_VALUE : level); - if (asu != Integer.MAX_VALUE) { + int asu = (level == 99 ? CellInfo.UNAVAILABLE : level); + if (asu != CellInfo.UNAVAILABLE) { dBm = -113 + (2 * asu); } else { - dBm = Integer.MAX_VALUE; + dBm = CellInfo.UNAVAILABLE; } if (DBG) log("getDbm=" + dBm); return dBm; diff --git a/telephony/java/android/telephony/CellSignalStrengthWcdma.java b/telephony/java/android/telephony/CellSignalStrengthWcdma.java index 66e08822dfa7..0048cbdea8f6 100644 --- a/telephony/java/android/telephony/CellSignalStrengthWcdma.java +++ b/telephony/java/android/telephony/CellSignalStrengthWcdma.java @@ -37,14 +37,14 @@ public final class CellSignalStrengthWcdma extends CellSignalStrength implements @UnsupportedAppUsage private int mSignalStrength; // in ASU; Valid values are (0-31, 99) as defined in TS 27.007 8.5 - // or Integer.MAX_VALUE if unknown + // or CellInfo.UNAVAILABLE if unknown @UnsupportedAppUsage private int mBitErrorRate; // bit error rate (0-7, 99) as defined in TS 27.007 8.5 or - // Integer.MAX_VALUE if unknown + // CellInfo.UNAVAILABLE if unknown private int mRscp; // bit error rate (0-96, 255) as defined in TS 27.007 8.69 or - // Integer.MAX_VALUE if unknown + // CellInfo.UNAVAILABLE if unknown private int mEcNo; // signal to noise radio (0-49, 255) as defined in TS 27.007 8.69 or - // Integer.MAX_VALUE if unknown + // CellInfo.UNAVAILABLE if unknown /** @hide */ public CellSignalStrengthWcdma() { @@ -81,10 +81,10 @@ public final class CellSignalStrengthWcdma extends CellSignalStrength implements /** @hide */ @Override public void setDefaultValues() { - mSignalStrength = Integer.MAX_VALUE; - mBitErrorRate = Integer.MAX_VALUE; - mRscp = Integer.MAX_VALUE; - mEcNo = Integer.MAX_VALUE; + mSignalStrength = CellInfo.UNAVAILABLE; + mBitErrorRate = CellInfo.UNAVAILABLE; + mRscp = CellInfo.UNAVAILABLE; + mEcNo = CellInfo.UNAVAILABLE; } /** @@ -119,11 +119,11 @@ public final class CellSignalStrengthWcdma extends CellSignalStrength implements int dBm; int level = mSignalStrength; - int asu = (level == 99 ? Integer.MAX_VALUE : level); - if (asu != Integer.MAX_VALUE) { + int asu = (level == 99 ? CellInfo.UNAVAILABLE : level); + if (asu != CellInfo.UNAVAILABLE) { dBm = -113 + (2 * asu); } else { - dBm = Integer.MAX_VALUE; + dBm = CellInfo.UNAVAILABLE; } if (DBG) log("getDbm=" + dBm); return dBm; diff --git a/telephony/java/android/telephony/PhoneStateListener.java b/telephony/java/android/telephony/PhoneStateListener.java index c83d6aa3897d..97b1bdcd1c22 100644 --- a/telephony/java/android/telephony/PhoneStateListener.java +++ b/telephony/java/android/telephony/PhoneStateListener.java @@ -301,6 +301,14 @@ public class PhoneStateListener { @SystemApi public static final int LISTEN_RADIO_POWER_STATE_CHANGED = 0x00800000; + /** + * Listen for changes to emergency number list based on all active subscriptions. + * + * <p>Requires permission {@link android.Manifest.permission#READ_PHONE_STATE} or the calling + * app has carrier privileges (see {@link TelephonyManager#hasCarrierPrivileges}). + */ + public static final int LISTEN_EMERGENCY_NUMBER_LIST = 0x01000000; + /* * Subscription used to listen to the phone state changes * @hide diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java index 04596679293f..0ba18ee54bf4 100644 --- a/telephony/java/android/telephony/SubscriptionManager.java +++ b/telephony/java/android/telephony/SubscriptionManager.java @@ -43,6 +43,7 @@ import android.database.ContentObserver; import android.net.INetworkPolicyManager; import android.net.NetworkCapabilities; import android.net.Uri; +import android.os.Binder; import android.os.Build; import android.os.Handler; import android.os.Looper; @@ -781,8 +782,13 @@ public class SubscriptionManager { IOnSubscriptionsChangedListener callback = new IOnSubscriptionsChangedListener.Stub() { @Override public void onSubscriptionsChanged() { - if (DBG) log("onOpportunisticSubscriptionsChanged callback received."); - mExecutor.execute(() -> onOpportunisticSubscriptionsChanged()); + final long identity = Binder.clearCallingIdentity(); + try { + if (DBG) log("onOpportunisticSubscriptionsChanged callback received."); + mExecutor.execute(() -> onOpportunisticSubscriptionsChanged()); + } finally { + Binder.restoreCallingIdentity(identity); + } } }; diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java index f484d1fc97f9..32ca156054f7 100644 --- a/telephony/java/android/telephony/TelephonyManager.java +++ b/telephony/java/android/telephony/TelephonyManager.java @@ -20,7 +20,9 @@ import static android.content.Context.TELECOM_SERVICE; import static com.android.internal.util.Preconditions.checkNotNull; +import android.Manifest; import android.annotation.IntDef; +import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; import android.annotation.SdkConstant; @@ -56,6 +58,8 @@ import android.telecom.PhoneAccount; import android.telecom.PhoneAccountHandle; import android.telecom.TelecomManager; import android.telephony.VisualVoicemailService.VisualVoicemailTask; +import android.telephony.emergency.EmergencyNumber; +import android.telephony.emergency.EmergencyNumber.EmergencyServiceCategories; import android.telephony.ims.aidl.IImsConfig; import android.telephony.ims.aidl.IImsMmTelFeature; import android.telephony.ims.aidl.IImsRcsFeature; @@ -85,6 +89,7 @@ import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Map; import java.util.concurrent.Executor; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -5169,6 +5174,9 @@ public class TelephonyManager { * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE} or that the calling * app has carrier privileges (see {@link #hasCarrierPrivileges}). * + * TODO: remove this one. use {@link #rebootRadio()} for reset type 1 and + * {@link #resetRadioConfig()} for reset type 3 + * * @param resetType reset type: 1: reload NV reset, 2: erase NV reset, 3: factory NV reset * @return true on success; false on any failure. * @@ -5178,8 +5186,15 @@ public class TelephonyManager { public boolean nvResetConfig(int resetType) { try { ITelephony telephony = getITelephony(); - if (telephony != null) - return telephony.nvResetConfig(resetType); + if (telephony != null) { + if (resetType == 1 /*1: reload NV reset */) { + return telephony.rebootModem(getSlotIndex()); + } else if (resetType == 3 /*3: factory NV reset */) { + return telephony.resetModemConfig(getSlotIndex()); + } else { + Rlog.e(TAG, "nvResetConfig unsupported reset type"); + } + } } catch (RemoteException ex) { Rlog.e(TAG, "nvResetConfig RemoteException", ex); } catch (NullPointerException ex) { @@ -5189,6 +5204,61 @@ public class TelephonyManager { } /** + * Rollback modem configurations to factory default except some config which are in whitelist. + * Used for device configuration by some CDMA operators. + * + * <p>Requires Permission: + * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE} or that the calling + * app has carrier privileges (see {@link #hasCarrierPrivileges}). + * + * @return {@code true} on success; {@code false} on any failure. + * + * @hide + */ + @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE) + @SystemApi + public boolean resetRadioConfig() { + try { + ITelephony telephony = getITelephony(); + if (telephony != null) { + return telephony.resetModemConfig(getSlotIndex()); + } + } catch (RemoteException ex) { + Rlog.e(TAG, "resetRadioConfig RemoteException", ex); + } catch (NullPointerException ex) { + Rlog.e(TAG, "resetRadioConfig NPE", ex); + } + return false; + } + + /** + * Generate a radio modem reset. Used for device configuration by some CDMA operators. + * + * <p>Requires Permission: + * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE} or that the calling + * app has carrier privileges (see {@link #hasCarrierPrivileges}). + * + * @return {@code true} on success; {@code false} on any failure. + * + * @hide + */ + @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE) + @SystemApi + public boolean rebootRadio() { + try { + ITelephony telephony = getITelephony(); + if (telephony != null) { + return telephony.rebootModem(getSlotIndex()); + } + } catch (RemoteException ex) { + Rlog.e(TAG, "rebootRadio RemoteException", ex); + } catch (NullPointerException ex) { + Rlog.e(TAG, "rebootRadio NPE", ex); + } + return false; + } + + /** * Return an appropriate subscription ID for any situation. * * If this object has been created with {@link #createForSubscriptionId}, then the provided @@ -8984,4 +9054,115 @@ public class TelephonyManager { } } + /** + * Get the emergency number list based on current locale, sim, default, modem and network. + * + * <p>The emergency number {@link EmergencyNumber} with higher display priority is located at + * the smaller index in the returned list. + * + * <p>The subscriptions which the returned list would be based on, are all the active + * subscriptions, no matter which subscription could be used to create TelephonyManager. + * + * <p>Requires permission {@link android.Manifest.permission#READ_PHONE_STATE} or the calling + * app has carrier privileges (see {@link #hasCarrierPrivileges}). + * + * @return Map including the key as the active subscription ID (Note: if there is no active + * subscription, the key is {@link SubscriptionManager#DEFAULT_SUBSCRIPTION_ID}) and the value + * as the list of {@link EmergencyNumber}; null if this information is not available. + */ + @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) + @Nullable + public Map<Integer, List<EmergencyNumber>> getCurrentEmergencyNumberList() { + try { + ITelephony telephony = getITelephony(); + if (telephony == null) { + return null; + } + return telephony.getCurrentEmergencyNumberList(mContext.getOpPackageName()); + } catch (RemoteException ex) { + Log.e(TAG, "getCurrentEmergencyNumberList RemoteException", ex); + } + return null; + } + + /** + * Get the per-category emergency number list based on current locale, sim, default, modem + * and network. + * + * <p>The emergency number {@link EmergencyNumber} with higher display priority is located at + * the smaller index in the returned list. + * + * <p>The subscriptions which the returned list would be based on, are all the active + * subscriptions, no matter which subscription could be used to create TelephonyManager. + * + * <p>Requires permission {@link android.Manifest.permission#READ_PHONE_STATE} or the calling + * app has carrier privileges (see {@link #hasCarrierPrivileges}). + * + * @param categories the emergency service categories which are the bitwise-OR combination of + * the following constants: + * <ol> + * <li>{@link EmergencyNumber#EMERGENCY_SERVICE_CATEGORY_UNSPECIFIED} </li> + * <li>{@link EmergencyNumber#EMERGENCY_SERVICE_CATEGORY_POLICE} </li> + * <li>{@link EmergencyNumber#EMERGENCY_SERVICE_CATEGORY_AMBULANCE} </li> + * <li>{@link EmergencyNumber#EMERGENCY_SERVICE_CATEGORY_FIRE_BRIGADE} </li> + * <li>{@link EmergencyNumber#EMERGENCY_SERVICE_CATEGORY_MARINE_GUARD} </li> + * <li>{@link EmergencyNumber#EMERGENCY_SERVICE_CATEGORY_MOUNTAIN_RESCUE} </li> + * <li>{@link EmergencyNumber#EMERGENCY_SERVICE_CATEGORY_MIEC} </li> + * <li>{@link EmergencyNumber#EMERGENCY_SERVICE_CATEGORY_AIEC} </li> + * </ol> + * @return Map including the key as the active subscription ID (Note: if there is no active + * subscription, the key is {@link SubscriptionManager#DEFAULT_SUBSCRIPTION_ID}) and the value + * as the list of {@link EmergencyNumber}; null if this information is not available. + */ + @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) + @Nullable + public Map<Integer, List<EmergencyNumber>> getCurrentEmergencyNumberList( + @EmergencyServiceCategories int categories) { + try { + ITelephony telephony = getITelephony(); + if (telephony == null) { + return null; + } + Map<Integer, List<EmergencyNumber>> numberMap = telephony + .getCurrentEmergencyNumberList(mContext.getOpPackageName()); + if (numberMap != null) { + for (Integer subscriptionId : numberMap.keySet()) { + List<EmergencyNumber> numberList = numberMap.get(subscriptionId); + for (EmergencyNumber number : numberList) { + if (!number.isInEmergencyServiceCategories(categories)) { + numberList.remove(number); + } + } + } + } + return numberMap; + } catch (RemoteException ex) { + Log.e(TAG, "getCurrentEmergencyNumberList with Categories RemoteException", ex); + } + return null; + } + + /** + * Checks if the supplied number is an emergency number based on current locale, sim, default, + * modem and network. + * + * <p>The subscriptions which the identification would be based on, are all the active + * subscriptions, no matter which subscription could be used to create TelephonyManager. + * + * @param number - the number to look up + * @return {@code true} if the given number is an emergency number based on current locale, + * sim, modem and network; {@code false} otherwise. + */ + public boolean isCurrentEmergencyNumber(@NonNull String number) { + try { + ITelephony telephony = getITelephony(); + if (telephony == null) { + return false; + } + return telephony.isCurrentEmergencyNumber(number); + } catch (RemoteException ex) { + Log.e(TAG, "isCurrentEmergencyNumber RemoteException", ex); + } + return false; + } } diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl index dc233585dea4..d20627762619 100644 --- a/telephony/java/com/android/internal/telephony/ITelephony.aidl +++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl @@ -49,6 +49,7 @@ import com.android.internal.telephony.CellNetworkScanResult; import com.android.internal.telephony.OperatorInfo; import java.util.List; +import java.util.Map; import android.telephony.UiccSlotInfo; @@ -638,15 +639,30 @@ interface ITelephony { boolean nvWriteCdmaPrl(in byte[] preferredRoamingList); /** - * Perform the specified type of NV config reset. The radio will be taken offline - * and the device must be rebooted after the operation. Used for device - * configuration by some CDMA operators. + * Rollback modem configurations to factory default except some config which are in whitelist. + * Used for device configuration by some CDMA operators. * - * @param resetType the type of reset to perform (1 == factory reset; 2 == NV-only reset). - * @return true on success; false on any failure. + * <p>Requires Permission: + * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE} or that the calling + * app has carrier privileges (see {@link #hasCarrierPrivileges}). + * + * @param slotIndex - device slot. + * @return {@code true} on success; {@code false} on any failure. */ - boolean nvResetConfig(int resetType); + boolean resetModemConfig(int slotIndex); + /** + * Generate a radio modem reset. Used for device configuration by some CDMA operators. + * Different than {@link #setRadioPower(boolean)}, modem reboot will power down sim card. + * + * <p>Requires Permission: + * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE} or that the calling + * app has carrier privileges (see {@link #hasCarrierPrivileges}). + * + * @param slotIndex - device slot. + * @return {@code true} on success; {@code false} on any failure. + */ + boolean rebootModem(int slotIndex); /* * Get the calculated preferred network type. * Used for device configuration by some CDMA operators. @@ -1615,4 +1631,14 @@ interface ITelephony { * return true if TTY over VoLTE is enabled for the subscription specified. */ boolean isTtyOverVolteEnabled(int subId); + + /** + * Return the emergency number list from all the active subscriptions. + */ + Map getCurrentEmergencyNumberList(String callingPackage); + + /** + * Identify if the number is emergency number, based on all the active subscriptions. + */ + boolean isCurrentEmergencyNumber(String number); } diff --git a/tests/net/java/android/net/NetworkStatsTest.java b/tests/net/java/android/net/NetworkStatsTest.java index 8f18d072a0bb..d6dbf5aaa9d8 100644 --- a/tests/net/java/android/net/NetworkStatsTest.java +++ b/tests/net/java/android/net/NetworkStatsTest.java @@ -39,6 +39,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; +import android.net.NetworkStats.Entry; import android.os.Process; import android.support.test.runner.AndroidJUnit4; import android.support.test.filters.SmallTest; @@ -785,7 +786,38 @@ public class NetworkStatsTest { ArrayMap<String, String> stackedIface = new ArrayMap<>(); stackedIface.put(v4Iface, baseIface); - NetworkStats.Entry otherEntry = new NetworkStats.Entry( + // Ipv4 traffic sent/received by an app on stacked interface. + final NetworkStats.Entry appEntry = new NetworkStats.Entry( + v4Iface, appUid, SET_DEFAULT, TAG_NONE, + 30501490 /* rxBytes */, + 22401 /* rxPackets */, + 876235 /* txBytes */, + 13805 /* txPackets */, + 0 /* operations */); + + // Traffic measured for the root uid on the base interface if eBPF is in use. + // Incorrectly includes appEntry's bytes and packets, plus IPv4-IPv6 translation + // overhead (20 bytes per packet), only for TX traffic. + final NetworkStats.Entry ebpfRootUidEntry = new NetworkStats.Entry( + baseIface, rootUid, SET_DEFAULT, TAG_NONE, + 163577 /* rxBytes */, + 187 /* rxPackets */, + 1169942 /* txBytes */, + 13902 /* txPackets */, + 0 /* operations */); + + // Traffic measured for the root uid on the base interface if xt_qtaguid is in use. + // Incorrectly includes appEntry's bytes and packets, plus IPv4-IPv6 translation + // overhead (20 bytes per packet), in both directions. + final NetworkStats.Entry xtRootUidEntry = new NetworkStats.Entry( + baseIface, rootUid, SET_DEFAULT, TAG_NONE, + 31113087 /* rxBytes */, + 22588 /* rxPackets */, + 1169942 /* txBytes */, + 13902 /* txPackets */, + 0 /* operations */); + + final NetworkStats.Entry otherEntry = new NetworkStats.Entry( otherIface, appUid, SET_DEFAULT, TAG_NONE, 2600 /* rxBytes */, 2 /* rxPackets */, @@ -793,39 +825,41 @@ public class NetworkStatsTest { 3 /* txPackets */, 0 /* operations */); - NetworkStats stats = new NetworkStats(TEST_START, 3) - .addValues(v4Iface, appUid, SET_DEFAULT, TAG_NONE, - 30501490 /* rxBytes */, - 22401 /* rxPackets */, - 876235 /* txBytes */, - 13805 /* txPackets */, - 0 /* operations */) - .addValues(baseIface, rootUid, SET_DEFAULT, TAG_NONE, - 31113087, - 22588, - 1169942, - 13902, - 0) + final NetworkStats statsXt = new NetworkStats(TEST_START, 3) + .addValues(appEntry) + .addValues(xtRootUidEntry) .addValues(otherEntry); - stats.apply464xlatAdjustments(stackedIface); + final NetworkStats statsEbpf = new NetworkStats(TEST_START, 3) + .addValues(appEntry) + .addValues(ebpfRootUidEntry) + .addValues(otherEntry); - assertEquals(3, stats.size()); - assertValues(stats, 0, v4Iface, appUid, SET_DEFAULT, TAG_NONE, - METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, + statsXt.apply464xlatAdjustments(stackedIface, false); + statsEbpf.apply464xlatAdjustments(stackedIface, true); + + assertEquals(3, statsXt.size()); + assertEquals(3, statsEbpf.size()); + final NetworkStats.Entry expectedAppUid = new NetworkStats.Entry( + v4Iface, appUid, SET_DEFAULT, TAG_NONE, 30949510, 22401, 1152335, 13805, 0); - assertValues(stats, 1, baseIface, 0, SET_DEFAULT, TAG_NONE, - METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, + final NetworkStats.Entry expectedRootUid = new NetworkStats.Entry( + baseIface, 0, SET_DEFAULT, TAG_NONE, 163577, 187, 17607, 97, 0); - assertEquals(otherEntry, stats.getValues(2, null)); + assertEquals(expectedAppUid, statsXt.getValues(0, null)); + assertEquals(expectedRootUid, statsXt.getValues(1, null)); + assertEquals(otherEntry, statsXt.getValues(2, null)); + assertEquals(expectedAppUid, statsEbpf.getValues(0, null)); + assertEquals(expectedRootUid, statsEbpf.getValues(1, null)); + assertEquals(otherEntry, statsEbpf.getValues(2, null)); } @Test @@ -850,7 +884,7 @@ public class NetworkStatsTest { .addValues(secondEntry); // Empty map: no adjustment - stats.apply464xlatAdjustments(new ArrayMap<>()); + stats.apply464xlatAdjustments(new ArrayMap<>(), false); assertEquals(2, stats.size()); assertEquals(firstEntry, stats.getValues(0, null)); diff --git a/tests/net/java/com/android/server/connectivity/PermissionMonitorTest.java b/tests/net/java/com/android/server/connectivity/PermissionMonitorTest.java index 4dc63f249a52..f12756a8062f 100644 --- a/tests/net/java/com/android/server/connectivity/PermissionMonitorTest.java +++ b/tests/net/java/com/android/server/connectivity/PermissionMonitorTest.java @@ -25,6 +25,7 @@ import static android.content.pm.ApplicationInfo.PRIVATE_FLAG_OEM; import static android.content.pm.ApplicationInfo.PRIVATE_FLAG_PRODUCT; import static android.content.pm.ApplicationInfo.PRIVATE_FLAG_VENDOR; import static android.content.pm.PackageManager.GET_PERMISSIONS; +import static android.os.Process.SYSTEM_UID; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; @@ -55,26 +56,41 @@ public class PermissionMonitorTest { private static final String PARTITION_OEM = "oem"; private static final String PARTITION_PRODUCT = "product"; private static final String PARTITION_VENDOR = "vendor"; + private static final int VERSION_P = Build.VERSION_CODES.P; + private static final int VERSION_Q = Build.VERSION_CODES.Q; @Mock private Context mContext; @Mock private PackageManager mPackageManager; private PermissionMonitor mPermissionMonitor; + private int mMockFirstSdkInt; @Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); when(mContext.getPackageManager()).thenReturn(mPackageManager); - when(mPackageManager.getPackagesForUid(MOCK_UID)).thenReturn(MOCK_PACKAGE_NAMES); - mPermissionMonitor = new PermissionMonitor(mContext, null); + when(mPackageManager.getPackagesForUid(anyInt())).thenReturn(MOCK_PACKAGE_NAMES); + // Try to use spy() here for stubbing getDeviceFirstSdkInt value but the spies are loaded + // by a custom class loader that's different from the loader used for loading the real + // thing. That means those two classes are not in the same package, so a package private + // method is not accessible. Hence, using override method to control FIRST_SDK_INT value + // instead of spy function for testing. + mPermissionMonitor = new PermissionMonitor(mContext, null) { + @Override + int getDeviceFirstSdkInt() { + return mMockFirstSdkInt; + } + }; } - private void expectPermission(String[] permissions, String partition, - int targetSdkVersion) throws Exception { - final PackageInfo packageInfo = packageInfoWithPermissions(permissions, partition); + private boolean hasBgPermission(String partition, int targetSdkVersion, int uid, + String... permission) throws Exception { + final PackageInfo packageInfo = packageInfoWithPermissions(permission, partition); packageInfo.applicationInfo.targetSdkVersion = targetSdkVersion; + packageInfo.applicationInfo.uid = uid; when(mPackageManager.getPackageInfoAsUser( eq(MOCK_PACKAGE_NAMES[0]), eq(GET_PERMISSIONS), anyInt())).thenReturn(packageInfo); + return mPermissionMonitor.hasUseBackgroundNetworksPermission(uid); } private PackageInfo packageInfoWithPermissions(String[] permissions, String partition) { @@ -136,51 +152,44 @@ public class PermissionMonitorTest { @Test public void testHasUseBackgroundNetworksPermission() throws Exception { - expectPermission(new String[] { CHANGE_NETWORK_STATE }, - PARTITION_SYSTEM, Build.VERSION_CODES.P); - assertTrue(mPermissionMonitor.hasUseBackgroundNetworksPermission(MOCK_UID)); - expectPermission(new String[] { NETWORK_STACK }, PARTITION_SYSTEM, Build.VERSION_CODES.P); - assertTrue(mPermissionMonitor.hasUseBackgroundNetworksPermission(MOCK_UID)); - expectPermission(new String[] { CONNECTIVITY_INTERNAL }, - PARTITION_SYSTEM, Build.VERSION_CODES.P); - assertTrue(mPermissionMonitor.hasUseBackgroundNetworksPermission(MOCK_UID)); - expectPermission(new String[] { CONNECTIVITY_USE_RESTRICTED_NETWORKS }, - PARTITION_SYSTEM, Build.VERSION_CODES.P); - assertTrue(mPermissionMonitor.hasUseBackgroundNetworksPermission(MOCK_UID)); - - expectPermission(new String[] { CHANGE_NETWORK_STATE }, - PARTITION_VENDOR, Build.VERSION_CODES.P); - assertTrue(mPermissionMonitor.hasUseBackgroundNetworksPermission(MOCK_UID)); - expectPermission(new String[] { NETWORK_STACK }, - PARTITION_VENDOR, Build.VERSION_CODES.P); - assertTrue(mPermissionMonitor.hasUseBackgroundNetworksPermission(MOCK_UID)); - expectPermission(new String[] { CONNECTIVITY_INTERNAL }, - PARTITION_VENDOR, Build.VERSION_CODES.P); - assertTrue(mPermissionMonitor.hasUseBackgroundNetworksPermission(MOCK_UID)); - expectPermission(new String[] { CONNECTIVITY_USE_RESTRICTED_NETWORKS }, - PARTITION_VENDOR, Build.VERSION_CODES.P); - assertTrue(mPermissionMonitor.hasUseBackgroundNetworksPermission(MOCK_UID)); - - expectPermission(new String[] {}, PARTITION_SYSTEM, Build.VERSION_CODES.P); - assertFalse(mPermissionMonitor.hasUseBackgroundNetworksPermission(MOCK_UID)); - expectPermission(new String[] { CHANGE_WIFI_STATE }, - PARTITION_SYSTEM, Build.VERSION_CODES.P); - assertFalse(mPermissionMonitor.hasUseBackgroundNetworksPermission(MOCK_UID)); - expectPermission(new String[] {}, PARTITION_VENDOR, Build.VERSION_CODES.P); - assertTrue(mPermissionMonitor.hasUseBackgroundNetworksPermission(MOCK_UID)); - expectPermission(new String[] { CHANGE_WIFI_STATE }, - PARTITION_VENDOR, Build.VERSION_CODES.P); - assertTrue(mPermissionMonitor.hasUseBackgroundNetworksPermission(MOCK_UID)); - - expectPermission(new String[] {}, PARTITION_SYSTEM, Build.VERSION_CODES.Q); - assertFalse(mPermissionMonitor.hasUseBackgroundNetworksPermission(MOCK_UID)); - expectPermission(new String[] { CHANGE_WIFI_STATE }, - PARTITION_SYSTEM, Build.VERSION_CODES.Q); - assertFalse(mPermissionMonitor.hasUseBackgroundNetworksPermission(MOCK_UID)); - expectPermission(new String[] {}, PARTITION_VENDOR, Build.VERSION_CODES.Q); - assertFalse(mPermissionMonitor.hasUseBackgroundNetworksPermission(MOCK_UID)); - expectPermission(new String[] { CHANGE_WIFI_STATE }, - PARTITION_VENDOR, Build.VERSION_CODES.Q); - assertFalse(mPermissionMonitor.hasUseBackgroundNetworksPermission(MOCK_UID)); + assertFalse(hasBgPermission(PARTITION_SYSTEM, VERSION_P, MOCK_UID)); + assertTrue(hasBgPermission(PARTITION_SYSTEM, VERSION_P, MOCK_UID, CHANGE_NETWORK_STATE)); + assertTrue(hasBgPermission(PARTITION_SYSTEM, VERSION_P, MOCK_UID, NETWORK_STACK)); + assertTrue(hasBgPermission(PARTITION_SYSTEM, VERSION_P, MOCK_UID, CONNECTIVITY_INTERNAL)); + assertTrue(hasBgPermission(PARTITION_SYSTEM, VERSION_P, MOCK_UID, + CONNECTIVITY_USE_RESTRICTED_NETWORKS)); + assertFalse(hasBgPermission(PARTITION_SYSTEM, VERSION_P, MOCK_UID, CHANGE_WIFI_STATE)); + + assertFalse(hasBgPermission(PARTITION_SYSTEM, VERSION_Q, MOCK_UID)); + assertFalse(hasBgPermission(PARTITION_SYSTEM, VERSION_Q, MOCK_UID, CHANGE_WIFI_STATE)); + } + + @Test + public void testHasUseBackgroundNetworksPermissionSystemUid() throws Exception { + mMockFirstSdkInt = VERSION_P; + assertTrue(hasBgPermission(PARTITION_SYSTEM, VERSION_P, SYSTEM_UID)); + assertTrue(hasBgPermission(PARTITION_SYSTEM, VERSION_P, SYSTEM_UID, CHANGE_WIFI_STATE)); + assertTrue(hasBgPermission(PARTITION_SYSTEM, VERSION_P, SYSTEM_UID, + CONNECTIVITY_USE_RESTRICTED_NETWORKS)); + + mMockFirstSdkInt = VERSION_Q; + assertFalse(hasBgPermission(PARTITION_SYSTEM, VERSION_Q, SYSTEM_UID)); + assertFalse(hasBgPermission(PARTITION_SYSTEM, VERSION_Q, SYSTEM_UID, CHANGE_WIFI_STATE)); + assertTrue(hasBgPermission(PARTITION_SYSTEM, VERSION_Q, SYSTEM_UID, + CONNECTIVITY_USE_RESTRICTED_NETWORKS)); + } + + @Test + public void testHasUseBackgroundNetworksPermissionVendorApp() throws Exception { + assertTrue(hasBgPermission(PARTITION_VENDOR, VERSION_P, MOCK_UID)); + assertTrue(hasBgPermission(PARTITION_VENDOR, VERSION_P, MOCK_UID, CHANGE_NETWORK_STATE)); + assertTrue(hasBgPermission(PARTITION_VENDOR, VERSION_P, MOCK_UID, NETWORK_STACK)); + assertTrue(hasBgPermission(PARTITION_VENDOR, VERSION_P, MOCK_UID, CONNECTIVITY_INTERNAL)); + assertTrue(hasBgPermission(PARTITION_VENDOR, VERSION_P, MOCK_UID, + CONNECTIVITY_USE_RESTRICTED_NETWORKS)); + assertTrue(hasBgPermission(PARTITION_VENDOR, VERSION_P, MOCK_UID, CHANGE_WIFI_STATE)); + + assertFalse(hasBgPermission(PARTITION_VENDOR, VERSION_Q, MOCK_UID)); + assertFalse(hasBgPermission(PARTITION_VENDOR, VERSION_Q, MOCK_UID, CHANGE_WIFI_STATE)); } } |