diff options
196 files changed, 12023 insertions, 2036 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/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 a63992da0916..6ecae15f5fee 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(); } @@ -42512,6 +42607,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 +42714,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 { diff --git a/api/system-current.txt b/api/system-current.txt index bd7420bcf819..b1cbfa64a870 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"; @@ -1148,6 +1171,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); @@ -4511,6 +4535,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 { @@ -6796,6 +6833,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..02dde5a666b7 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,277 @@ 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"], + + 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/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 1deeb111788e..51d05bbb86d1 100644 --- a/cmds/statsd/src/atoms.proto +++ b/cmds/statsd/src/atoms.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. @@ -3121,3 +3122,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/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/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/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..4708ea48cb6e 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; 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..e4f0358173e0 100644 --- a/core/java/android/os/Binder.java +++ b/core/java/android/os/Binder.java @@ -555,6 +555,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/Settings.java b/core/java/android/provider/Settings.java index 79babace76d9..f58624ff55c0 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -6038,6 +6038,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"; @@ -7388,8 +7389,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 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/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/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java index ac5009451b6d..fb44eda5bc73 100644 --- a/core/java/android/util/FeatureFlagUtils.java +++ b/core/java/android/util/FeatureFlagUtils.java @@ -48,7 +48,7 @@ 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..4cbb097580d6 100644 --- a/core/java/android/view/inputmethod/InputMethodManager.java +++ b/core/java/android/view/inputmethod/InputMethodManager.java @@ -470,26 +470,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 +1027,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 +1116,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 +1141,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 +1189,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 +1257,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 +1426,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; } @@ -1827,9 +1856,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 +1900,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 +1971,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 +2009,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 +2061,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 +2239,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/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..06519758a698 --- /dev/null +++ b/core/java/com/android/internal/os/ProcStatsUtil.java @@ -0,0 +1,145 @@ +/* + * 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 java.io.ByteArrayOutputStream; +import java.io.FileInputStream; +import java.io.IOException; + +/** + * Utility functions for reading {@code proc} files + */ +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 + */ + @Nullable + 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 + */ + @Nullable + 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/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/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/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/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/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/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/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/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/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/layout/notification_info.xml b/packages/SystemUI/res/layout/notification_info.xml index f138685e9810..5c950ecfb49e 100644 --- a/packages/SystemUI/res/layout/notification_info.xml +++ b/packages/SystemUI/res/layout/notification_info.xml @@ -51,7 +51,7 @@ android:layout_centerVertical="true" android:layout_toEndOf="@id/pkgicon" /> <TextView - android:id="@+id/pkg_group_divider" + android:id="@+id/pkg_divider" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textAppearance="@*android:style/TextAppearance.Material.Notification.Info" @@ -61,7 +61,7 @@ android:layout_centerVertical="true" android:layout_toEndOf="@id/pkgname" /> <TextView - android:id="@+id/group_name" + android:id="@+id/delegate_name" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textAppearance="@*android:style/TextAppearance.Material.Notification.Info" @@ -70,7 +70,7 @@ android:ellipsize="end" android:maxLines="1" android:layout_centerVertical="true" - android:layout_toEndOf="@id/pkg_group_divider" /> + android:layout_toEndOf="@id/pkg_divider" /> <!-- 24 dp icon with 16 dp padding all around to mirror notification content margins --> <ImageButton android:id="@+id/info" @@ -101,13 +101,39 @@ android:layout_marginStart="@*android:dimen/notification_content_margin_start" android:layout_marginEnd="@*android:dimen/notification_content_margin_start" android:orientation="vertical"> - <!-- Channel Name --> - <TextView - android:id="@+id/channel_name" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_weight="1" - style="@android:style/TextAppearance.Material.Notification.Title" /> + <RelativeLayout + android:id="@+id/names" + android:layout_width="match_parent" + android:layout_height="wrap_content"> + <TextView + android:id="@+id/group_name" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:textAppearance="@*android:style/TextAppearance.Material.Notification.Title" + android:layout_marginStart="2dp" + android:layout_marginEnd="2dp" + android:ellipsize="end" + android:maxLines="1" + android:layout_centerVertical="true" /> + <TextView + android:id="@+id/pkg_group_divider" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:textAppearance="@*android:style/TextAppearance.Material.Notification.Title" + android:layout_marginStart="2dp" + android:layout_marginEnd="2dp" + android:text="@*android:string/notification_header_divider_symbol" + android:layout_centerVertical="true" + android:layout_toEndOf="@id/group_name" /> + <!-- Channel Name --> + <TextView + android:id="@+id/channel_name" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_weight="1" + style="@android:style/TextAppearance.Material.Notification.Title" + android:layout_toEndOf="@id/pkg_group_divider"/> + </RelativeLayout> <!-- Question prompt --> <TextView android:id="@+id/block_prompt" diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index b92fcc6c1d31..0cf7306cbe4b 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -1529,6 +1529,9 @@ <!-- Notification: Control panel: Label that displays when the app's notifications cannot be blocked. --> <string name="notification_unblockable_desc">These notifications can\'t be turned off</string> + <!-- Notification: Control panel: Label for the app that posted this notification, if it's not the package that the notification was posted for --> + <string name="notification_delegate_header">via <xliff:g id="app_name" example="YouTube">%1$s</xliff:g></string> + <!-- Notification Inline controls: describes what the app is doing in the background [CHAR_LIMIT=NONE] --> <string name="appops_camera">This app is using the camera.</string> <!-- Notification Inline controls: describes what the app is doing in the background [CHAR_LIMIT=NONE] --> @@ -1851,6 +1854,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/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/BiometricDialogView.java b/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDialogView.java index 7d77929556a5..d902b7266549 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDialogView.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDialogView.java @@ -167,7 +167,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 +188,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/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/notification/row/NotificationInfo.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInfo.java index 903c27277b70..912a2f7e598d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInfo.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInfo.java @@ -72,6 +72,7 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G private String mPackageName; private String mAppName; private int mAppUid; + private String mDelegatePkg; private int mNumUniqueChannelsInRow; private NotificationChannel mSingleNotificationChannel; private int mStartingUserImportance; @@ -193,6 +194,7 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G (mSbn.getNotification().flags & Notification.FLAG_FOREGROUND_SERVICE) != 0; mIsForBlockingHelper = isForBlockingHelper; mAppUid = mSbn.getUid(); + mDelegatePkg = mSbn.getOpPkg(); mIsDeviceProvisioned = isDeviceProvisioned; int numTotalChannels = mINotificationManager.getNumNotificationChannelsForPackage( @@ -234,26 +236,8 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G ((ImageView) findViewById(R.id.pkgicon)).setImageDrawable(pkgicon); ((TextView) findViewById(R.id.pkgname)).setText(mAppName); - // Set group information if this channel has an associated group. - CharSequence groupName = null; - if (mSingleNotificationChannel != null && mSingleNotificationChannel.getGroup() != null) { - final NotificationChannelGroup notificationChannelGroup = - mINotificationManager.getNotificationChannelGroupForPackage( - mSingleNotificationChannel.getGroup(), mPackageName, mAppUid); - if (notificationChannelGroup != null) { - groupName = notificationChannelGroup.getName(); - } - } - TextView groupNameView = findViewById(R.id.group_name); - TextView groupDividerView = findViewById(R.id.pkg_group_divider); - if (groupName != null) { - groupNameView.setText(groupName); - groupNameView.setVisibility(View.VISIBLE); - groupDividerView.setVisibility(View.VISIBLE); - } else { - groupNameView.setVisibility(View.GONE); - groupDividerView.setVisibility(View.GONE); - } + // Delegate + bindDelegate(); // Settings button. final View settingsButton = findViewById(R.id.info); @@ -273,9 +257,10 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G } } - private void bindPrompt() { + private void bindPrompt() throws RemoteException { final TextView blockPrompt = findViewById(R.id.block_prompt); bindName(); + bindGroup(); if (mIsNonblockable) { blockPrompt.setText(R.string.notification_unblockable_desc); } else { @@ -298,6 +283,60 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G } } + private void bindDelegate() { + TextView delegateView = findViewById(R.id.delegate_name); + TextView dividerView = findViewById(R.id.pkg_divider); + + CharSequence delegatePkg = null; + if (!TextUtils.equals(mPackageName, mDelegatePkg)) { + // this notification was posted by a delegate! + ApplicationInfo info; + try { + info = mPm.getApplicationInfo( + mDelegatePkg, + PackageManager.MATCH_UNINSTALLED_PACKAGES + | PackageManager.MATCH_DISABLED_COMPONENTS + | PackageManager.MATCH_DIRECT_BOOT_UNAWARE + | PackageManager.MATCH_DIRECT_BOOT_AWARE); + if (info != null) { + delegatePkg = String.valueOf(mPm.getApplicationLabel(info)); + } + } catch (PackageManager.NameNotFoundException e) {} + } + if (delegatePkg != null) { + delegateView.setText(mContext.getResources().getString( + R.string.notification_delegate_header, delegatePkg)); + delegateView.setVisibility(View.VISIBLE); + dividerView.setVisibility(View.VISIBLE); + } else { + delegateView.setVisibility(View.GONE); + dividerView.setVisibility(View.GONE); + } + } + + private void bindGroup() throws RemoteException { + // Set group information if this channel has an associated group. + CharSequence groupName = null; + if (mSingleNotificationChannel != null && mSingleNotificationChannel.getGroup() != null) { + final NotificationChannelGroup notificationChannelGroup = + mINotificationManager.getNotificationChannelGroupForPackage( + mSingleNotificationChannel.getGroup(), mPackageName, mAppUid); + if (notificationChannelGroup != null) { + groupName = notificationChannelGroup.getName(); + } + } + TextView groupNameView = findViewById(R.id.group_name); + TextView groupDividerView = findViewById(R.id.pkg_group_divider); + if (groupName != null) { + groupNameView.setText(groupName); + groupNameView.setVisibility(View.VISIBLE); + groupDividerView.setVisibility(View.VISIBLE); + } else { + groupNameView.setVisibility(View.GONE); + groupDividerView.setVisibility(View.GONE); + } + } + @VisibleForTesting void logBlockingHelperCounter(String counterTag) { if (mIsForBlockingHelper) { 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..936c2b879fa7 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; @@ -127,6 +129,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 +162,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 +519,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 @@ -5087,8 +5098,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/NotificationIconAreaController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java index 21b98db11a36..5960b13e8e31 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,31 @@ package com.android.systemui.statusbar.phone; +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.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 +35,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 +65,7 @@ public class NotificationIconAreaController implements DarkReceiver { private ViewGroup mNotificationScrollLayout; private Context mContext; private boolean mFullyDark; + private boolean mShowLowPriority; public NotificationIconAreaController(Context context, StatusBar statusBar) { mStatusBar = statusBar; @@ -56,6 +73,8 @@ public class NotificationIconAreaController implements DarkReceiver { mContext = context; mEntryManager = Dependency.get(NotificationEntryManager.class); + Dependency.get(TunerService.class).addTunable(mTunable, LOW_PRIORITY); + initializeNotificationAreaViews(context); } @@ -142,10 +161,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 +206,14 @@ public class NotificationIconAreaController implements DarkReceiver { private void updateShelfIcons() { updateIconsForLayout(entry -> entry.expandedIcon, mShelfIcons, - NotificationShelf.SHOW_AMBIENT_ICONS, false /* hideDismissed */, - mFullyDark /* hideRepliedMessages */); + NotificationShelf.SHOW_AMBIENT_ICONS, !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 +226,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 +236,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)); } 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..2b661abce04a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java @@ -3961,6 +3961,9 @@ public class StatusBar extends SystemUI implements DemoMode, @Override public void dozeTimeTick() { mNotificationPanel.dozeTimeTick(); + if (mAmbientIndicationContainer instanceof DozeReceiver) { + ((DozeReceiver) mAmbientIndicationContainer).dozeTimeTick(); + } } @Override @@ -4027,7 +4030,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 +4046,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 +4271,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 +4284,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/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/row/NotificationInfoTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationInfoTest.java index ca968a8af85b..02a618b7f82a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationInfoTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationInfoTest.java @@ -129,7 +129,7 @@ public class NotificationInfoTest extends SysuiTestCase { .thenReturn(packageInfo); final ApplicationInfo applicationInfo = new ApplicationInfo(); applicationInfo.uid = TEST_UID; // non-zero - when(mMockPackageManager.getApplicationInfo(anyString(), anyInt())).thenReturn( + when(mMockPackageManager.getApplicationInfo(eq(TEST_PACKAGE_NAME), anyInt())).thenReturn( applicationInfo); final PackageInfo systemPackageInfo = new PackageInfo(); systemPackageInfo.packageName = TEST_SYSTEM_PACKAGE_NAME; @@ -190,6 +190,35 @@ public class NotificationInfoTest extends SysuiTestCase { } @Test + public void testBindNotification_noDelegate() throws Exception { + mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, + TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false); + final TextView nameView = mNotificationInfo.findViewById(R.id.delegate_name); + assertEquals(GONE, nameView.getVisibility()); + final TextView dividerView = mNotificationInfo.findViewById(R.id.pkg_divider); + assertEquals(GONE, dividerView.getVisibility()); + } + + @Test + public void testBindNotification_delegate() throws Exception { + mSbn = new StatusBarNotification(TEST_PACKAGE_NAME, "other", 0, null, TEST_UID, 0, + new Notification(), UserHandle.CURRENT, null, 0); + final ApplicationInfo applicationInfo = new ApplicationInfo(); + applicationInfo.uid = 7; // non-zero + when(mMockPackageManager.getApplicationInfo(eq("other"), anyInt())).thenReturn( + applicationInfo); + when(mMockPackageManager.getApplicationLabel(any())).thenReturn("Other"); + + mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, + TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false); + final TextView nameView = mNotificationInfo.findViewById(R.id.delegate_name); + assertEquals(VISIBLE, nameView.getVisibility()); + assertTrue(nameView.getText().toString().contains("Other")); + final TextView dividerView = mNotificationInfo.findViewById(R.id.pkg_divider); + assertEquals(VISIBLE, dividerView.getVisibility()); + } + + @Test public void testBindNotification_GroupNameHiddenIfNoGroup() throws Exception { mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false); 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/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java index fbceade2341f..097405af0222 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java @@ -2068,7 +2068,9 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub && componentNameToEnable.equals(userState.mServiceToEnableWithShortcut)) { return false; } + userState.mServiceToEnableWithShortcut = componentNameToEnable; + scheduleNotifyClientsOfServicesStateChange(userState); return true; } @@ -2343,10 +2345,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 +2383,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}. */ 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/am/ActivityDisplay.java b/services/core/java/com/android/server/am/ActivityDisplay.java index ede13ef66ac4..a72470d90dab 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; @@ -1021,6 +1038,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/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java index 257a0042b510..17454b42524c 100644 --- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java +++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java @@ -226,9 +226,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 +672,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(); @@ -4108,25 +4105,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); + mService.startHomeActivityLocked(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,7 +4182,6 @@ 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; } @@ -4199,47 +4207,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 +4231,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 +4552,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/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/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index 93b83ae72cca..8d581df2c43e 100644 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -4332,19 +4332,20 @@ public class NotificationManagerService extends SystemService { * * Has side effects. */ - private boolean checkDisqualifyingFeatures(int userId, int callingUid, int id, String tag, + private boolean checkDisqualifyingFeatures(int userId, int uid, int id, String tag, NotificationRecord r, boolean isAutogroup) { final String pkg = r.sbn.getPackageName(); final boolean isSystemNotification = - isUidSystemOrPhone(callingUid) || ("android".equals(pkg)); + isUidSystemOrPhone(uid) || ("android".equals(pkg)); final boolean isNotificationFromListener = mListeners.isListenerPackage(pkg); // Limit the number of notifications that any given package except the android // package or a registered listener can enqueue. Prevents DOS attacks and deals with leaks. if (!isSystemNotification && !isNotificationFromListener) { synchronized (mNotificationLock) { + final int callingUid = Binder.getCallingUid(); if (mNotificationsByKey.get(r.sbn.getKey()) == null - && isCallerInstantApp(pkg, Binder.getCallingUid(), userId)) { + && isCallerInstantApp(callingUid, userId)) { // Ephemeral apps have some special constraints for notifications. // They are not allowed to create new notifications however they are allowed to // update notifications created by the system (e.g. a foreground service @@ -6452,24 +6453,24 @@ public class NotificationManagerService extends SystemService { } @VisibleForTesting - boolean isCallerInstantApp(String pkg, int callingUid, int userId) { + boolean isCallerInstantApp(int callingUid, int userId) { // System is always allowed to act for ephemeral apps. if (isUidSystemOrPhone(callingUid)) { return false; } - mAppOps.checkPackage(callingUid, pkg); - try { - ApplicationInfo ai = mPackageManager.getApplicationInfo(pkg, 0, userId); - if (ai == null) { - throw new SecurityException("Unknown package " + pkg); - } - return ai.isInstantApp(); + final String pkg = mPackageManager.getNameForUid(callingUid); + mAppOps.checkPackage(callingUid, pkg); + + ApplicationInfo ai = mPackageManager.getApplicationInfo(pkg, 0, userId); + if (ai == null) { + throw new SecurityException("Unknown package " + pkg); + } + return ai.isInstantApp(); } catch (RemoteException re) { - throw new SecurityException("Unknown package " + pkg, re); + throw new SecurityException("Unknown uid " + callingUid, re); } - } private void checkCallerIsSameApp(String pkg) { 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..7b35b9145f9a 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 @@ -1445,6 +1449,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 +1627,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/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/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/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/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/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java index 3266b8b92a19..f5c0603a2417 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -288,6 +288,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { when(mAudioManager.getRingerModeInternal()).thenReturn(AudioManager.RINGER_MODE_NORMAL); when(mPackageManagerClient.hasSystemFeature(FEATURE_WATCH)).thenReturn(false); when(mUgmInternal.newUriPermissionOwner(anyString())).thenReturn(mPermOwner); + when(mPackageManager.getNameForUid(mUid)).thenReturn(PKG); // write to a test file; the system file isn't readable from tests mFile = new File(mContext.getCacheDir(), "test.xml"); @@ -1735,7 +1736,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } @Test - public void testGetNotificationChannelFromPrivilegedListener_assistant_noAccess() throws Exception { + public void testGetNotificationChannelFromPrivilegedListener_assistant_noAccess() + throws Exception { mService.setPreferencesHelper(mPreferencesHelper); when(mCompanionMgr.getAssociations(PKG, mUid)).thenReturn(new ArrayList<>()); when(mAssistants.isServiceTokenValidLocked(any())).thenReturn(false); @@ -3459,11 +3461,12 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { ApplicationInfo info = new ApplicationInfo(); info.privateFlags = ApplicationInfo.PRIVATE_FLAG_INSTANT; when(mPackageManager.getApplicationInfo(anyString(), anyInt(), eq(0))).thenReturn(info); + when(mPackageManager.getNameForUid(anyInt())).thenReturn("any"); - assertTrue(mService.isCallerInstantApp("any", 45770, 0)); + assertTrue(mService.isCallerInstantApp(45770, 0)); info.privateFlags = 0; - assertFalse(mService.isCallerInstantApp("any", 575370, 0)); + assertFalse(mService.isCallerInstantApp(575370, 0)); } @Test @@ -3472,8 +3475,9 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { info.privateFlags = ApplicationInfo.PRIVATE_FLAG_INSTANT; when(mPackageManager.getApplicationInfo(anyString(), anyInt(), eq(10))).thenReturn(info); when(mPackageManager.getApplicationInfo(anyString(), anyInt(), eq(0))).thenReturn(null); + when(mPackageManager.getNameForUid(anyInt())).thenReturn("any"); - assertTrue(mService.isCallerInstantApp("any", 68638450, 10)); + assertTrue(mService.isCallerInstantApp(68638450, 10)); } @Test 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/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..f4b5f86270ae 100644 --- a/telephony/java/android/telephony/CarrierConfigManager.java +++ b/telephony/java/android/telephony/CarrierConfigManager.java @@ -2231,6 +2231,13 @@ 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"; + /** The default value for every variable. */ private final static PersistableBundle sDefaults; @@ -2579,6 +2586,7 @@ 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); } /** 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/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/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)); |