diff options
109 files changed, 2194 insertions, 1620 deletions
diff --git a/apct-tests/perftests/core/src/android/text/PrecomputedTextMemoryUsageTest.java b/apct-tests/perftests/core/src/android/text/MeasuredTextMemoryUsageTest.java index 73e17242ae78..fc6302ea9394 100644 --- a/apct-tests/perftests/core/src/android/text/PrecomputedTextMemoryUsageTest.java +++ b/apct-tests/perftests/core/src/android/text/MeasuredTextMemoryUsageTest.java @@ -45,7 +45,7 @@ import java.util.Random; @LargeTest @RunWith(AndroidJUnit4.class) -public class PrecomputedTextMemoryUsageTest { +public class MeasuredTextMemoryUsageTest { private static final int WORD_LENGTH = 9; // Random word has 9 characters. private static final boolean NO_STYLE_TEXT = false; @@ -53,7 +53,7 @@ public class PrecomputedTextMemoryUsageTest { private static int TRIAL_COUNT = 100; - public PrecomputedTextMemoryUsageTest() {} + public MeasuredTextMemoryUsageTest() {} private TextPerfUtils mTextUtil = new TextPerfUtils(); @@ -77,16 +77,13 @@ public class PrecomputedTextMemoryUsageTest { @Test public void testMemoryUsage_NoHyphenation() { int[] memories = new int[TRIAL_COUNT]; - final PrecomputedText.Params param = new PrecomputedText.Params.Builder(PAINT) + // Report median of randomly generated MeasuredText. + for (int i = 0; i < TRIAL_COUNT; ++i) { + memories[i] = new MeasuredText.Builder( + mTextUtil.nextRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT), PAINT) .setBreakStrategy(Layout.BREAK_STRATEGY_SIMPLE) .setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NONE) - .build(); - - // Report median of randomly generated PrecomputedText. - for (int i = 0; i < TRIAL_COUNT; ++i) { - memories[i] = PrecomputedText.create( - mTextUtil.nextRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT), param) - .getMemoryUsage(); + .build().getMemoryUsage(); } reportMemoryUsage(median(memories), "MemoryUsage_NoHyphenation"); } @@ -94,16 +91,13 @@ public class PrecomputedTextMemoryUsageTest { @Test public void testMemoryUsage_Hyphenation() { int[] memories = new int[TRIAL_COUNT]; - final PrecomputedText.Params param = new PrecomputedText.Params.Builder(PAINT) + // Report median of randomly generated MeasuredText. + for (int i = 0; i < TRIAL_COUNT; ++i) { + memories[i] = new MeasuredText.Builder( + mTextUtil.nextRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT), PAINT) .setBreakStrategy(Layout.BREAK_STRATEGY_BALANCED) .setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NORMAL) - .build(); - - // Report median of randomly generated PrecomputedText. - for (int i = 0; i < TRIAL_COUNT; ++i) { - memories[i] = PrecomputedText.create( - mTextUtil.nextRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT), param) - .getMemoryUsage(); + .build().getMemoryUsage(); } reportMemoryUsage(median(memories), "MemoryUsage_Hyphenation"); } @@ -111,16 +105,13 @@ public class PrecomputedTextMemoryUsageTest { @Test public void testMemoryUsage_NoHyphenation_WidthOnly() { int[] memories = new int[TRIAL_COUNT]; - final PrecomputedText.Params param = new PrecomputedText.Params.Builder(PAINT) + // Report median of randomly generated MeasuredText. + for (int i = 0; i < TRIAL_COUNT; ++i) { + memories[i] = new MeasuredText.Builder( + mTextUtil.nextRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT), PAINT) .setBreakStrategy(Layout.BREAK_STRATEGY_SIMPLE) .setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NONE) - .build(); - - // Report median of randomly generated PrecomputedText. - for (int i = 0; i < TRIAL_COUNT; ++i) { - CharSequence cs = mTextUtil.nextRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT); - memories[i] = PrecomputedText.createWidthOnly(cs, param, 0, cs.length()) - .getMemoryUsage(); + .build(false /* width only */).getMemoryUsage(); } reportMemoryUsage(median(memories), "MemoryUsage_NoHyphenation_WidthOnly"); } @@ -128,16 +119,13 @@ public class PrecomputedTextMemoryUsageTest { @Test public void testMemoryUsage_Hyphenatation_WidthOnly() { int[] memories = new int[TRIAL_COUNT]; - final PrecomputedText.Params param = new PrecomputedText.Params.Builder(PAINT) + // Report median of randomly generated MeasuredText. + for (int i = 0; i < TRIAL_COUNT; ++i) { + memories[i] = new MeasuredText.Builder( + mTextUtil.nextRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT), PAINT) .setBreakStrategy(Layout.BREAK_STRATEGY_BALANCED) .setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NORMAL) - .build(); - - // Report median of randomly generated PrecomputedText. - for (int i = 0; i < TRIAL_COUNT; ++i) { - CharSequence cs = mTextUtil.nextRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT); - memories[i] = PrecomputedText.createWidthOnly(cs, param, 0, cs.length()) - .getMemoryUsage(); + .build(false /* width only */).getMemoryUsage(); } reportMemoryUsage(median(memories), "MemoryUsage_Hyphenation_WidthOnly"); } diff --git a/apct-tests/perftests/core/src/android/text/PrecomputedTextPerfTest.java b/apct-tests/perftests/core/src/android/text/MeasuredTextPerfTest.java index 1cd0ae13069b..98f2bd5e5736 100644 --- a/apct-tests/perftests/core/src/android/text/PrecomputedTextPerfTest.java +++ b/apct-tests/perftests/core/src/android/text/MeasuredTextPerfTest.java @@ -42,7 +42,7 @@ import java.util.Random; @LargeTest @RunWith(AndroidJUnit4.class) -public class PrecomputedTextPerfTest { +public class MeasuredTextPerfTest { private static final int WORD_LENGTH = 9; // Random word has 9 characters. private static final int WORDS_IN_LINE = 8; // Roughly, 8 words in a line. private static final boolean NO_STYLE_TEXT = false; @@ -51,7 +51,7 @@ public class PrecomputedTextPerfTest { private static TextPaint PAINT = new TextPaint(); private static final int TEXT_WIDTH = WORDS_IN_LINE * WORD_LENGTH * (int) PAINT.getTextSize(); - public PrecomputedTextPerfTest() {} + public MeasuredTextPerfTest() {} @Rule public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter(); @@ -66,136 +66,120 @@ public class PrecomputedTextPerfTest { @Test public void testCreate_NoStyled_Hyphenation() { final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); - final PrecomputedText.Params param = new PrecomputedText.Params.Builder(PAINT) - .setBreakStrategy(Layout.BREAK_STRATEGY_BALANCED) - .setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NORMAL) - .build(); - while (state.keepRunning()) { state.pauseTiming(); final CharSequence text = mTextUtil.nextRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT); state.resumeTiming(); - PrecomputedText.create(text, param); + new MeasuredText.Builder(text, PAINT) + .setBreakStrategy(Layout.BREAK_STRATEGY_BALANCED) + .setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NORMAL) + .build(true /* do full layout */); } } @Test public void testCreate_NoStyled_NoHyphenation() { final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); - final PrecomputedText.Params param = new PrecomputedText.Params.Builder(PAINT) - .setBreakStrategy(Layout.BREAK_STRATEGY_SIMPLE) - .setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NONE) - .build(); - while (state.keepRunning()) { state.pauseTiming(); final CharSequence text = mTextUtil.nextRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT); state.resumeTiming(); - PrecomputedText.create(text, param); + new MeasuredText.Builder(text, PAINT) + .setBreakStrategy(Layout.BREAK_STRATEGY_SIMPLE) + .setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NONE) + .build(true /* do full layout */); } } @Test public void testCreate_NoStyled_Hyphenation_WidthOnly() { final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); - final PrecomputedText.Params param = new PrecomputedText.Params.Builder(PAINT) - .setBreakStrategy(Layout.BREAK_STRATEGY_BALANCED) - .setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NORMAL) - .build(); - while (state.keepRunning()) { state.pauseTiming(); final CharSequence text = mTextUtil.nextRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT); state.resumeTiming(); - PrecomputedText.create(text, param); + new MeasuredText.Builder(text, PAINT) + .setBreakStrategy(Layout.BREAK_STRATEGY_BALANCED) + .setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NORMAL) + .build(false /* width only */); } } @Test public void testCreate_NoStyled_NoHyphenation_WidthOnly() { final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); - final PrecomputedText.Params param = new PrecomputedText.Params.Builder(PAINT) - .setBreakStrategy(Layout.BREAK_STRATEGY_SIMPLE) - .setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NONE) - .build(); - while (state.keepRunning()) { state.pauseTiming(); final CharSequence text = mTextUtil.nextRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT); state.resumeTiming(); - PrecomputedText.create(text, param); + new MeasuredText.Builder(text, PAINT) + .setBreakStrategy(Layout.BREAK_STRATEGY_SIMPLE) + .setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NONE) + .build(false /* width only */); } } @Test public void testCreate_Styled_Hyphenation() { final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); - final PrecomputedText.Params param = new PrecomputedText.Params.Builder(PAINT) - .setBreakStrategy(Layout.BREAK_STRATEGY_BALANCED) - .setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NORMAL) - .build(); - while (state.keepRunning()) { state.pauseTiming(); final CharSequence text = mTextUtil.nextRandomParagraph(WORD_LENGTH, STYLE_TEXT); state.resumeTiming(); - PrecomputedText.create(text, param); + new MeasuredText.Builder(text, PAINT) + .setBreakStrategy(Layout.BREAK_STRATEGY_BALANCED) + .setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NORMAL) + .build(true /* do full layout */); } } @Test public void testCreate_Styled_NoHyphenation() { final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); - final PrecomputedText.Params param = new PrecomputedText.Params.Builder(PAINT) - .setBreakStrategy(Layout.BREAK_STRATEGY_SIMPLE) - .setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NONE) - .build(); - while (state.keepRunning()) { state.pauseTiming(); final CharSequence text = mTextUtil.nextRandomParagraph(WORD_LENGTH, STYLE_TEXT); state.resumeTiming(); - PrecomputedText.create(text, param); + new MeasuredText.Builder(text, PAINT) + .setBreakStrategy(Layout.BREAK_STRATEGY_SIMPLE) + .setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NONE) + .build(true /* do full layout */); } } @Test public void testCreate_Styled_Hyphenation_WidthOnly() { final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); - final PrecomputedText.Params param = new PrecomputedText.Params.Builder(PAINT) - .setBreakStrategy(Layout.BREAK_STRATEGY_BALANCED) - .setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NORMAL) - .build(); - while (state.keepRunning()) { state.pauseTiming(); final CharSequence text = mTextUtil.nextRandomParagraph(WORD_LENGTH, STYLE_TEXT); state.resumeTiming(); - PrecomputedText.create(text, param); + new MeasuredText.Builder(text, PAINT) + .setBreakStrategy(Layout.BREAK_STRATEGY_BALANCED) + .setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NORMAL) + .build(false /* width only */); } } @Test public void testCreate_Styled_NoHyphenation_WidthOnly() { final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); - final PrecomputedText.Params param = new PrecomputedText.Params.Builder(PAINT) - .setBreakStrategy(Layout.BREAK_STRATEGY_SIMPLE) - .setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NONE) - .build(); - while (state.keepRunning()) { state.pauseTiming(); final CharSequence text = mTextUtil.nextRandomParagraph(WORD_LENGTH, STYLE_TEXT); state.resumeTiming(); - PrecomputedText.create(text, param); + new MeasuredText.Builder(text, PAINT) + .setBreakStrategy(Layout.BREAK_STRATEGY_SIMPLE) + .setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NONE) + .build(false /* width only */); } } } diff --git a/apct-tests/perftests/core/src/android/text/StaticLayoutPerfTest.java b/apct-tests/perftests/core/src/android/text/StaticLayoutPerfTest.java index 8823af1df350..231aaf2ca074 100644 --- a/apct-tests/perftests/core/src/android/text/StaticLayoutPerfTest.java +++ b/apct-tests/perftests/core/src/android/text/StaticLayoutPerfTest.java @@ -63,18 +63,6 @@ public class StaticLayoutPerfTest { mTextUtil.resetRandom(0 /* seed */); } - private PrecomputedText makeMeasured(CharSequence text, TextPaint paint) { - PrecomputedText.Params param = new PrecomputedText.Params.Builder(paint).build(); - return PrecomputedText.create(text, param); - } - - private PrecomputedText makeMeasured(CharSequence text, TextPaint paint, int strategy, - int frequency) { - PrecomputedText.Params param = new PrecomputedText.Params.Builder(paint) - .setHyphenationFrequency(frequency).setBreakStrategy(strategy).build(); - return PrecomputedText.create(text, param); - } - @Test public void testCreate_FixedText_NoStyle_Greedy_NoHyphenation() { final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); @@ -163,16 +151,18 @@ public class StaticLayoutPerfTest { } @Test - public void testCreate_PrecomputedText_NoStyled_Greedy_NoHyphenation() { + public void testCreate_MeasuredText_NoStyled_Greedy_NoHyphenation() { final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); while (state.keepRunning()) { state.pauseTiming(); - final PrecomputedText text = makeMeasured( - mTextUtil.nextRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT), PAINT, - Layout.BREAK_STRATEGY_SIMPLE, Layout.HYPHENATION_FREQUENCY_NONE); + final MeasuredText text = new MeasuredText.Builder( + mTextUtil.nextRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT), PAINT) + .setBreakStrategy(Layout.BREAK_STRATEGY_SIMPLE) + .setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NONE) + .build(); state.resumeTiming(); - StaticLayout.Builder.obtain(text, 0, text.getText().length(), PAINT, TEXT_WIDTH) + StaticLayout.Builder.obtain(text, 0, text.length(), PAINT, TEXT_WIDTH) .setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NONE) .setBreakStrategy(Layout.BREAK_STRATEGY_SIMPLE) .build(); @@ -180,16 +170,18 @@ public class StaticLayoutPerfTest { } @Test - public void testCreate_PrecomputedText_NoStyled_Greedy_Hyphenation() { + public void testCreate_MeasuredText_NoStyled_Greedy_Hyphenation() { final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); while (state.keepRunning()) { state.pauseTiming(); - final PrecomputedText text = makeMeasured( - mTextUtil.nextRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT), PAINT, - Layout.BREAK_STRATEGY_SIMPLE, Layout.HYPHENATION_FREQUENCY_NORMAL); + final MeasuredText text = new MeasuredText.Builder( + mTextUtil.nextRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT), PAINT) + .setBreakStrategy(Layout.BREAK_STRATEGY_SIMPLE) + .setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NORMAL) + .build(); state.resumeTiming(); - StaticLayout.Builder.obtain(text, 0, text.getText().length(), PAINT, TEXT_WIDTH) + StaticLayout.Builder.obtain(text, 0, text.length(), PAINT, TEXT_WIDTH) .setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NORMAL) .setBreakStrategy(Layout.BREAK_STRATEGY_SIMPLE) .build(); @@ -197,16 +189,18 @@ public class StaticLayoutPerfTest { } @Test - public void testCreate_PrecomputedText_NoStyled_Balanced_NoHyphenation() { + public void testCreate_MeasuredText_NoStyled_Balanced_NoHyphenation() { final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); while (state.keepRunning()) { state.pauseTiming(); - final PrecomputedText text = makeMeasured( - mTextUtil.nextRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT), PAINT, - Layout.BREAK_STRATEGY_BALANCED, Layout.HYPHENATION_FREQUENCY_NONE); + final MeasuredText text = new MeasuredText.Builder( + mTextUtil.nextRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT), PAINT) + .setBreakStrategy(Layout.BREAK_STRATEGY_BALANCED) + .setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NONE) + .build(); state.resumeTiming(); - StaticLayout.Builder.obtain(text, 0, text.getText().length(), PAINT, TEXT_WIDTH) + StaticLayout.Builder.obtain(text, 0, text.length(), PAINT, TEXT_WIDTH) .setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NONE) .setBreakStrategy(Layout.BREAK_STRATEGY_BALANCED) .build(); @@ -214,16 +208,18 @@ public class StaticLayoutPerfTest { } @Test - public void testCreate_PrecomputedText_NoStyled_Balanced_Hyphenation() { + public void testCreate_MeasuredText_NoStyled_Balanced_Hyphenation() { final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); while (state.keepRunning()) { state.pauseTiming(); - final PrecomputedText text = makeMeasured( - mTextUtil.nextRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT), PAINT, - Layout.BREAK_STRATEGY_BALANCED, Layout.HYPHENATION_FREQUENCY_NORMAL); + final MeasuredText text = new MeasuredText.Builder( + mTextUtil.nextRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT), PAINT) + .setBreakStrategy(Layout.BREAK_STRATEGY_BALANCED) + .setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NORMAL) + .build(); state.resumeTiming(); - StaticLayout.Builder.obtain(text, 0, text.getText().length(), PAINT, TEXT_WIDTH) + StaticLayout.Builder.obtain(text, 0, text.length(), PAINT, TEXT_WIDTH) .setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NORMAL) .setBreakStrategy(Layout.BREAK_STRATEGY_BALANCED) .build(); @@ -231,16 +227,18 @@ public class StaticLayoutPerfTest { } @Test - public void testCreate_PrecomputedText_Styled_Greedy_NoHyphenation() { + public void testCreate_MeasuredText_Styled_Greedy_NoHyphenation() { final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); while (state.keepRunning()) { state.pauseTiming(); - final PrecomputedText text = makeMeasured( - mTextUtil.nextRandomParagraph(WORD_LENGTH, STYLE_TEXT), PAINT, - Layout.BREAK_STRATEGY_SIMPLE, Layout.HYPHENATION_FREQUENCY_NONE); + final MeasuredText text = new MeasuredText.Builder( + mTextUtil.nextRandomParagraph(WORD_LENGTH, STYLE_TEXT), PAINT) + .setBreakStrategy(Layout.BREAK_STRATEGY_SIMPLE) + .setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NONE) + .build(); state.resumeTiming(); - StaticLayout.Builder.obtain(text, 0, text.getText().length(), PAINT, TEXT_WIDTH) + StaticLayout.Builder.obtain(text, 0, text.length(), PAINT, TEXT_WIDTH) .setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NONE) .setBreakStrategy(Layout.BREAK_STRATEGY_SIMPLE) .build(); @@ -330,16 +328,15 @@ public class StaticLayoutPerfTest { } @Test - public void testDraw_PrecomputedText_Styled() { + public void testDraw_MeasuredText_Styled() { final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); final RenderNode node = RenderNode.create("benchmark", null); while (state.keepRunning()) { state.pauseTiming(); - final PrecomputedText text = makeMeasured( - mTextUtil.nextRandomParagraph(WORD_LENGTH, STYLE_TEXT), PAINT); + final MeasuredText text = new MeasuredText.Builder( + mTextUtil.nextRandomParagraph(WORD_LENGTH, STYLE_TEXT), PAINT).build(); final StaticLayout layout = - StaticLayout.Builder.obtain( - text, 0, text.getText().length(), PAINT, TEXT_WIDTH).build(); + StaticLayout.Builder.obtain(text, 0, text.length(), PAINT, TEXT_WIDTH).build(); final DisplayListCanvas c = node.start(1200, 200); state.resumeTiming(); @@ -348,16 +345,15 @@ public class StaticLayoutPerfTest { } @Test - public void testDraw_PrecomputedText_NoStyled() { + public void testDraw_MeasuredText_NoStyled() { final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); final RenderNode node = RenderNode.create("benchmark", null); while (state.keepRunning()) { state.pauseTiming(); - final PrecomputedText text = makeMeasured( - mTextUtil.nextRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT), PAINT); + final MeasuredText text = new MeasuredText.Builder( + mTextUtil.nextRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT), PAINT).build(); final StaticLayout layout = - StaticLayout.Builder.obtain( - text, 0, text.getText().length(), PAINT, TEXT_WIDTH).build(); + StaticLayout.Builder.obtain(text, 0, text.length(), PAINT, TEXT_WIDTH).build(); final DisplayListCanvas c = node.start(1200, 200); state.resumeTiming(); @@ -366,16 +362,15 @@ public class StaticLayoutPerfTest { } @Test - public void testDraw_PrecomputedText_Styled_WithoutCache() { + public void testDraw_MeasuredText_Styled_WithoutCache() { final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); final RenderNode node = RenderNode.create("benchmark", null); while (state.keepRunning()) { state.pauseTiming(); - final PrecomputedText text = makeMeasured( - mTextUtil.nextRandomParagraph(WORD_LENGTH, STYLE_TEXT), PAINT); + final MeasuredText text = new MeasuredText.Builder( + mTextUtil.nextRandomParagraph(WORD_LENGTH, STYLE_TEXT), PAINT).build(); final StaticLayout layout = - StaticLayout.Builder.obtain( - text, 0, text.getText().length(), PAINT, TEXT_WIDTH).build(); + StaticLayout.Builder.obtain(text, 0, text.length(), PAINT, TEXT_WIDTH).build(); final DisplayListCanvas c = node.start(1200, 200); Canvas.freeTextLayoutCaches(); state.resumeTiming(); @@ -385,16 +380,15 @@ public class StaticLayoutPerfTest { } @Test - public void testDraw_PrecomputedText_NoStyled_WithoutCache() { + public void testDraw_MeasuredText_NoStyled_WithoutCache() { final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); final RenderNode node = RenderNode.create("benchmark", null); while (state.keepRunning()) { state.pauseTiming(); - final PrecomputedText text = makeMeasured( - mTextUtil.nextRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT), PAINT); + final MeasuredText text = new MeasuredText.Builder( + mTextUtil.nextRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT), PAINT).build(); final StaticLayout layout = - StaticLayout.Builder.obtain( - text, 0, text.getText().length(), PAINT, TEXT_WIDTH).build(); + StaticLayout.Builder.obtain(text, 0, text.length(), PAINT, TEXT_WIDTH).build(); final DisplayListCanvas c = node.start(1200, 200); Canvas.freeTextLayoutCaches(); state.resumeTiming(); diff --git a/api/current.txt b/api/current.txt index 7a9108e592a1..3963f3ac0c43 100644 --- a/api/current.txt +++ b/api/current.txt @@ -13839,7 +13839,6 @@ package android.graphics { method public int breakText(java.lang.String, boolean, float, float[]); method public void clearShadowLayer(); method public float descent(); - method public boolean equalsForTextMeasurement(android.graphics.Paint); method public int getAlpha(); method public int getColor(); method public android.graphics.ColorFilter getColorFilter(); @@ -43229,6 +43228,36 @@ package android.text { method public boolean isAllowed(char); } + public class MeasuredText implements android.text.Spanned { + method public char charAt(int); + method public int getBreakStrategy(); + method public int getEnd(); + method public int getHyphenationFrequency(); + method public android.text.TextPaint getPaint(); + method public int getParagraphCount(); + method public int getParagraphEnd(int); + method public int getParagraphStart(int); + method public int getSpanEnd(java.lang.Object); + method public int getSpanFlags(java.lang.Object); + method public int getSpanStart(java.lang.Object); + method public <T> T[] getSpans(int, int, java.lang.Class<T>); + method public int getStart(); + method public java.lang.CharSequence getText(); + method public android.text.TextDirectionHeuristic getTextDir(); + method public int length(); + method public int nextSpanTransition(int, int, java.lang.Class); + method public java.lang.CharSequence subSequence(int, int); + } + + public static final class MeasuredText.Builder { + ctor public MeasuredText.Builder(java.lang.CharSequence, android.text.TextPaint); + method public android.text.MeasuredText build(); + method public android.text.MeasuredText.Builder setBreakStrategy(int); + method public android.text.MeasuredText.Builder setHyphenationFrequency(int); + method public android.text.MeasuredText.Builder setRange(int, int); + method public android.text.MeasuredText.Builder setTextDirection(android.text.TextDirectionHeuristic); + } + public abstract interface NoCopySpan { } @@ -43240,31 +43269,6 @@ package android.text { method public abstract int getSpanTypeId(); } - public class PrecomputedText { - method public static android.text.PrecomputedText create(java.lang.CharSequence, android.text.PrecomputedText.Params); - method public int getParagraphCount(); - method public int getParagraphEnd(int); - method public int getParagraphStart(int); - method public android.text.PrecomputedText.Params getParams(); - method public java.lang.CharSequence getText(); - } - - public static class PrecomputedText.Params { - method public int getBreakStrategy(); - method public int getHyphenationFrequency(); - method public android.text.TextDirectionHeuristic getTextDirection(); - method public android.text.TextPaint getTextPaint(); - method public boolean sameTextMetrics(android.text.PrecomputedText.Params); - } - - public static class PrecomputedText.Params.Builder { - ctor public PrecomputedText.Params.Builder(android.text.TextPaint); - method public android.text.PrecomputedText.Params build(); - method public android.text.PrecomputedText.Params.Builder setBreakStrategy(int); - method public android.text.PrecomputedText.Params.Builder setHyphenationFrequency(int); - method public android.text.PrecomputedText.Params.Builder setTextDirection(android.text.TextDirectionHeuristic); - } - public class Selection { method public static boolean extendDown(android.text.Spannable, android.text.Layout); method public static boolean extendLeft(android.text.Spannable, android.text.Layout); @@ -43396,7 +43400,6 @@ package android.text { public static final class StaticLayout.Builder { method public android.text.StaticLayout build(); - method public static android.text.StaticLayout.Builder obtain(android.text.PrecomputedText, int, int, android.text.TextPaint, int); method public static android.text.StaticLayout.Builder obtain(java.lang.CharSequence, int, int, android.text.TextPaint, int); method public android.text.StaticLayout.Builder setAlignment(android.text.Layout.Alignment); method public android.text.StaticLayout.Builder setBreakStrategy(int); @@ -53648,7 +53651,6 @@ package android.widget { method public final android.content.res.ColorStateList getTextColors(); method public java.util.Locale getTextLocale(); method public android.os.LocaleList getTextLocales(); - method public android.text.PrecomputedText.Params getTextMetricsParams(); method public float getTextScaleX(); method public float getTextSize(); method public int getTotalPaddingBottom(); @@ -53754,8 +53756,6 @@ package android.widget { method public final void setMovementMethod(android.text.method.MovementMethod); method public void setOnEditorActionListener(android.widget.TextView.OnEditorActionListener); method public void setPaintFlags(int); - method public void setPrecomputedTextAndParams(android.text.PrecomputedText); - method public void setPrecomputedTextOrThrow(android.text.PrecomputedText); method public void setPrivateImeOptions(java.lang.String); method public void setRawInputType(int); method public void setScroller(android.widget.Scroller); @@ -53780,7 +53780,6 @@ package android.widget { method public final void setTextKeepState(java.lang.CharSequence, android.widget.TextView.BufferType); method public void setTextLocale(java.util.Locale); method public void setTextLocales(android.os.LocaleList); - method public void setTextMetricsParams(android.text.PrecomputedText.Params); method public void setTextScaleX(float); method public void setTextSize(float); method public void setTextSize(int, float); diff --git a/cmds/statsd/src/metrics/CountMetricProducer.cpp b/cmds/statsd/src/metrics/CountMetricProducer.cpp index 178db1ae5e5f..af2e362368c3 100644 --- a/cmds/statsd/src/metrics/CountMetricProducer.cpp +++ b/cmds/statsd/src/metrics/CountMetricProducer.cpp @@ -63,7 +63,8 @@ CountMetricProducer::CountMetricProducer(const ConfigKey& key, const CountMetric : MetricProducer(metric.id(), key, startTimeNs, conditionIndex, wizard) { // TODO: evaluate initial conditions. and set mConditionMet. if (metric.has_bucket()) { - mBucketSizeNs = TimeUnitToBucketSizeInMillis(metric.bucket()) * 1000000; + mBucketSizeNs = + TimeUnitToBucketSizeInMillisGuardrailed(key.GetUid(), metric.bucket()) * 1000000; } else { mBucketSizeNs = LLONG_MAX; } diff --git a/cmds/statsd/src/metrics/DurationMetricProducer.cpp b/cmds/statsd/src/metrics/DurationMetricProducer.cpp index 67d95dbd4cc5..3b7936dee497 100644 --- a/cmds/statsd/src/metrics/DurationMetricProducer.cpp +++ b/cmds/statsd/src/metrics/DurationMetricProducer.cpp @@ -72,7 +72,8 @@ DurationMetricProducer::DurationMetricProducer(const ConfigKey& key, const Durat // them in the base class, because the proto generated CountMetric, and DurationMetric are // not related. Maybe we should add a template in the future?? if (metric.has_bucket()) { - mBucketSizeNs = TimeUnitToBucketSizeInMillis(metric.bucket()) * 1000000; + mBucketSizeNs = + TimeUnitToBucketSizeInMillisGuardrailed(key.GetUid(), metric.bucket()) * 1000000; } else { mBucketSizeNs = LLONG_MAX; } diff --git a/cmds/statsd/src/metrics/GaugeMetricProducer.cpp b/cmds/statsd/src/metrics/GaugeMetricProducer.cpp index 8aa816938c0d..0daa506ba42d 100644 --- a/cmds/statsd/src/metrics/GaugeMetricProducer.cpp +++ b/cmds/statsd/src/metrics/GaugeMetricProducer.cpp @@ -69,7 +69,7 @@ GaugeMetricProducer::GaugeMetricProducer(const ConfigKey& key, const GaugeMetric mCurrentSlicedBucketForAnomaly = std::make_shared<DimToValMap>(); int64_t bucketSizeMills = 0; if (metric.has_bucket()) { - bucketSizeMills = TimeUnitToBucketSizeInMillis(metric.bucket()); + bucketSizeMills = TimeUnitToBucketSizeInMillisGuardrailed(key.GetUid(), metric.bucket()); } else { bucketSizeMills = TimeUnitToBucketSizeInMillis(ONE_HOUR); } diff --git a/cmds/statsd/src/metrics/MetricProducer.h b/cmds/statsd/src/metrics/MetricProducer.h index e8f8299abd89..574c59f740bd 100644 --- a/cmds/statsd/src/metrics/MetricProducer.h +++ b/cmds/statsd/src/metrics/MetricProducer.h @@ -137,6 +137,11 @@ public: return mBucketSizeNs; } + // Only needed for unit-testing to override guardrail. + void setBucketSize(int64_t bucketSize) { + mBucketSizeNs = bucketSize; + } + inline const int64_t& getMetricId() { return mMetricId; } diff --git a/cmds/statsd/src/metrics/ValueMetricProducer.cpp b/cmds/statsd/src/metrics/ValueMetricProducer.cpp index cbca884ae6f3..35fcdc48a08a 100644 --- a/cmds/statsd/src/metrics/ValueMetricProducer.cpp +++ b/cmds/statsd/src/metrics/ValueMetricProducer.cpp @@ -72,7 +72,7 @@ ValueMetricProducer::ValueMetricProducer(const ConfigKey& key, const ValueMetric // TODO: valuemetric for pushed events may need unlimited bucket length int64_t bucketSizeMills = 0; if (metric.has_bucket()) { - bucketSizeMills = TimeUnitToBucketSizeInMillis(metric.bucket()); + bucketSizeMills = TimeUnitToBucketSizeInMillisGuardrailed(key.GetUid(), metric.bucket()); } else { bucketSizeMills = TimeUnitToBucketSizeInMillis(ONE_HOUR); } diff --git a/cmds/statsd/src/stats_log_util.cpp b/cmds/statsd/src/stats_log_util.cpp index f7b768f5f2b2..30eef4fef166 100644 --- a/cmds/statsd/src/stats_log_util.cpp +++ b/cmds/statsd/src/stats_log_util.cpp @@ -17,6 +17,7 @@ #include "stats_log_util.h" #include <logd/LogEvent.h> +#include <private/android_filesystem_config.h> #include <utils/Log.h> #include <set> #include <stack> @@ -216,6 +217,14 @@ void writeFieldValueTreeToStream(int tagId, const std::vector<FieldValue>& value protoOutput->end(atomToken); } +int64_t TimeUnitToBucketSizeInMillisGuardrailed(int uid, TimeUnit unit) { + int64_t bucketSizeMillis = TimeUnitToBucketSizeInMillis(unit); + if (bucketSizeMillis > 1000 && bucketSizeMillis < 5 * 60 * 1000LL && uid != AID_SHELL) { + bucketSizeMillis = 5 * 60 * 1000LL; + } + return bucketSizeMillis; +} + int64_t TimeUnitToBucketSizeInMillis(TimeUnit unit) { switch (unit) { case ONE_MINUTE: @@ -283,4 +292,4 @@ int64_t getWallClockMillis() { } // namespace statsd } // namespace os -} // namespace android
\ No newline at end of file +} // namespace android diff --git a/cmds/statsd/src/stats_log_util.h b/cmds/statsd/src/stats_log_util.h index 32fe0b8370b8..6a5123d8c844 100644 --- a/cmds/statsd/src/stats_log_util.h +++ b/cmds/statsd/src/stats_log_util.h @@ -32,6 +32,10 @@ void writeFieldValueTreeToStream(int tagId, const std::vector<FieldValue>& value void writeDimensionToProto(const HashableDimensionKey& dimension, util::ProtoOutputStream* protoOutput); +// Convert the TimeUnit enum to the bucket size in millis with a guardrail on +// bucket size. +int64_t TimeUnitToBucketSizeInMillisGuardrailed(int uid, TimeUnit unit); + // Convert the TimeUnit enum to the bucket size in millis. int64_t TimeUnitToBucketSizeInMillis(TimeUnit unit); @@ -71,4 +75,4 @@ bool parseProtoOutputStream(util::ProtoOutputStream& protoOutput, T* message) { } // namespace statsd } // namespace os -} // namespace android
\ No newline at end of file +} // namespace android diff --git a/cmds/statsd/tests/e2e/Attribution_e2e_test.cpp b/cmds/statsd/tests/e2e/Attribution_e2e_test.cpp index 93cd5875c52b..022800427b11 100644 --- a/cmds/statsd/tests/e2e/Attribution_e2e_test.cpp +++ b/cmds/statsd/tests/e2e/Attribution_e2e_test.cpp @@ -47,7 +47,7 @@ StatsdConfig CreateStatsdConfig() { *countMetric->mutable_dimensions_in_what() = CreateAttributionUidAndTagDimensions( android::util::WAKELOCK_STATE_CHANGED, {Position::FIRST}); - countMetric->set_bucket(ONE_MINUTE); + countMetric->set_bucket(FIVE_MINUTES); return config; } @@ -206,4 +206,4 @@ GTEST_LOG_(INFO) << "This test does nothing.\n"; } // namespace statsd } // namespace os -} // namespace android
\ No newline at end of file +} // namespace android diff --git a/cmds/statsd/tests/e2e/DimensionInCondition_e2e_test.cpp b/cmds/statsd/tests/e2e/DimensionInCondition_e2e_test.cpp index 293f579ac848..4dffd13bef66 100644 --- a/cmds/statsd/tests/e2e/DimensionInCondition_e2e_test.cpp +++ b/cmds/statsd/tests/e2e/DimensionInCondition_e2e_test.cpp @@ -61,7 +61,7 @@ StatsdConfig CreateCountMetricWithNoLinkConfig() { CreateDimensions(android::util::SCREEN_BRIGHTNESS_CHANGED, {1 /* level */}); *metric->mutable_dimensions_in_condition() = CreateAttributionUidDimensions( android::util::WAKELOCK_STATE_CHANGED, {Position::FIRST}); - metric->set_bucket(ONE_MINUTE); + metric->set_bucket(FIVE_MINUTES); return config; } @@ -252,7 +252,7 @@ StatsdConfig CreateCountMetricWithLinkConfig() { addPredicateToPredicateCombination(isSyncingPredicate, combinationPredicate); auto metric = config.add_count_metric(); - metric->set_bucket(ONE_MINUTE); + metric->set_bucket(FIVE_MINUTES); metric->set_id(StringToId("AppCrashMetric")); metric->set_what(appCrashMatcher.id()); metric->set_condition(combinationPredicate->id()); @@ -441,7 +441,7 @@ StatsdConfig CreateDurationMetricConfigNoLink(DurationMetric::AggregationType ag addPredicateToPredicateCombination(isSyncingPredicate, combinationPredicate); auto metric = config.add_duration_metric(); - metric->set_bucket(ONE_MINUTE); + metric->set_bucket(FIVE_MINUTES); metric->set_id(StringToId("BatterySaverModeDurationMetric")); metric->set_what(inBatterySaverModePredicate.id()); metric->set_condition(combinationPredicate->id()); @@ -595,7 +595,7 @@ StatsdConfig CreateDurationMetricConfigWithLink(DurationMetric::AggregationType addPredicateToPredicateCombination(isSyncingPredicate, combinationPredicate); auto metric = config.add_duration_metric(); - metric->set_bucket(ONE_MINUTE); + metric->set_bucket(FIVE_MINUTES); metric->set_id(StringToId("AppInBackgroundMetric")); metric->set_what(isInBackgroundPredicate.id()); metric->set_condition(combinationPredicate->id()); @@ -730,4 +730,4 @@ GTEST_LOG_(INFO) << "This test does nothing.\n"; } // namespace statsd } // namespace os -} // namespace android
\ No newline at end of file +} // namespace android diff --git a/cmds/statsd/tests/e2e/GaugeMetric_e2e_test.cpp b/cmds/statsd/tests/e2e/GaugeMetric_e2e_test.cpp index 6aa7dd64393e..3843e0a3c67d 100644 --- a/cmds/statsd/tests/e2e/GaugeMetric_e2e_test.cpp +++ b/cmds/statsd/tests/e2e/GaugeMetric_e2e_test.cpp @@ -53,7 +53,7 @@ StatsdConfig CreateStatsdConfigForPushedEvent() { fieldMatcher->add_child()->set_field(7); // activity_start_msec(int64) *gaugeMetric->mutable_dimensions_in_what() = CreateDimensions(android::util::APP_START_CHANGED, {1 /* uid field */ }); - gaugeMetric->set_bucket(ONE_MINUTE); + gaugeMetric->set_bucket(FIVE_MINUTES); auto links = gaugeMetric->add_links(); links->set_condition(isInBackgroundPredicate.id()); @@ -198,4 +198,4 @@ GTEST_LOG_(INFO) << "This test does nothing.\n"; } // namespace statsd } // namespace os -} // namespace android
\ No newline at end of file +} // namespace android diff --git a/cmds/statsd/tests/e2e/MetricConditionLink_e2e_test.cpp b/cmds/statsd/tests/e2e/MetricConditionLink_e2e_test.cpp index d00518147bfe..1b51780a59bc 100644 --- a/cmds/statsd/tests/e2e/MetricConditionLink_e2e_test.cpp +++ b/cmds/statsd/tests/e2e/MetricConditionLink_e2e_test.cpp @@ -71,7 +71,7 @@ StatsdConfig CreateStatsdConfig() { // The metric is dimensioning by uid only. *countMetric->mutable_dimensions_in_what() = CreateDimensions(android::util::PROCESS_LIFE_CYCLE_STATE_CHANGED, {1}); - countMetric->set_bucket(ONE_MINUTE); + countMetric->set_bucket(FIVE_MINUTES); // Links between crash atom and condition of app is in syncing. auto links = countMetric->add_links(); @@ -337,4 +337,4 @@ GTEST_LOG_(INFO) << "This test does nothing.\n"; } // namespace statsd } // namespace os -} // namespace android
\ No newline at end of file +} // namespace android diff --git a/cmds/statsd/tests/e2e/WakelockDuration_e2e_test.cpp b/cmds/statsd/tests/e2e/WakelockDuration_e2e_test.cpp index 024fa3e23180..efdab9822984 100644 --- a/cmds/statsd/tests/e2e/WakelockDuration_e2e_test.cpp +++ b/cmds/statsd/tests/e2e/WakelockDuration_e2e_test.cpp @@ -56,7 +56,7 @@ StatsdConfig CreateStatsdConfig(DurationMetric::AggregationType aggregationType) *durationMetric->mutable_dimensions_in_what() = CreateAttributionUidDimensions( android::util::WAKELOCK_STATE_CHANGED, {Position::FIRST}); - durationMetric->set_bucket(ONE_MINUTE); + durationMetric->set_bucket(FIVE_MINUTES); return config; } @@ -327,4 +327,4 @@ GTEST_LOG_(INFO) << "This test does nothing.\n"; } // namespace statsd } // namespace os -} // namespace android
\ No newline at end of file +} // namespace android diff --git a/cmds/statsd/tests/metrics/CountMetricProducer_test.cpp b/cmds/statsd/tests/metrics/CountMetricProducer_test.cpp index d9dbf1d7cadc..20ddbe9f0e38 100644 --- a/cmds/statsd/tests/metrics/CountMetricProducer_test.cpp +++ b/cmds/statsd/tests/metrics/CountMetricProducer_test.cpp @@ -56,6 +56,7 @@ TEST(CountMetricProducerTest, TestNonDimensionalEvents) { CountMetricProducer countProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, wizard, bucketStartTimeNs); + countProducer.setBucketSize(60 * NS_PER_SEC); // 2 events in bucket 1. countProducer.onMatchedLogEvent(1 /*log matcher index*/, event1); @@ -118,6 +119,7 @@ TEST(CountMetricProducerTest, TestEventsWithNonSlicedCondition) { sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>(); CountMetricProducer countProducer(kConfigKey, metric, 1, wizard, bucketStartTimeNs); + countProducer.setBucketSize(60 * NS_PER_SEC); countProducer.onConditionChanged(true, bucketStartTimeNs); countProducer.onMatchedLogEvent(1 /*matcher index*/, event1); @@ -179,6 +181,7 @@ TEST(CountMetricProducerTest, TestEventsWithSlicedCondition) { CountMetricProducer countProducer(kConfigKey, metric, 1 /*condition tracker index*/, wizard, bucketStartTimeNs); + countProducer.setBucketSize(60 * NS_PER_SEC); countProducer.onMatchedLogEvent(1 /*log matcher index*/, event1); countProducer.flushIfNeededLocked(bucketStartTimeNs + 1); @@ -217,6 +220,8 @@ TEST(CountMetricProducerTest, TestEventWithAppUpgrade) { sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>(); CountMetricProducer countProducer(kConfigKey, metric, -1 /* no condition */, wizard, bucketStartTimeNs); + countProducer.setBucketSize(60 * NS_PER_SEC); + sp<AnomalyTracker> anomalyTracker = countProducer.addAnomalyTracker(alert); EXPECT_TRUE(anomalyTracker != nullptr); @@ -274,6 +279,7 @@ TEST(CountMetricProducerTest, TestEventWithAppUpgradeInNextBucket) { sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>(); CountMetricProducer countProducer(kConfigKey, metric, -1 /* no condition */, wizard, bucketStartTimeNs); + countProducer.setBucketSize(60 * NS_PER_SEC); // Bucket is flushed yet. countProducer.onMatchedLogEvent(1 /*log matcher index*/, event1); @@ -329,6 +335,8 @@ TEST(CountMetricProducerTest, TestAnomalyDetectionUnSliced) { sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>(); CountMetricProducer countProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, wizard, bucketStartTimeNs); + countProducer.setBucketSize(60 * NS_PER_SEC); + sp<AnomalyTracker> anomalyTracker = countProducer.addAnomalyTracker(alert); int tagId = 1; diff --git a/cmds/statsd/tests/metrics/DurationMetricProducer_test.cpp b/cmds/statsd/tests/metrics/DurationMetricProducer_test.cpp index 57e2794c0eea..79695967a6dd 100644 --- a/cmds/statsd/tests/metrics/DurationMetricProducer_test.cpp +++ b/cmds/statsd/tests/metrics/DurationMetricProducer_test.cpp @@ -59,6 +59,7 @@ TEST(DurationMetricTrackerTest, TestNoCondition) { DurationMetricProducer durationProducer( kConfigKey, metric, -1 /*no condition*/, 1 /* start index */, 2 /* stop index */, 3 /* stop_all index */, false /*nesting*/, wizard, dimensions, bucketStartTimeNs); + durationProducer.setBucketSize(60 * NS_PER_SEC); durationProducer.onMatchedLogEvent(1 /* start index*/, event1); durationProducer.onMatchedLogEvent(2 /* stop index*/, event2); @@ -100,6 +101,8 @@ TEST(DurationMetricTrackerTest, TestNonSlicedCondition) { DurationMetricProducer durationProducer( kConfigKey, metric, 0 /* condition index */, 1 /* start index */, 2 /* stop index */, 3 /* stop_all index */, false /*nesting*/, wizard, dimensions, bucketStartTimeNs); + durationProducer.setBucketSize(60 * NS_PER_SEC); + EXPECT_FALSE(durationProducer.mCondition); EXPECT_FALSE(durationProducer.isConditionSliced()); @@ -149,6 +152,7 @@ TEST(DurationMetricTrackerTest, TestSumDurationWithUpgrade) { DurationMetricProducer durationProducer( kConfigKey, metric, -1 /* no condition */, 1 /* start index */, 2 /* stop index */, 3 /* stop_all index */, false /*nesting*/, wizard, dimensions, bucketStartTimeNs); + durationProducer.setBucketSize(60 * NS_PER_SEC); LogEvent start_event(tagId, startTimeNs); start_event.init(); @@ -203,6 +207,7 @@ TEST(DurationMetricTrackerTest, TestSumDurationWithUpgradeInFollowingBucket) { DurationMetricProducer durationProducer( kConfigKey, metric, -1 /* no condition */, 1 /* start index */, 2 /* stop index */, 3 /* stop_all index */, false /*nesting*/, wizard, dimensions, bucketStartTimeNs); + durationProducer.setBucketSize(60 * NS_PER_SEC); LogEvent start_event(tagId, startTimeNs); start_event.init(); @@ -256,6 +261,8 @@ TEST(DurationMetricTrackerTest, TestSumDurationAnomalyWithUpgrade) { DurationMetricProducer durationProducer( kConfigKey, metric, -1 /* no condition */, 1 /* start index */, 2 /* stop index */, 3 /* stop_all index */, false /*nesting*/, wizard, dimensions, bucketStartTimeNs); + durationProducer.setBucketSize(60 * NS_PER_SEC); + sp<AnomalyTracker> anomalyTracker = durationProducer.addAnomalyTracker(alert); EXPECT_TRUE(anomalyTracker != nullptr); @@ -293,6 +300,7 @@ TEST(DurationMetricTrackerTest, TestMaxDurationWithUpgrade) { DurationMetricProducer durationProducer( kConfigKey, metric, -1 /* no condition */, 1 /* start index */, 2 /* stop index */, 3 /* stop_all index */, false /*nesting*/, wizard, dimensions, bucketStartTimeNs); + durationProducer.setBucketSize(60 * NS_PER_SEC); LogEvent start_event(tagId, startTimeNs); start_event.init(); @@ -340,6 +348,7 @@ TEST(DurationMetricTrackerTest, TestMaxDurationWithUpgradeInNextBucket) { DurationMetricProducer durationProducer( kConfigKey, metric, -1 /* no condition */, 1 /* start index */, 2 /* stop index */, 3 /* stop_all index */, false /*nesting*/, wizard, dimensions, bucketStartTimeNs); + durationProducer.setBucketSize(60 * NS_PER_SEC); LogEvent start_event(tagId, startTimeNs); start_event.init(); diff --git a/cmds/statsd/tests/metrics/GaugeMetricProducer_test.cpp b/cmds/statsd/tests/metrics/GaugeMetricProducer_test.cpp index 8b4273bc7929..0eb8ce2603bd 100644 --- a/cmds/statsd/tests/metrics/GaugeMetricProducer_test.cpp +++ b/cmds/statsd/tests/metrics/GaugeMetricProducer_test.cpp @@ -67,6 +67,7 @@ TEST(GaugeMetricProducerTest, TestNoCondition) { GaugeMetricProducer gaugeProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, wizard, tagId, bucketStartTimeNs, pullerManager); + gaugeProducer.setBucketSize(60 * NS_PER_SEC); vector<shared_ptr<LogEvent>> allData; allData.clear(); @@ -144,6 +145,7 @@ TEST(GaugeMetricProducerTest, TestPushedEventsWithUpgrade) { GaugeMetricProducer gaugeProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, wizard, -1 /* -1 means no pulling */, bucketStartTimeNs, pullerManager); + gaugeProducer.setBucketSize(60 * NS_PER_SEC); sp<AnomalyTracker> anomalyTracker = gaugeProducer.addAnomalyTracker(alert); EXPECT_TRUE(anomalyTracker != nullptr); @@ -225,6 +227,7 @@ TEST(GaugeMetricProducerTest, TestPulledWithUpgrade) { GaugeMetricProducer gaugeProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, wizard, tagId, bucketStartTimeNs, pullerManager); + gaugeProducer.setBucketSize(60 * NS_PER_SEC); vector<shared_ptr<LogEvent>> allData; shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucketStartTimeNs + 1); @@ -292,6 +295,7 @@ TEST(GaugeMetricProducerTest, TestWithCondition) { GaugeMetricProducer gaugeProducer(kConfigKey, metric, 1, wizard, tagId, bucketStartTimeNs, pullerManager); + gaugeProducer.setBucketSize(60 * NS_PER_SEC); gaugeProducer.onConditionChanged(true, bucketStartTimeNs + 8); EXPECT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->size()); @@ -350,6 +354,7 @@ TEST(GaugeMetricProducerTest, TestAnomalyDetection) { gaugeFieldMatcher->add_child()->set_field(2); GaugeMetricProducer gaugeProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, wizard, tagId, bucketStartTimeNs, pullerManager); + gaugeProducer.setBucketSize(60 * NS_PER_SEC); Alert alert; alert.set_id(101); diff --git a/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp b/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp index 6e66c6e8dcb6..ce4fa3278493 100644 --- a/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp +++ b/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp @@ -66,6 +66,7 @@ TEST(ValueMetricProducerTest, TestNonDimensionalEvents) { ValueMetricProducer valueProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, wizard, tagId, bucketStartTimeNs, pullerManager); + valueProducer.setBucketSize(60 * NS_PER_SEC); vector<shared_ptr<LogEvent>> allData; allData.clear(); @@ -79,6 +80,8 @@ TEST(ValueMetricProducerTest, TestNonDimensionalEvents) { // has one slice EXPECT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); ValueMetricProducer::Interval curInterval = valueProducer.mCurrentSlicedBucket.begin()->second; + valueProducer.setBucketSize(60 * NS_PER_SEC); + // startUpdated:true tainted:0 sum:0 start:11 EXPECT_EQ(true, curInterval.startUpdated); EXPECT_EQ(0, curInterval.tainted); @@ -162,7 +165,7 @@ TEST(ValueMetricProducerTest, TestEventsWithNonSlicedCondition) { ValueMetricProducer valueProducer(kConfigKey, metric, 1, wizard, tagId, bucketStartTimeNs, pullerManager); - + valueProducer.setBucketSize(60 * NS_PER_SEC); valueProducer.onConditionChanged(true, bucketStartTimeNs + 8); // has one slice @@ -215,6 +218,7 @@ TEST(ValueMetricProducerTest, TestPushedEventsWithUpgrade) { make_shared<StrictMock<MockStatsPullerManager>>(); ValueMetricProducer valueProducer(kConfigKey, metric, -1, wizard, -1, bucketStartTimeNs, pullerManager); + valueProducer.setBucketSize(60 * NS_PER_SEC); shared_ptr<LogEvent> event1 = make_shared<LogEvent>(tagId, bucketStartTimeNs + 10); event1->write(1); @@ -269,6 +273,7 @@ TEST(ValueMetricProducerTest, TestPulledValueWithUpgrade) { })); ValueMetricProducer valueProducer(kConfigKey, metric, -1, wizard, tagId, bucketStartTimeNs, pullerManager); + valueProducer.setBucketSize(60 * NS_PER_SEC); vector<shared_ptr<LogEvent>> allData; allData.clear(); @@ -311,6 +316,7 @@ TEST(ValueMetricProducerTest, TestPushedEventsWithoutCondition) { ValueMetricProducer valueProducer(kConfigKey, metric, -1, wizard, -1, bucketStartTimeNs, pullerManager); + valueProducer.setBucketSize(60 * NS_PER_SEC); shared_ptr<LogEvent> event1 = make_shared<LogEvent>(tagId, bucketStartTimeNs + 10); event1->write(1); @@ -357,6 +363,8 @@ TEST(ValueMetricProducerTest, TestAnomalyDetection) { sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>(); ValueMetricProducer valueProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, wizard, -1 /*not pulled*/, bucketStartTimeNs); + valueProducer.setBucketSize(60 * NS_PER_SEC); + sp<AnomalyTracker> anomalyTracker = valueProducer.addAnomalyTracker(alert); diff --git a/core/java/android/app/ActivityOptions.java b/core/java/android/app/ActivityOptions.java index fee58274a5fc..d5430f05d65b 100644 --- a/core/java/android/app/ActivityOptions.java +++ b/core/java/android/app/ActivityOptions.java @@ -1106,6 +1106,11 @@ public class ActivityOptions { } /** @hide */ + public void setRemoteAnimationAdapter(RemoteAnimationAdapter remoteAnimationAdapter) { + mRemoteAnimationAdapter = remoteAnimationAdapter; + } + + /** @hide */ public static ActivityOptions fromBundle(Bundle bOptions) { return bOptions != null ? new ActivityOptions(bOptions) : null; } diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl index 02be00268a45..60dccbc176d3 100644 --- a/core/java/android/app/IActivityManager.aidl +++ b/core/java/android/app/IActivityManager.aidl @@ -68,6 +68,7 @@ import android.os.WorkSource; import android.service.voice.IVoiceInteractionSession; import android.view.IRecentsAnimationRunner; import android.view.RemoteAnimationDefinition; +import android.view.RemoteAnimationAdapter; import com.android.internal.app.IVoiceInteractor; import com.android.internal.os.IResultReceiver; import com.android.internal.policy.IKeyguardDismissCallback; @@ -696,4 +697,11 @@ interface IActivityManager { * Registers remote animations for a specific activity. */ void registerRemoteAnimations(in IBinder token, in RemoteAnimationDefinition definition); + + /** + * Registers a remote animation to be run for all activity starts from a certain package during + * a short predefined amount of time. + */ + void registerRemoteAnimationForNextActivityStart(in String packageName, + in RemoteAnimationAdapter adapter); } diff --git a/core/java/android/app/servertransaction/ActivityLifecycleItem.java b/core/java/android/app/servertransaction/ActivityLifecycleItem.java index 9a50a009ce34..7f8c50cd4ce5 100644 --- a/core/java/android/app/servertransaction/ActivityLifecycleItem.java +++ b/core/java/android/app/servertransaction/ActivityLifecycleItem.java @@ -91,4 +91,9 @@ public abstract class ActivityLifecycleItem extends ClientTransactionItem { pw.println(prefix + "target state:" + getTargetState()); pw.println(prefix + "description: " + mDescription); } + + @Override + public void recycle() { + setDescription(null); + } } diff --git a/core/java/android/app/servertransaction/DestroyActivityItem.java b/core/java/android/app/servertransaction/DestroyActivityItem.java index 48a79f79dae1..0edcf1884f01 100644 --- a/core/java/android/app/servertransaction/DestroyActivityItem.java +++ b/core/java/android/app/servertransaction/DestroyActivityItem.java @@ -65,6 +65,7 @@ public class DestroyActivityItem extends ActivityLifecycleItem { @Override public void recycle() { + super.recycle(); mFinished = false; mConfigChanges = 0; ObjectPool.recycle(this); diff --git a/core/java/android/app/servertransaction/PauseActivityItem.java b/core/java/android/app/servertransaction/PauseActivityItem.java index 70a4755f99af..91e73cd5b1cd 100644 --- a/core/java/android/app/servertransaction/PauseActivityItem.java +++ b/core/java/android/app/servertransaction/PauseActivityItem.java @@ -102,6 +102,7 @@ public class PauseActivityItem extends ActivityLifecycleItem { @Override public void recycle() { + super.recycle(); mFinished = false; mUserLeaving = false; mConfigChanges = 0; diff --git a/core/java/android/app/servertransaction/ResumeActivityItem.java b/core/java/android/app/servertransaction/ResumeActivityItem.java index ed90f2cb1013..af2fb713e1bc 100644 --- a/core/java/android/app/servertransaction/ResumeActivityItem.java +++ b/core/java/android/app/servertransaction/ResumeActivityItem.java @@ -101,6 +101,7 @@ public class ResumeActivityItem extends ActivityLifecycleItem { @Override public void recycle() { + super.recycle(); mProcState = ActivityManager.PROCESS_STATE_UNKNOWN; mUpdateProcState = false; mIsForward = false; diff --git a/core/java/android/app/servertransaction/StopActivityItem.java b/core/java/android/app/servertransaction/StopActivityItem.java index b814d1ae1392..f955a903d649 100644 --- a/core/java/android/app/servertransaction/StopActivityItem.java +++ b/core/java/android/app/servertransaction/StopActivityItem.java @@ -72,6 +72,7 @@ public class StopActivityItem extends ActivityLifecycleItem { @Override public void recycle() { + super.recycle(); mShowWindow = false; mConfigChanges = 0; ObjectPool.recycle(this); diff --git a/core/java/android/hardware/OWNERS b/core/java/android/hardware/OWNERS new file mode 100644 index 000000000000..b8fea556e4c5 --- /dev/null +++ b/core/java/android/hardware/OWNERS @@ -0,0 +1,7 @@ +# Camera +per-file *Camera* = cychen@google.com +per-file *Camera* = epeev@google.com +per-file *Camera* = etalvala@google.com +per-file *Camera* = shuzhenwang@google.com +per-file *Camera* = yinchiayeh@google.com +per-file *Camera* = zhijunhe@google.com diff --git a/core/java/android/hardware/camera2/OWNERS b/core/java/android/hardware/camera2/OWNERS new file mode 100644 index 000000000000..18acfee14555 --- /dev/null +++ b/core/java/android/hardware/camera2/OWNERS @@ -0,0 +1,6 @@ +cychen@google.com +epeev@google.com +etalvala@google.com +shuzhenwang@google.com +yinchiayeh@google.com +zhijunhe@google.com diff --git a/core/java/android/net/IpSecConfig.java b/core/java/android/net/IpSecConfig.java index 6a262e2c87ca..8599f47c6245 100644 --- a/core/java/android/net/IpSecConfig.java +++ b/core/java/android/net/IpSecConfig.java @@ -218,6 +218,25 @@ public final class IpSecConfig implements Parcelable { @VisibleForTesting public IpSecConfig() {} + /** Copy constructor */ + @VisibleForTesting + public IpSecConfig(IpSecConfig c) { + mMode = c.mMode; + mSourceAddress = c.mSourceAddress; + mDestinationAddress = c.mDestinationAddress; + mNetwork = c.mNetwork; + mSpiResourceId = c.mSpiResourceId; + mEncryption = c.mEncryption; + mAuthentication = c.mAuthentication; + mAuthenticatedEncryption = c.mAuthenticatedEncryption; + mEncapType = c.mEncapType; + mEncapSocketResourceId = c.mEncapSocketResourceId; + mEncapRemotePort = c.mEncapRemotePort; + mNattKeepaliveInterval = c.mNattKeepaliveInterval; + mMarkValue = c.mMarkValue; + mMarkMask = c.mMarkMask; + } + private IpSecConfig(Parcel in) { mMode = in.readInt(); mSourceAddress = in.readString(); diff --git a/core/java/android/net/IpSecTransform.java b/core/java/android/net/IpSecTransform.java index 38759a9183f2..60e96f943401 100644 --- a/core/java/android/net/IpSecTransform.java +++ b/core/java/android/net/IpSecTransform.java @@ -84,9 +84,11 @@ public final class IpSecTransform implements AutoCloseable { @Retention(RetentionPolicy.SOURCE) public @interface EncapType {} - private IpSecTransform(Context context, IpSecConfig config) { + /** @hide */ + @VisibleForTesting + public IpSecTransform(Context context, IpSecConfig config) { mContext = context; - mConfig = config; + mConfig = new IpSecConfig(config); mResourceId = INVALID_RESOURCE_ID; } @@ -143,6 +145,18 @@ public final class IpSecTransform implements AutoCloseable { } /** + * Equals method used for testing + * + * @hide + */ + @VisibleForTesting + public static boolean equals(IpSecTransform lhs, IpSecTransform rhs) { + if (lhs == null || rhs == null) return (lhs == rhs); + return IpSecConfig.equals(lhs.getConfig(), rhs.getConfig()) + && lhs.mResourceId == rhs.mResourceId; + } + + /** * Deactivate this {@code IpSecTransform} and free allocated resources. * * <p>Deactivating a transform while it is still applied to a socket will result in errors on diff --git a/core/java/android/os/IUserManager.aidl b/core/java/android/os/IUserManager.aidl index 65e9473380ce..5e23932c48cc 100644 --- a/core/java/android/os/IUserManager.aidl +++ b/core/java/android/os/IUserManager.aidl @@ -71,6 +71,7 @@ interface IUserManager { Bundle getUserRestrictions(int userHandle); boolean hasBaseUserRestriction(String restrictionKey, int userHandle); boolean hasUserRestriction(in String restrictionKey, int userHandle); + boolean hasUserRestrictionOnAnyUser(in String restrictionKey); void setUserRestriction(String key, boolean value, int userHandle); void setApplicationRestrictions(in String packageName, in Bundle restrictions, int userHandle); diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java index 7e7af1a12299..185620066454 100644 --- a/core/java/android/os/UserManager.java +++ b/core/java/android/os/UserManager.java @@ -1678,6 +1678,18 @@ public class UserManager { } /** + * @hide + * Returns whether any user on the device has the given user restriction set. + */ + public boolean hasUserRestrictionOnAnyUser(String restrictionKey) { + try { + return mService.hasUserRestrictionOnAnyUser(restrictionKey); + } catch (RemoteException re) { + throw re.rethrowFromSystemServer(); + } + } + + /** * Return the serial number for a user. This is a device-unique * number assigned to that user; if the user is deleted and then a new * user created, the new users will not be given the same serial number. diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 1223271ad4c7..1feb822ffc58 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -5443,32 +5443,6 @@ public final class Settings { */ public static final String ENABLED_INPUT_METHODS = "enabled_input_methods"; - private static final Validator ENABLED_INPUT_METHODS_VALIDATOR = new Validator() { - @Override - public boolean validate(String value) { - if (value == null) { - return false; - } - String[] inputMethods = value.split(":"); - boolean valid = true; - for (String inputMethod : inputMethods) { - if (inputMethod.length() == 0) { - return false; - } - String[] subparts = inputMethod.split(";"); - for (String subpart : subparts) { - // allow either a non negative integer or a ComponentName - valid |= (NON_NEGATIVE_INTEGER_VALIDATOR.validate(subpart) - || COMPONENT_NAME_VALIDATOR.validate(subpart)); - } - if (!valid) { - return false; - } - } - return valid; - } - }; - /** * List of system input methods that are currently disabled. This is a string * containing the IDs of all disabled input methods, each ID separated @@ -7709,7 +7683,6 @@ public final class Settings { ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE, ENABLED_ACCESSIBILITY_SERVICES, ENABLED_VR_LISTENERS, - ENABLED_INPUT_METHODS, TOUCH_EXPLORATION_GRANTED_ACCESSIBILITY_SERVICES, TOUCH_EXPLORATION_ENABLED, ACCESSIBILITY_ENABLED, @@ -7815,7 +7788,6 @@ public final class Settings { VALIDATORS.put(ENABLED_ACCESSIBILITY_SERVICES, ENABLED_ACCESSIBILITY_SERVICES_VALIDATOR); VALIDATORS.put(ENABLED_VR_LISTENERS, ENABLED_VR_LISTENERS_VALIDATOR); - VALIDATORS.put(ENABLED_INPUT_METHODS, ENABLED_INPUT_METHODS_VALIDATOR); VALIDATORS.put(TOUCH_EXPLORATION_GRANTED_ACCESSIBILITY_SERVICES, TOUCH_EXPLORATION_GRANTED_ACCESSIBILITY_SERVICES_VALIDATOR); VALIDATORS.put(TOUCH_EXPLORATION_ENABLED, TOUCH_EXPLORATION_ENABLED_VALIDATOR); @@ -11382,7 +11354,8 @@ public final class Settings { "chained_battery_attribution_enabled"; /** - * The packages whitelisted to be run in autofill compatibility mode. + * The packages whitelisted to be run in autofill compatibility mode. The list + * of packages is ":" colon delimited. * * @hide */ diff --git a/core/java/android/security/keystore/OWNERS b/core/java/android/security/keystore/OWNERS new file mode 100644 index 000000000000..bb487fb52c9f --- /dev/null +++ b/core/java/android/security/keystore/OWNERS @@ -0,0 +1,4 @@ +aseemk@google.com +bozhu@google.com +dementyev@google.com +robertberry@google.com diff --git a/core/java/android/service/autofill/AutofillServiceInfo.java b/core/java/android/service/autofill/AutofillServiceInfo.java index f644887676a2..70c4ec07ee4e 100644 --- a/core/java/android/service/autofill/AutofillServiceInfo.java +++ b/core/java/android/service/autofill/AutofillServiceInfo.java @@ -45,7 +45,6 @@ import org.xmlpull.v1.XmlPullParserException; import java.io.IOException; import java.io.PrintWriter; -import java.util.Map; /** * {@link ServiceInfo} and meta-data about an {@link AutofillService}. @@ -80,7 +79,7 @@ public final class AutofillServiceInfo { private final String mSettingsActivity; @Nullable - private final Map<String, Pair<Long, String>> mCompatibilityPackages; + private final ArrayMap<String, Pair<Long, String>> mCompatibilityPackages; public AutofillServiceInfo(Context context, ComponentName comp, int userHandle) throws PackageManager.NameNotFoundException { @@ -118,7 +117,7 @@ public final class AutofillServiceInfo { } String settingsActivity = null; - Map<String, Pair<Long, String>> compatibilityPackages = null; + ArrayMap<String, Pair<Long, String>> compatibilityPackages = null; try { final Resources resources = context.getPackageManager().getResourcesForApplication( @@ -154,10 +153,10 @@ public final class AutofillServiceInfo { mCompatibilityPackages = compatibilityPackages; } - private Map<String, Pair<Long, String>> parseCompatibilityPackages(XmlPullParser parser, + private ArrayMap<String, Pair<Long, String>> parseCompatibilityPackages(XmlPullParser parser, Resources resources) throws IOException, XmlPullParserException { - Map<String, Pair<Long, String>> compatibilityPackages = null; + ArrayMap<String, Pair<Long, String>> compatibilityPackages = null; final int outerDepth = parser.getDepth(); int type; @@ -229,15 +228,8 @@ public final class AutofillServiceInfo { return mSettingsActivity; } - public boolean isCompatibilityModeRequested(String packageName, long versionCode) { - if (mCompatibilityPackages == null) { - return false; - } - final Pair<Long, String> pair = mCompatibilityPackages.get(packageName); - if (pair == null) { - return false; - } - return versionCode <= pair.first; + public ArrayMap<String, Pair<Long, String>> getCompatibilityPackages() { + return mCompatibilityPackages; } /** diff --git a/core/java/android/text/BoringLayout.java b/core/java/android/text/BoringLayout.java index dbe415773899..6fa5312be5cc 100644 --- a/core/java/android/text/BoringLayout.java +++ b/core/java/android/text/BoringLayout.java @@ -322,12 +322,6 @@ public class BoringLayout extends Layout implements TextUtils.EllipsizeCallback */ public static Metrics isBoring(CharSequence text, TextPaint paint, TextDirectionHeuristic textDir, Metrics metrics) { - return isBoring(text, null /* precomputed */, paint, textDir, metrics); - } - - /** @hide */ - public static Metrics isBoring(CharSequence text, PrecomputedText precomputed, TextPaint paint, - TextDirectionHeuristic textDir, Metrics metrics) { final int textLength = text.length(); if (hasAnyInterestingChars(text, textLength)) { return null; // There are some interesting characters. Not boring. @@ -350,17 +344,18 @@ public class BoringLayout extends Layout implements TextUtils.EllipsizeCallback fm.reset(); } - if (precomputed != null) { + TextLine line = TextLine.obtain(); + line.set(paint, text, 0, textLength, Layout.DIR_LEFT_TO_RIGHT, + Layout.DIRS_ALL_LEFT_TO_RIGHT, false, null); + if (text instanceof MeasuredText) { + MeasuredText mt = (MeasuredText) text; // Reaching here means there is only one paragraph. - MeasuredParagraph mp = precomputed.getMeasuredParagraph(0); + MeasuredParagraph mp = mt.getMeasuredParagraph(0); fm.width = (int) Math.ceil(mp.getWidth(0, mp.getTextLength())); } else { - TextLine line = TextLine.obtain(); - line.set(paint, text, 0, textLength, Layout.DIR_LEFT_TO_RIGHT, - Layout.DIRS_ALL_LEFT_TO_RIGHT, false, null); fm.width = (int) Math.ceil(line.metrics(fm)); - TextLine.recycle(line); } + TextLine.recycle(line); return fm; } diff --git a/core/java/android/text/DynamicLayout.java b/core/java/android/text/DynamicLayout.java index 10444f045d6f..18431cacbfaf 100644 --- a/core/java/android/text/DynamicLayout.java +++ b/core/java/android/text/DynamicLayout.java @@ -363,7 +363,7 @@ public class DynamicLayout extends Layout { @JustificationMode int justificationMode, @Nullable TextUtils.TruncateAt ellipsize, @IntRange(from = 0) int ellipsizedWidth) { - super(createEllipsizer(ellipsize, display), null /* precomputed */, + super(createEllipsizer(ellipsize, display), paint, width, align, textDir, spacingmult, spacingadd); final Builder b = Builder.obtain(base, paint, width) diff --git a/core/java/android/text/Layout.java b/core/java/android/text/Layout.java index d5d35904031c..aa97b2aba749 100644 --- a/core/java/android/text/Layout.java +++ b/core/java/android/text/Layout.java @@ -245,13 +245,6 @@ public abstract class Layout { protected Layout(CharSequence text, TextPaint paint, int width, Alignment align, TextDirectionHeuristic textDir, float spacingMult, float spacingAdd) { - this(text, null /* precomputed */, paint, width, align, textDir, spacingMult, spacingAdd); - } - - /** @hide */ - protected Layout(CharSequence text, PrecomputedText precomputed, TextPaint paint, - int width, Alignment align, TextDirectionHeuristic textDir, - float spacingMult, float spacingAdd) { if (width < 0) throw new IllegalArgumentException("Layout: " + width + " < 0"); @@ -266,7 +259,6 @@ public abstract class Layout { } mText = text; - mPrecomputed = precomputed; mPaint = paint; mWidth = width; mAlignment = align; @@ -570,7 +562,7 @@ public abstract class Layout { // XXX: assumes there's nothing additional to be done canvas.drawText(buf, start, end, x, lbaseline, paint); } else { - tl.set(paint, buf, mPrecomputed, start, end, dir, directions, hasTab, tabStops); + tl.set(paint, buf, start, end, dir, directions, hasTab, tabStops); if (justify) { tl.justify(right - left - indentWidth); } @@ -2272,7 +2264,6 @@ public abstract class Layout { } private CharSequence mText; - private PrecomputedText mPrecomputed; private TextPaint mPaint; private TextPaint mWorkPaint = new TextPaint(); private int mWidth; diff --git a/core/java/android/text/MeasuredText.java b/core/java/android/text/MeasuredText.java new file mode 100644 index 000000000000..bb7a9e0b7906 --- /dev/null +++ b/core/java/android/text/MeasuredText.java @@ -0,0 +1,427 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.text; + +import android.annotation.IntRange; +import android.annotation.NonNull; +import android.util.IntArray; + +import com.android.internal.util.ArrayUtils; +import com.android.internal.util.Preconditions; + +import java.util.ArrayList; + +/** + * A text which has already been measured. + */ +public class MeasuredText implements Spanned { + private static final char LINE_FEED = '\n'; + + // The original text. + private final @NonNull CharSequence mText; + + // The inclusive start offset of the measuring target. + private final @IntRange(from = 0) int mStart; + + // The exclusive end offset of the measuring target. + private final @IntRange(from = 0) int mEnd; + + // The TextPaint used for measurement. + private final @NonNull TextPaint mPaint; + + // The requested text direction. + private final @NonNull TextDirectionHeuristic mTextDir; + + // The measured paragraph texts. + private final @NonNull MeasuredParagraph[] mMeasuredParagraphs; + + // The sorted paragraph end offsets. + private final @NonNull int[] mParagraphBreakPoints; + + // The break strategy for this measured text. + private final @Layout.BreakStrategy int mBreakStrategy; + + // The hyphenation frequency for this measured text. + private final @Layout.HyphenationFrequency int mHyphenationFrequency; + + /** + * A Builder for MeasuredText + */ + public static final class Builder { + // Mandatory parameters. + private final @NonNull CharSequence mText; + private final @NonNull TextPaint mPaint; + + // Members to be updated by setters. + private @IntRange(from = 0) int mStart; + private @IntRange(from = 0) int mEnd; + private TextDirectionHeuristic mTextDir = TextDirectionHeuristics.FIRSTSTRONG_LTR; + private @Layout.BreakStrategy int mBreakStrategy = Layout.BREAK_STRATEGY_HIGH_QUALITY; + private @Layout.HyphenationFrequency int mHyphenationFrequency = + Layout.HYPHENATION_FREQUENCY_NORMAL; + + + /** + * Builder constructor + * + * @param text The text to be measured. + * @param paint The paint to be used for drawing. + */ + public Builder(@NonNull CharSequence text, @NonNull TextPaint paint) { + Preconditions.checkNotNull(text); + Preconditions.checkNotNull(paint); + + mText = text; + mPaint = paint; + mStart = 0; + mEnd = text.length(); + } + + /** + * Set the range of measuring target. + * + * @param start The measuring target start offset in the text. + * @param end The measuring target end offset in the text. + */ + public @NonNull Builder setRange(@IntRange(from = 0) int start, + @IntRange(from = 0) int end) { + Preconditions.checkArgumentInRange(start, 0, mText.length(), "start"); + Preconditions.checkArgumentInRange(end, 0, mText.length(), "end"); + Preconditions.checkArgument(start <= end, "The range is reversed."); + + mStart = start; + mEnd = end; + return this; + } + + /** + * Set the text direction heuristic + * + * The default value is {@link TextDirectionHeuristics#FIRSTSTRONG_LTR}. + * + * @param textDir The text direction heuristic for resolving bidi behavior. + * @return this builder, useful for chaining. + */ + public @NonNull Builder setTextDirection(@NonNull TextDirectionHeuristic textDir) { + Preconditions.checkNotNull(textDir); + mTextDir = textDir; + return this; + } + + /** + * Set the break strategy + * + * The default value is {@link Layout#BREAK_STRATEGY_HIGH_QUALITY}. + * + * @param breakStrategy The break strategy. + * @return this builder, useful for chaining. + */ + public @NonNull Builder setBreakStrategy(@Layout.BreakStrategy int breakStrategy) { + mBreakStrategy = breakStrategy; + return this; + } + + /** + * Set the hyphenation frequency + * + * The default value is {@link Layout#HYPHENATION_FREQUENCY_NORMAL}. + * + * @param hyphenationFrequency The hyphenation frequency. + * @return this builder, useful for chaining. + */ + public @NonNull Builder setHyphenationFrequency( + @Layout.HyphenationFrequency int hyphenationFrequency) { + mHyphenationFrequency = hyphenationFrequency; + return this; + } + + /** + * Build the measured text + * + * @return the measured text. + */ + public @NonNull MeasuredText build() { + return build(true /* build full layout result */); + } + + /** @hide */ + public @NonNull MeasuredText build(boolean computeLayout) { + final boolean needHyphenation = mBreakStrategy != Layout.BREAK_STRATEGY_SIMPLE + && mHyphenationFrequency != Layout.HYPHENATION_FREQUENCY_NONE; + + final IntArray paragraphEnds = new IntArray(); + final ArrayList<MeasuredParagraph> measuredTexts = new ArrayList<>(); + + int paraEnd = 0; + for (int paraStart = mStart; paraStart < mEnd; paraStart = paraEnd) { + paraEnd = TextUtils.indexOf(mText, LINE_FEED, paraStart, mEnd); + if (paraEnd < 0) { + // No LINE_FEED(U+000A) character found. Use end of the text as the paragraph + // end. + paraEnd = mEnd; + } else { + paraEnd++; // Includes LINE_FEED(U+000A) to the prev paragraph. + } + + paragraphEnds.add(paraEnd); + measuredTexts.add(MeasuredParagraph.buildForStaticLayout( + mPaint, mText, paraStart, paraEnd, mTextDir, needHyphenation, + computeLayout, null /* no recycle */)); + } + + return new MeasuredText(mText, mStart, mEnd, mPaint, mTextDir, mBreakStrategy, + mHyphenationFrequency, measuredTexts.toArray( + new MeasuredParagraph[measuredTexts.size()]), + paragraphEnds.toArray()); + } + }; + + // Use MeasuredText.Builder instead. + private MeasuredText(@NonNull CharSequence text, + @IntRange(from = 0) int start, + @IntRange(from = 0) int end, + @NonNull TextPaint paint, + @NonNull TextDirectionHeuristic textDir, + @Layout.BreakStrategy int breakStrategy, + @Layout.HyphenationFrequency int frequency, + @NonNull MeasuredParagraph[] measuredTexts, + @NonNull int[] paragraphBreakPoints) { + mText = text; + mStart = start; + mEnd = end; + // Copy the paint so that we can keep the reference of typeface in native layout result. + mPaint = new TextPaint(paint); + mMeasuredParagraphs = measuredTexts; + mParagraphBreakPoints = paragraphBreakPoints; + mTextDir = textDir; + mBreakStrategy = breakStrategy; + mHyphenationFrequency = frequency; + } + + /** + * Return the underlying text. + */ + public @NonNull CharSequence getText() { + return mText; + } + + /** + * Returns the inclusive start offset of measured region. + */ + public @IntRange(from = 0) int getStart() { + return mStart; + } + + /** + * Returns the exclusive end offset of measured region. + */ + public @IntRange(from = 0) int getEnd() { + return mEnd; + } + + /** + * Returns the text direction associated with char sequence. + */ + public @NonNull TextDirectionHeuristic getTextDir() { + return mTextDir; + } + + /** + * Returns the paint used to measure this text. + */ + public @NonNull TextPaint getPaint() { + return mPaint; + } + + /** + * Returns the length of the paragraph of this text. + */ + public @IntRange(from = 0) int getParagraphCount() { + return mParagraphBreakPoints.length; + } + + /** + * Returns the paragraph start offset of the text. + */ + public @IntRange(from = 0) int getParagraphStart(@IntRange(from = 0) int paraIndex) { + Preconditions.checkArgumentInRange(paraIndex, 0, getParagraphCount(), "paraIndex"); + return paraIndex == 0 ? mStart : mParagraphBreakPoints[paraIndex - 1]; + } + + /** + * Returns the paragraph end offset of the text. + */ + public @IntRange(from = 0) int getParagraphEnd(@IntRange(from = 0) int paraIndex) { + Preconditions.checkArgumentInRange(paraIndex, 0, getParagraphCount(), "paraIndex"); + return mParagraphBreakPoints[paraIndex]; + } + + /** @hide */ + public @NonNull MeasuredParagraph getMeasuredParagraph(@IntRange(from = 0) int paraIndex) { + return mMeasuredParagraphs[paraIndex]; + } + + /** + * Returns the break strategy for this text. + */ + public @Layout.BreakStrategy int getBreakStrategy() { + return mBreakStrategy; + } + + /** + * Returns the hyphenation frequency for this text. + */ + public @Layout.HyphenationFrequency int getHyphenationFrequency() { + return mHyphenationFrequency; + } + + /** + * Returns true if the given TextPaint gives the same result of text layout for this text. + * @hide + */ + public boolean canUseMeasuredResult(@NonNull TextPaint paint) { + return mPaint.getTextSize() == paint.getTextSize() + && mPaint.getTextSkewX() == paint.getTextSkewX() + && mPaint.getTextScaleX() == paint.getTextScaleX() + && mPaint.getLetterSpacing() == paint.getLetterSpacing() + && mPaint.getWordSpacing() == paint.getWordSpacing() + && mPaint.getFlags() == paint.getFlags() // Maybe not all flag affects text layout. + && mPaint.getTextLocales() == paint.getTextLocales() // need to be equals? + && mPaint.getFontVariationSettings() == paint.getFontVariationSettings() + && mPaint.getTypeface() == paint.getTypeface() + && TextUtils.equals(mPaint.getFontFeatureSettings(), paint.getFontFeatureSettings()); + } + + /** @hide */ + public int findParaIndex(@IntRange(from = 0) int pos) { + // TODO: Maybe good to remove paragraph concept from MeasuredText and add substring layout + // support to StaticLayout. + for (int i = 0; i < mParagraphBreakPoints.length; ++i) { + if (pos < mParagraphBreakPoints[i]) { + return i; + } + } + throw new IndexOutOfBoundsException( + "pos must be less than " + mParagraphBreakPoints[mParagraphBreakPoints.length - 1] + + ", gave " + pos); + } + + /** @hide */ + public float getWidth(@IntRange(from = 0) int start, @IntRange(from = 0) int end) { + final int paraIndex = findParaIndex(start); + final int paraStart = getParagraphStart(paraIndex); + final int paraEnd = getParagraphEnd(paraIndex); + if (start < paraStart || paraEnd < end) { + throw new RuntimeException("Cannot measured across the paragraph:" + + "para: (" + paraStart + ", " + paraEnd + "), " + + "request: (" + start + ", " + end + ")"); + } + return getMeasuredParagraph(paraIndex).getWidth(start - paraStart, end - paraStart); + } + + /** + * Returns the size of native MeasuredText memory usage + * + * Note that this may not be aculate. Must be used only for testing purposes. + * @hide + */ + public int getMemoryUsage() { + int r = 0; + for (int i = 0; i < getParagraphCount(); ++i) { + r += getMeasuredParagraph(i).getMemoryUsage(); + } + return r; + } + + /////////////////////////////////////////////////////////////////////////////////////////////// + // Spanned overrides + // + // Just proxy for underlying mText if appropriate. + + @Override + public <T> T[] getSpans(int start, int end, Class<T> type) { + if (mText instanceof Spanned) { + return ((Spanned) mText).getSpans(start, end, type); + } else { + return ArrayUtils.emptyArray(type); + } + } + + @Override + public int getSpanStart(Object tag) { + if (mText instanceof Spanned) { + return ((Spanned) mText).getSpanStart(tag); + } else { + return -1; + } + } + + @Override + public int getSpanEnd(Object tag) { + if (mText instanceof Spanned) { + return ((Spanned) mText).getSpanEnd(tag); + } else { + return -1; + } + } + + @Override + public int getSpanFlags(Object tag) { + if (mText instanceof Spanned) { + return ((Spanned) mText).getSpanFlags(tag); + } else { + return 0; + } + } + + @Override + public int nextSpanTransition(int start, int limit, Class type) { + if (mText instanceof Spanned) { + return ((Spanned) mText).nextSpanTransition(start, limit, type); + } else { + return mText.length(); + } + } + + /////////////////////////////////////////////////////////////////////////////////////////////// + // CharSequence overrides. + // + // Just proxy for underlying mText. + + @Override + public int length() { + return mText.length(); + } + + @Override + public char charAt(int index) { + // TODO: Should this be index + mStart ? + return mText.charAt(index); + } + + @Override + public CharSequence subSequence(int start, int end) { + // TODO: return MeasuredText. + // TODO: Should this be index + mStart, end + mStart ? + return mText.subSequence(start, end); + } + + @Override + public String toString() { + return mText.toString(); + } +} diff --git a/core/java/android/text/PrecomputedText.java b/core/java/android/text/PrecomputedText.java deleted file mode 100644 index 39fc2bd52db4..000000000000 --- a/core/java/android/text/PrecomputedText.java +++ /dev/null @@ -1,412 +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. - */ - -package android.text; - -import android.annotation.IntRange; -import android.annotation.NonNull; -import android.util.IntArray; - -import com.android.internal.util.Preconditions; - -import java.util.ArrayList; - -/** - * A text which has the character metrics data - * - * This text holds a part of the text layout result. You can accelerate - * {@link android.widget.TextView} or {@link StaticLayout} by using this text. - * - * <pre> - * Example of background measurement. - * <code> - * void asyncSetText(final TextView textView, final String expensiveLongString, Handler handler) { - * final PrecomputedText.Params params = textView.getTextParams(); - * handler.post(() -> { - * final PrecomputedText precomputedText - * = PrecomputedText.create(expensiveLongString, params); - * textView.post(() -> { - * textView.setPrecomputedTextOrThrow(precomputedText); - * }); - * }); - * } - * </code> - * </pre> - * - * Note that the {@link PrecomputedText} created from different parameters of the target - * {@link android.widget.TextView} will be rejected internally and compute the text layout again - * with the current {@link android.widget.TextView} parameters. - */ -public class PrecomputedText { - private static final char LINE_FEED = '\n'; - - /** - * The information required for building {@link PrecomputedText}. - * - * Contains information required for precomputing text measurement metadata, so it can be done - * in isolation of a {@link android.widget.TextView} or {@link StaticLayout}, when final layout - * constraints are not known. - */ - public static class Params { - // The TextPaint used for measurement. - private final @NonNull TextPaint mPaint; - - // The requested text direction. - private final @NonNull TextDirectionHeuristic mTextDir; - - // The break strategy for this measured text. - private final @Layout.BreakStrategy int mBreakStrategy; - - // The hyphenation frequency for this measured text. - private final @Layout.HyphenationFrequency int mHyphenationFrequency; - - /** - * A builder for creating {@link Params}. - */ - public static class Builder { - // The TextPaint used for measurement. - private final @NonNull TextPaint mPaint; - - // The requested text direction. - private TextDirectionHeuristic mTextDir = TextDirectionHeuristics.FIRSTSTRONG_LTR; - - // The break strategy for this measured text. - private @Layout.BreakStrategy int mBreakStrategy = Layout.BREAK_STRATEGY_HIGH_QUALITY; - - // The hyphenation frequency for this measured text. - private @Layout.HyphenationFrequency int mHyphenationFrequency = - Layout.HYPHENATION_FREQUENCY_NORMAL; - - /** - * Builder constructor - * - * @param paint The paint to be used for drawing - */ - public Builder(@NonNull TextPaint paint) { - mPaint = paint; - } - - /** - * Set the line break strategy - * - * The default value is {@link Layout#BREAK_STRATEGY_HIGH_QUALITY}. - * - * @param strategy The break strategy - * @return this builder, useful for chaining - */ - public Builder setBreakStrategy(@Layout.BreakStrategy int strategy) { - mBreakStrategy = strategy; - return this; - } - - /** - * Set the hyphenation frequency - * - * The default value is {@link Layout#HYPHENATION_FREQUENCY_NORMAL}. - * - * @param frequency The hyphenation frequency - * @return this builder, useful for chaining - */ - public Builder setHyphenationFrequency(@Layout.HyphenationFrequency int frequency) { - mHyphenationFrequency = frequency; - return this; - } - - /** - * Set the text direction heuristic - * - * The default value is {@link TextDirectionHeuristics#FIRSTSTRONG_LTR}. - * - * @param textDir The text direction heuristic for resolving bidi behavior - * @return this builder, useful for chaining - */ - public Builder setTextDirection(@NonNull TextDirectionHeuristic textDir) { - mTextDir = textDir; - return this; - } - - /** - * Build the {@link Params} - * - * @return the layout parameter - */ - public @NonNull Params build() { - return new Params(mPaint, mTextDir, mBreakStrategy, mHyphenationFrequency); - } - } - - // Use Builder instead. - /** @hide */ - public Params(@NonNull TextPaint paint, @NonNull TextDirectionHeuristic textDir, - @Layout.BreakStrategy int strategy, @Layout.HyphenationFrequency int frequency) { - mPaint = paint; - mTextDir = textDir; - mBreakStrategy = strategy; - mHyphenationFrequency = frequency; - } - - /** - * Returns the {@link TextPaint} for this text - * - * @return A {@link TextPaint} - */ - public @NonNull TextPaint getTextPaint() { - return mPaint; - } - - /** - * Returns the {@link TextDirectionHeuristic} for this text - * - * @return A {@link TextDirectionHeuristic} - */ - public @NonNull TextDirectionHeuristic getTextDirection() { - return mTextDir; - } - - /** - * Returns the break strategy for this text - * - * @return A line break strategy - */ - public @Layout.BreakStrategy int getBreakStrategy() { - return mBreakStrategy; - } - - /** - * Returns the hyphenation frequency for this text - * - * @return A hyphenation frequency - */ - public @Layout.HyphenationFrequency int getHyphenationFrequency() { - return mHyphenationFrequency; - } - - private boolean sameTextMetricsInternal(@NonNull TextPaint paint, - @NonNull TextDirectionHeuristic textDir, @Layout.BreakStrategy int strategy, - @Layout.HyphenationFrequency int frequency) { - return mTextDir == textDir - && mBreakStrategy == strategy - && mHyphenationFrequency == frequency - && mPaint.equalsForTextMeasurement(paint); - } - - /** - * Check if the same text layout. - * - * @return true if this and the given param result in the same text layout - */ - public boolean sameTextMetrics(@NonNull Params param) { - return sameTextMetricsInternal(param.mPaint, param.mTextDir, param.mBreakStrategy, - param.mHyphenationFrequency); - } - }; - - // The original text. - private final @NonNull CharSequence mText; - - // The inclusive start offset of the measuring target. - private final @IntRange(from = 0) int mStart; - - // The exclusive end offset of the measuring target. - private final @IntRange(from = 0) int mEnd; - - private final @NonNull Params mParams; - - // The measured paragraph texts. - private final @NonNull MeasuredParagraph[] mMeasuredParagraphs; - - // The sorted paragraph end offsets. - private final @NonNull int[] mParagraphBreakPoints; - - /** - * Create a new {@link PrecomputedText} which will pre-compute text measurement and glyph - * positioning information. - * <p> - * This can be expensive, so computing this on a background thread before your text will be - * presented can save work on the UI thread. - * </p> - * - * @param text The text to be measured - * @param param Parameters that define how text will be precomputed - * @return A {@link PrecomputedText} - */ - public static PrecomputedText create(@NonNull CharSequence text, @NonNull Params param) { - return createInternal(text, param, 0, text.length(), true /* compute full Layout */); - } - - - /** @hide */ - public static PrecomputedText createWidthOnly(@NonNull CharSequence text, @NonNull Params param, - @IntRange(from = 0) int start, @IntRange(from = 0) int end) { - return createInternal(text, param, start, end, false /* compute width only */); - } - - private static PrecomputedText createInternal(@NonNull CharSequence text, @NonNull Params param, - @IntRange(from = 0) int start, @IntRange(from = 0) int end, boolean computeLayout) { - Preconditions.checkNotNull(text); - Preconditions.checkNotNull(param); - final boolean needHyphenation = param.getBreakStrategy() != Layout.BREAK_STRATEGY_SIMPLE - && param.getHyphenationFrequency() != Layout.HYPHENATION_FREQUENCY_NONE; - - final IntArray paragraphEnds = new IntArray(); - final ArrayList<MeasuredParagraph> measuredTexts = new ArrayList<>(); - - int paraEnd = 0; - for (int paraStart = start; paraStart < end; paraStart = paraEnd) { - paraEnd = TextUtils.indexOf(text, LINE_FEED, paraStart, end); - if (paraEnd < 0) { - // No LINE_FEED(U+000A) character found. Use end of the text as the paragraph - // end. - paraEnd = end; - } else { - paraEnd++; // Includes LINE_FEED(U+000A) to the prev paragraph. - } - - paragraphEnds.add(paraEnd); - measuredTexts.add(MeasuredParagraph.buildForStaticLayout( - param.getTextPaint(), text, paraStart, paraEnd, param.getTextDirection(), - needHyphenation, computeLayout, null /* no recycle */)); - } - - return new PrecomputedText(text, start, end, param, - measuredTexts.toArray(new MeasuredParagraph[measuredTexts.size()]), - paragraphEnds.toArray()); - } - - // Use PrecomputedText.create instead. - private PrecomputedText(@NonNull CharSequence text, @IntRange(from = 0) int start, - @IntRange(from = 0) int end, @NonNull Params param, - @NonNull MeasuredParagraph[] measuredTexts, @NonNull int[] paragraphBreakPoints) { - mText = TextUtils.stringOrSpannedString(text); - mStart = start; - mEnd = end; - mParams = param; - mMeasuredParagraphs = measuredTexts; - mParagraphBreakPoints = paragraphBreakPoints; - } - - /** - * Return the underlying text. - */ - public @NonNull CharSequence getText() { - return mText; - } - - /** - * Returns the inclusive start offset of measured region. - * @hide - */ - public @IntRange(from = 0) int getStart() { - return mStart; - } - - /** - * Returns the exclusive end offset of measured region. - * @hide - */ - public @IntRange(from = 0) int getEnd() { - return mEnd; - } - - /** - * Returns the layout parameters used to measure this text. - */ - public @NonNull Params getParams() { - return mParams; - } - - /** - * Returns the length of the paragraph of this text. - */ - public @IntRange(from = 0) int getParagraphCount() { - return mParagraphBreakPoints.length; - } - - /** - * Returns the paragraph start offset of the text. - */ - public @IntRange(from = 0) int getParagraphStart(@IntRange(from = 0) int paraIndex) { - Preconditions.checkArgumentInRange(paraIndex, 0, getParagraphCount(), "paraIndex"); - return paraIndex == 0 ? mStart : mParagraphBreakPoints[paraIndex - 1]; - } - - /** - * Returns the paragraph end offset of the text. - */ - public @IntRange(from = 0) int getParagraphEnd(@IntRange(from = 0) int paraIndex) { - Preconditions.checkArgumentInRange(paraIndex, 0, getParagraphCount(), "paraIndex"); - return mParagraphBreakPoints[paraIndex]; - } - - /** @hide */ - public @NonNull MeasuredParagraph getMeasuredParagraph(@IntRange(from = 0) int paraIndex) { - return mMeasuredParagraphs[paraIndex]; - } - - /** - * Returns true if the given TextPaint gives the same result of text layout for this text. - * @hide - */ - public boolean canUseMeasuredResult(@IntRange(from = 0) int start, @IntRange(from = 0) int end, - @NonNull TextDirectionHeuristic textDir, @NonNull TextPaint paint, - @Layout.BreakStrategy int strategy, @Layout.HyphenationFrequency int frequency) { - final TextPaint mtPaint = mParams.getTextPaint(); - return mStart == start - && mEnd == end - && mParams.sameTextMetricsInternal(paint, textDir, strategy, frequency); - } - - /** @hide */ - public int findParaIndex(@IntRange(from = 0) int pos) { - // TODO: Maybe good to remove paragraph concept from PrecomputedText and add substring - // layout support to StaticLayout. - for (int i = 0; i < mParagraphBreakPoints.length; ++i) { - if (pos < mParagraphBreakPoints[i]) { - return i; - } - } - throw new IndexOutOfBoundsException( - "pos must be less than " + mParagraphBreakPoints[mParagraphBreakPoints.length - 1] - + ", gave " + pos); - } - - /** @hide */ - public float getWidth(@IntRange(from = 0) int start, @IntRange(from = 0) int end) { - final int paraIndex = findParaIndex(start); - final int paraStart = getParagraphStart(paraIndex); - final int paraEnd = getParagraphEnd(paraIndex); - if (start < paraStart || paraEnd < end) { - throw new RuntimeException("Cannot measured across the paragraph:" - + "para: (" + paraStart + ", " + paraEnd + "), " - + "request: (" + start + ", " + end + ")"); - } - return getMeasuredParagraph(paraIndex).getWidth(start - paraStart, end - paraStart); - } - - /** - * Returns the size of native PrecomputedText memory usage - * - * Note that this is not guaranteed to be accurate. Must be used only for testing purposes. - * @hide - */ - public int getMemoryUsage() { - int r = 0; - for (int i = 0; i < getParagraphCount(); ++i) { - r += getMeasuredParagraph(i).getMemoryUsage(); - } - return r; - } -} diff --git a/core/java/android/text/StaticLayout.java b/core/java/android/text/StaticLayout.java index 5869802e74e6..e62f4216f33a 100644 --- a/core/java/android/text/StaticLayout.java +++ b/core/java/android/text/StaticLayout.java @@ -78,23 +78,6 @@ public class StaticLayout extends Layout { /** * Obtain a builder for constructing StaticLayout objects. * - * @param source The precomputed text. - * @param start The index of the start of the text - * @param end The index + 1 of the end of the text - * @param paint The base paint used for layout - * @param width The width in pixels - * @return a builder object used for constructing the StaticLayout - */ - @NonNull - public static Builder obtain(@NonNull PrecomputedText source, @IntRange(from = 0) int start, - @IntRange(from = 0) int end, @NonNull TextPaint paint, - @IntRange(from = 0) int width) { - return obtain(source.getText(), source, start, end, paint, width); - } - - /** - * Obtain a builder for constructing StaticLayout objects. - * * @param source The text to be laid out, optionally with spans * @param start The index of the start of the text * @param end The index + 1 of the end of the text @@ -106,12 +89,6 @@ public class StaticLayout extends Layout { public static Builder obtain(@NonNull CharSequence source, @IntRange(from = 0) int start, @IntRange(from = 0) int end, @NonNull TextPaint paint, @IntRange(from = 0) int width) { - return obtain(source, null, start, end, paint, width); - } - - private static Builder obtain(@NonNull CharSequence source, @Nullable PrecomputedText text, - @IntRange(from = 0) int start, @IntRange(from = 0) int end, - @NonNull TextPaint paint, @IntRange(from = 0) int width) { Builder b = sPool.acquire(); if (b == null) { b = new Builder(); @@ -119,7 +96,6 @@ public class StaticLayout extends Layout { // set default initial values b.mText = source; - b.mPrecomputed = text; b.mStart = start; b.mEnd = end; b.mPaint = paint; @@ -452,7 +428,6 @@ public class StaticLayout extends Layout { } private CharSequence mText; - private PrecomputedText mPrecomputed; private int mStart; private int mEnd; private TextPaint mPaint; @@ -515,7 +490,7 @@ public class StaticLayout extends Layout { float spacingmult, float spacingadd, boolean includepad, TextUtils.TruncateAt ellipsize, int ellipsizedWidth) { - this(source, null /* precomputed */, bufstart, bufend, paint, outerwidth, align, + this(source, bufstart, bufend, paint, outerwidth, align, TextDirectionHeuristics.FIRSTSTRONG_LTR, spacingmult, spacingadd, includepad, ellipsize, ellipsizedWidth, Integer.MAX_VALUE); } @@ -525,16 +500,7 @@ public class StaticLayout extends Layout { * @deprecated Use {@link Builder} instead. */ @Deprecated - public StaticLayout(CharSequence source, int bufstart, int bufend, TextPaint paint, - int outerwidth, Alignment align, TextDirectionHeuristic textDir, - float spacingmult, float spacingadd, boolean includepad, - TextUtils.TruncateAt ellipsize, int ellipsizedWidth, int maxLines) { - this(source, null /* precomputed */, bufstart, bufend, paint, outerwidth, align, textDir, - spacingmult, spacingadd, includepad, ellipsize, ellipsizedWidth, maxLines); - } - - /** @hide */ - public StaticLayout(CharSequence source, PrecomputedText precomputed, int bufstart, int bufend, + public StaticLayout(CharSequence source, int bufstart, int bufend, TextPaint paint, int outerwidth, Alignment align, TextDirectionHeuristic textDir, float spacingmult, float spacingadd, @@ -545,7 +511,6 @@ public class StaticLayout extends Layout { : (source instanceof Spanned) ? new SpannedEllipsizer(source) : new Ellipsizer(source), - (ellipsize == null) ? precomputed : null, paint, outerwidth, align, textDir, spacingmult, spacingadd); Builder b = Builder.obtain(source, bufstart, bufend, paint, outerwidth) @@ -686,20 +651,43 @@ public class StaticLayout extends Layout { b.mJustificationMode != Layout.JUSTIFICATION_MODE_NONE, indents, mLeftPaddings, mRightPaddings); - PrecomputedText measured = null; - final Spanned spanned = (b.mText instanceof Spanned) ? (Spanned) b.mText : null; - if (b.mPrecomputed != null) { - if (b.mPrecomputed.canUseMeasuredResult(bufStart, bufEnd, textDir, paint, - b.mBreakStrategy, b.mHyphenationFrequency)) { - measured = b.mPrecomputed; + MeasuredText measured = null; + final Spanned spanned; + final boolean canUseMeasuredText; + if (source instanceof MeasuredText) { + measured = (MeasuredText) source; + + if (bufStart != measured.getStart() || bufEnd != measured.getEnd()) { + // The buffer position has changed. Re-measure here. + canUseMeasuredText = false; + } else if (b.mBreakStrategy != measured.getBreakStrategy() + || b.mHyphenationFrequency != measured.getHyphenationFrequency()) { + // The computed hyphenation pieces may not be able to used. Re-measure it. + canUseMeasuredText = false; + } else { + // We can use measured information. + canUseMeasuredText = true; } - } - if (measured == null) { - final PrecomputedText.Params param = new PrecomputedText.Params(paint, textDir, - b.mBreakStrategy, b.mHyphenationFrequency); - measured = PrecomputedText.createWidthOnly(source, param, bufStart, bufEnd); + } else { + canUseMeasuredText = false; } + if (!canUseMeasuredText) { + measured = new MeasuredText.Builder(source, paint) + .setRange(bufStart, bufEnd) + .setTextDirection(textDir) + .setBreakStrategy(b.mBreakStrategy) + .setHyphenationFrequency(b.mHyphenationFrequency) + .build(false /* full layout is not necessary for line breaking */); + spanned = (source instanceof Spanned) ? (Spanned) source : null; + } else { + final CharSequence original = measured.getText(); + spanned = (original instanceof Spanned) ? (Spanned) original : null; + // Overwrite with the one when measured. + // TODO: Give an option for developer not to overwrite and measure again here? + textDir = measured.getTextDir(); + paint = measured.getPaint(); + } try { for (int paraIndex = 0; paraIndex < measured.getParagraphCount(); paraIndex++) { diff --git a/core/java/android/text/TextLine.java b/core/java/android/text/TextLine.java index be5bb4d1809a..55367dcce47e 100644 --- a/core/java/android/text/TextLine.java +++ b/core/java/android/text/TextLine.java @@ -16,7 +16,6 @@ package android.text; -import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; import android.graphics.Canvas; @@ -61,7 +60,7 @@ public class TextLine { private char[] mChars; private boolean mCharsValid; private Spanned mSpanned; - private PrecomputedText mComputed; + private MeasuredText mMeasured; // Additional width of whitespace for justification. This value is per whitespace, thus // the line width will increase by mAddedWidth x (number of stretchable whitespaces). @@ -120,7 +119,7 @@ public class TextLine { tl.mSpanned = null; tl.mTabs = null; tl.mChars = null; - tl.mComputed = null; + tl.mMeasured = null; tl.mMetricAffectingSpanSpanSet.recycle(); tl.mCharacterStyleSpanSet.recycle(); @@ -150,31 +149,10 @@ public class TextLine { * @param tabStops the tabStops. Can be null. */ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) - public void set(TextPaint paint, CharSequence text, int start, - int limit, int dir, Directions directions, boolean hasTabs, TabStops tabStops) { - set(paint, text, null, start, limit, dir, directions, hasTabs, tabStops); - } - - /** - * Initializes a TextLine and prepares it for use. - * - * @param paint the base paint for the line - * @param text the text, can be Styled - * @param precomputed the precomputed text - * @param start the start of the line relative to the text - * @param limit the limit of the line relative to the text - * @param dir the paragraph direction of this line - * @param directions the directions information of this line - * @param hasTabs true if the line might contain tabs - * @param tabStops the tabStops. - */ - public void set(@NonNull TextPaint paint, @NonNull CharSequence text, - @Nullable PrecomputedText precomputed, @IntRange(from = 0) int start, - @IntRange(from = 0) int limit, int dir, @NonNull Directions directions, boolean hasTabs, - @Nullable TabStops tabStops) { + public void set(TextPaint paint, CharSequence text, int start, int limit, int dir, + Directions directions, boolean hasTabs, TabStops tabStops) { mPaint = paint; mText = text; - mComputed = precomputed; mStart = start; mLen = limit - start; mDir = dir; @@ -192,6 +170,14 @@ public class TextLine { hasReplacement = mReplacementSpanSpanSet.numberOfSpans > 0; } + mMeasured = null; + if (text instanceof MeasuredText) { + MeasuredText mt = (MeasuredText) text; + if (mt.canUseMeasuredResult(paint)) { + mMeasured = mt; + } + } + mCharsValid = hasReplacement || hasTabs || directions != Layout.DIRS_ALL_LEFT_TO_RIGHT; if (mCharsValid) { @@ -760,12 +746,12 @@ public class TextLine { return wp.getRunAdvance(mChars, start, end, contextStart, contextEnd, runIsRtl, offset); } else { final int delta = mStart; - if (mComputed == null) { + if (mMeasured == null) { // TODO: Enable measured getRunAdvance for ReplacementSpan and RTL text. return wp.getRunAdvance(mText, delta + start, delta + end, delta + contextStart, delta + contextEnd, runIsRtl, delta + offset); } else { - return mComputed.getWidth(start + delta, end + delta); + return mMeasured.getWidth(start + delta, end + delta); } } } diff --git a/core/java/android/util/LongArray.java b/core/java/android/util/LongArray.java index 9b0489ca5c6e..f9454d962eb4 100644 --- a/core/java/android/util/LongArray.java +++ b/core/java/android/util/LongArray.java @@ -18,9 +18,11 @@ package android.util; import com.android.internal.util.ArrayUtils; import com.android.internal.util.Preconditions; -import java.util.Arrays; + import libcore.util.EmptyArray; +import java.util.Arrays; + /** * Implements a growing array of long primitives. * @@ -216,4 +218,17 @@ public class LongArray implements Cloneable { throw new ArrayIndexOutOfBoundsException(mSize, index); } } + + /** + * Test if each element of {@code a} equals corresponding element from {@code b} + */ + public static boolean elementsEqual(LongArray a, LongArray b) { + if (a.mSize != b.mSize) return false; + for (int i = 0; i < a.mSize; i++) { + if (a.get(i) != b.get(i)) { + return false; + } + } + return true; + } } diff --git a/core/java/android/view/RecordingCanvas.java b/core/java/android/view/RecordingCanvas.java index fc7d828de12e..fbb862be54ef 100644 --- a/core/java/android/view/RecordingCanvas.java +++ b/core/java/android/view/RecordingCanvas.java @@ -34,7 +34,7 @@ import android.graphics.Rect; import android.graphics.RectF; import android.graphics.TemporaryBuffer; import android.text.GraphicsOperations; -import android.text.PrecomputedText; +import android.text.MeasuredText; import android.text.SpannableString; import android.text.SpannedString; import android.text.TextUtils; @@ -507,8 +507,8 @@ public class RecordingCanvas extends Canvas { TextUtils.getChars(text, contextStart, contextEnd, buf, 0); long measuredTextPtr = 0; int measuredTextOffset = 0; - if (text instanceof PrecomputedText) { - PrecomputedText mt = (PrecomputedText) text; + if (text instanceof MeasuredText) { + MeasuredText mt = (MeasuredText) text; int paraIndex = mt.findParaIndex(start); if (end <= mt.getParagraphEnd(paraIndex)) { // Only support if the target is in the same paragraph. @@ -641,7 +641,7 @@ public class RecordingCanvas extends Canvas { @FastNative private static native void nDrawTextRun(long nativeCanvas, char[] text, int start, int count, int contextStart, int contextCount, float x, float y, boolean isRtl, long nativePaint, - long nativePrecomputedText, int measuredTextOffset); + long nativeMeasuredText, int measuredTextOffset); @FastNative private static native void nDrawTextOnPath(long nativeCanvas, char[] text, int index, int count, diff --git a/core/java/android/view/RenderNodeAnimator.java b/core/java/android/view/RenderNodeAnimator.java index c4a716011765..d26a2f643ed4 100644 --- a/core/java/android/view/RenderNodeAnimator.java +++ b/core/java/android/view/RenderNodeAnimator.java @@ -158,7 +158,7 @@ public class RenderNodeAnimator extends Animator { } private void applyInterpolator() { - if (mInterpolator == null) return; + if (mInterpolator == null || mNativePtr == null) return; long ni; if (isNativeInterpolator(mInterpolator)) { diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java index 417a72530b72..5b1dd5c88cae 100644 --- a/core/java/android/view/accessibility/AccessibilityNodeInfo.java +++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java @@ -3203,7 +3203,7 @@ public class AccessibilityNodeInfo implements Parcelable { fieldIndex++; if (mConnectionId != DEFAULT.mConnectionId) nonDefaultFields |= bitAt(fieldIndex); fieldIndex++; - if (!Objects.equals(mChildNodeIds, DEFAULT.mChildNodeIds)) { + if (!LongArray.elementsEqual(mChildNodeIds, DEFAULT.mChildNodeIds)) { nonDefaultFields |= bitAt(fieldIndex); } fieldIndex++; diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index f6e771a9605e..5710db3ce8e0 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -80,8 +80,8 @@ import android.text.GraphicsOperations; import android.text.InputFilter; import android.text.InputType; import android.text.Layout; +import android.text.MeasuredText; import android.text.ParcelableSpan; -import android.text.PrecomputedText; import android.text.Selection; import android.text.SpanWatcher; import android.text.Spannable; @@ -637,7 +637,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener private CharSequence mText; private CharSequence mTransformed; private BufferType mBufferType = BufferType.NORMAL; - private PrecomputedText mPrecomputed; private CharSequence mHint; private Layout mHintLayout; @@ -4086,80 +4085,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } /** - * Gets the parameters for text layout precomputation, for use with {@link PrecomputedText} - * - * @return A current {@link PrecomputedText.Params} - */ - public @NonNull PrecomputedText.Params getTextMetricsParams() { - return new PrecomputedText.Params(new TextPaint(mTextPaint), getTextDirectionHeuristic(), - mBreakStrategy, mHyphenationFrequency); - } - - /** - * Apply the text layout parameter. - */ - public void setTextMetricsParams(@NonNull PrecomputedText.Params params) { - mTextPaint.set(params.getTextPaint()); - mTextDir = params.getTextDirection(); - mBreakStrategy = params.getBreakStrategy(); - mHyphenationFrequency = params.getHyphenationFrequency(); - if (mLayout != null) { - nullLayouts(); - requestLayout(); - invalidate(); - } - } - - /** - * Sets the precomputed text. - * - * If the parameters for the precomputed text is different from current text view parameters, - * apply the parameteres to the text view too. - * - * @param text A precomputed text. - */ - public void setPrecomputedTextAndParams(@NonNull PrecomputedText text) { - Preconditions.checkNotNull(text); - final PrecomputedText.Params params = text.getParams(); - if (!params.sameTextMetrics(getTextMetricsParams())) { - setTextMetricsParams(params); - } - setText(text.getText()); - if (mTransformed != text.getText()) { - // setText modified given text for some reasons, selection, transformation, etc. - // Can't use computed result. - return; - } else { - mPrecomputed = text; - } - } - - /** - * Sets the precomputed text. - * - * If the parameters for the precomputed text is different from current text view parameters, - * throws {@link IllegalArgumentException}. - * - * @param text A precomputed text. - */ - public void setPrecomputedTextOrThrow(@NonNull PrecomputedText text) { - Preconditions.checkNotNull(text); - final PrecomputedText.Params params = text.getParams(); - if (!params.sameTextMetrics(getTextMetricsParams())) { - throw new IllegalArgumentException( - "The precomputed configuration is different from this TextView."); - } - setText(text.getText()); - if (mTransformed != text.getText()) { - // setText modified given text for some reasons, selection, transformation, etc. - // Can't use computed result. - // TODO: Do we throw an exception here too? - } else { - mPrecomputed = text; - } - } - - /** * Set justification mode. The default value is {@link Layout#JUSTIFICATION_MODE_NONE}. If the * last line is too short for justification, the last line will be displayed with the * alignment set by {@link android.view.View#setTextAlignment}. @@ -5594,7 +5519,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener private void setText(CharSequence text, BufferType type, boolean notifyBefore, int oldlen) { - mPrecomputed = null; mTextSetFromXmlOrResourceId = false; if (text == null) { text = ""; @@ -5653,7 +5577,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener if (imm != null) imm.restartInput(this); } else if (type == BufferType.SPANNABLE || mMovement != null) { text = mSpannableFactory.newSpannable(text); - } else if (!(text instanceof CharWrapper)) { + } else if (!(text instanceof MeasuredText || text instanceof CharWrapper)) { text = TextUtils.stringOrSpannedString(text); } @@ -8320,8 +8244,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener result = builder.build(); } else { if (boring == UNKNOWN_BORING) { - boring = BoringLayout.isBoring(mTransformed, mPrecomputed, mTextPaint, mTextDir, - mBoring); + boring = BoringLayout.isBoring(mTransformed, mTextPaint, mTextDir, mBoring); if (boring != null) { mBoring = boring; } @@ -8359,15 +8282,9 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } } if (result == null) { - StaticLayout.Builder builder; - if (mPrecomputed != null) { - builder = StaticLayout.Builder.obtain(mPrecomputed, 0, - mPrecomputed.getText().length(), mTextPaint, wantWidth); - } else { - builder = StaticLayout.Builder.obtain(mTransformed, 0, mTransformed.length(), - mTextPaint, wantWidth); - } - builder.setAlignment(alignment) + StaticLayout.Builder builder = StaticLayout.Builder.obtain(mTransformed, + 0, mTransformed.length(), mTextPaint, wantWidth) + .setAlignment(alignment) .setTextDirection(mTextDir) .setLineSpacing(mSpacingAdd, mSpacingMult) .setIncludePad(mIncludePad) @@ -8494,8 +8411,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } if (des < 0) { - boring = BoringLayout.isBoring(mTransformed, mPrecomputed, mTextPaint, mTextDir, - mBoring); + boring = BoringLayout.isBoring(mTransformed, mTextPaint, mTextDir, mBoring); if (boring != null) { mBoring = boring; } @@ -11780,9 +11696,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } /** - * Returns the current {@link TextDirectionHeuristic} - * - * @return A {@link TextDirectionHeuristic}. * @hide */ protected TextDirectionHeuristic getTextDirectionHeuristic() { diff --git a/core/jni/OWNERS b/core/jni/OWNERS new file mode 100644 index 000000000000..e37452d98eec --- /dev/null +++ b/core/jni/OWNERS @@ -0,0 +1,7 @@ +# Camera +per-file *Camera*,*camera* = cychen@google.com +per-file *Camera*,*camera* = epeev@google.com +per-file *Camera*,*camera* = etalvala@google.com +per-file *Camera*,*camera* = shuzhenwang@google.com +per-file *Camera*,*camera* = yinchiayeh@google.com +per-file *Camera*,*camera* = zhijunhe@google.com diff --git a/core/jni/android/graphics/Paint.cpp b/core/jni/android/graphics/Paint.cpp index 2c05d0b976fc..482d028a67f3 100644 --- a/core/jni/android/graphics/Paint.cpp +++ b/core/jni/android/graphics/Paint.cpp @@ -1008,23 +1008,6 @@ namespace PaintGlue { return paint->getLooper() && paint->getLooper()->asABlurShadow(nullptr); } - static jboolean equalsForTextMeasurement(jlong lPaint, jlong rPaint) { - if (lPaint == rPaint) { - return true; - } - Paint* leftPaint = reinterpret_cast<Paint*>(lPaint); - Paint* rightPaint = reinterpret_cast<Paint*>(rPaint); - - const Typeface* leftTypeface = Typeface::resolveDefault(leftPaint->getAndroidTypeface()); - const Typeface* rightTypeface = Typeface::resolveDefault(rightPaint->getAndroidTypeface()); - minikin::MinikinPaint leftMinikinPaint - = MinikinUtils::prepareMinikinPaint(leftPaint, leftTypeface); - minikin::MinikinPaint rightMinikinPaint - = MinikinUtils::prepareMinikinPaint(rightPaint, rightTypeface); - - return leftMinikinPaint == rightMinikinPaint; - } - }; // namespace PaintGlue static const JNINativeMethod methods[] = { @@ -1124,8 +1107,7 @@ static const JNINativeMethod methods[] = { {"nGetStrikeThruPosition","(J)F", (void*) PaintGlue::getStrikeThruPosition}, {"nGetStrikeThruThickness","(J)F", (void*) PaintGlue::getStrikeThruThickness}, {"nSetShadowLayer", "(JFFFI)V", (void*)PaintGlue::setShadowLayer}, - {"nHasShadowLayer", "(J)Z", (void*)PaintGlue::hasShadowLayer}, - {"nEqualsForTextMeasurement", "(JJ)Z", (void*)PaintGlue::equalsForTextMeasurement}, + {"nHasShadowLayer", "(J)Z", (void*)PaintGlue::hasShadowLayer} }; int register_android_graphics_Paint(JNIEnv* env) { diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 0218750d0f87..1f4425fef271 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -3316,4 +3316,5 @@ <string-array name="config_wearActivityModeRadios"> <item>"wifi"</item> </string-array> + </resources> diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index 0efb6f91fce0..59c742e5d499 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -547,7 +547,7 @@ <string name="global_action_voice_assist">Voice Assist</string> <!-- label for item that locks the phone and enforces that it can't be unlocked without strong authentication. [CHAR LIMIT=15] --> - <string name="global_action_lockdown">Enter lockdown</string> + <string name="global_action_lockdown">Lockdown</string> <!-- Text to use when the number in a notification info is too large (greater than status_bar_notification_info_maxnum, defined in diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 5a9dc7fa92be..b69ded80009c 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -3262,4 +3262,5 @@ <java-symbol type="string" name="zen_upgrade_notification_title" /> <java-symbol type="string" name="zen_upgrade_notification_content" /> + </resources> diff --git a/core/tests/coretests/src/android/provider/SettingsBackupTest.java b/core/tests/coretests/src/android/provider/SettingsBackupTest.java index dc8ed9efaa76..85cf9b6c3c91 100644 --- a/core/tests/coretests/src/android/provider/SettingsBackupTest.java +++ b/core/tests/coretests/src/android/provider/SettingsBackupTest.java @@ -495,6 +495,7 @@ public class SettingsBackupTest { Settings.Secure.DOZE_ALWAYS_ON, Settings.Secure.DOZE_PULSE_ON_LONG_PRESS, Settings.Secure.EMERGENCY_ASSISTANCE_APPLICATION, + Settings.Secure.ENABLED_INPUT_METHODS, // Intentionally removed in P Settings.Secure.ENABLED_NOTIFICATION_ASSISTANT, Settings.Secure.ENABLED_NOTIFICATION_LISTENERS, Settings.Secure.ENABLED_NOTIFICATION_POLICY_ACCESS_PACKAGES, diff --git a/graphics/java/android/graphics/BaseCanvas.java b/graphics/java/android/graphics/BaseCanvas.java index 07df0454362c..eacb727099ed 100644 --- a/graphics/java/android/graphics/BaseCanvas.java +++ b/graphics/java/android/graphics/BaseCanvas.java @@ -22,7 +22,7 @@ import android.annotation.Nullable; import android.annotation.Size; import android.graphics.Canvas.VertexMode; import android.text.GraphicsOperations; -import android.text.PrecomputedText; +import android.text.MeasuredText; import android.text.SpannableString; import android.text.SpannedString; import android.text.TextUtils; @@ -487,8 +487,8 @@ public abstract class BaseCanvas { TextUtils.getChars(text, contextStart, contextEnd, buf, 0); long measuredTextPtr = 0; int measuredTextOffset = 0; - if (text instanceof PrecomputedText) { - PrecomputedText mt = (PrecomputedText) text; + if (text instanceof MeasuredText) { + MeasuredText mt = (MeasuredText) text; int paraIndex = mt.findParaIndex(start); if (end <= mt.getParagraphEnd(paraIndex)) { // Only suppor the same paragraph. @@ -647,7 +647,7 @@ public abstract class BaseCanvas { private static native void nDrawTextRun(long nativeCanvas, char[] text, int start, int count, int contextStart, int contextCount, float x, float y, boolean isRtl, long nativePaint, - long nativePrecomputedText, int measuredTextOffset); + long nativeMeasuredText, int measuredTextOffset); private static native void nDrawTextOnPath(long nativeCanvas, char[] text, int index, int count, long nativePath, float hOffset, float vOffset, int bidiFlags, long nativePaint); diff --git a/graphics/java/android/graphics/Paint.java b/graphics/java/android/graphics/Paint.java index 42dac38affba..ed147e9e2ec7 100644 --- a/graphics/java/android/graphics/Paint.java +++ b/graphics/java/android/graphics/Paint.java @@ -2835,16 +2835,6 @@ public class Paint { return result; } - /** - * Returns true of the passed {@link Paint} will have the same effect on text measurement - * - * @param other A {@link Paint} object. - * @return true if the other {@link Paint} has the same effect on text measurement. - */ - public boolean equalsForTextMeasurement(@NonNull Paint other) { - return nEqualsForTextMeasurement(mNativePaint, other.mNativePaint); - } - // regular JNI private static native long nGetNativeFinalizer(); private static native long nInit(); @@ -3012,6 +3002,4 @@ public class Paint { private static native float nGetStrikeThruThickness(long paintPtr); @CriticalNative private static native void nSetTextSize(long paintPtr, float textSize); - @CriticalNative - private static native boolean nEqualsForTextMeasurement(long leftPaintPtr, long rightPaintPtr); } diff --git a/packages/SettingsLib/src/com/android/settingslib/Utils.java b/packages/SettingsLib/src/com/android/settingslib/Utils.java index 1f67dfb568a6..9947dec14d48 100644 --- a/packages/SettingsLib/src/com/android/settingslib/Utils.java +++ b/packages/SettingsLib/src/com/android/settingslib/Utils.java @@ -1,6 +1,7 @@ package com.android.settingslib; import android.annotation.ColorInt; +import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.pm.PackageInfo; @@ -17,10 +18,13 @@ import android.graphics.drawable.Drawable; import android.location.LocationManager; import android.net.ConnectivityManager; import android.os.BatteryManager; +import android.os.SystemProperties; import android.os.UserHandle; import android.os.UserManager; import android.print.PrintManager; import android.provider.Settings; + +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.UserIcons; import com.android.settingslib.drawable.UserIconDrawable; import com.android.settingslib.wrapper.LocationManagerWrapper; @@ -30,6 +34,9 @@ public class Utils { private static final String CURRENT_MODE_KEY = "CURRENT_MODE"; private static final String NEW_MODE_KEY = "NEW_MODE"; + @VisibleForTesting + static final String STORAGE_MANAGER_SHOW_OPT_IN_PROPERTY = + "ro.storage_manager.show_opt_in"; private static Signature[] sSystemSignature; private static String sPermissionControllerPackageName; @@ -37,11 +44,11 @@ public class Utils { private static String sSharedSystemSharedLibPackageName; static final int[] WIFI_PIE = { - com.android.internal.R.drawable.ic_wifi_signal_0, - com.android.internal.R.drawable.ic_wifi_signal_1, - com.android.internal.R.drawable.ic_wifi_signal_2, - com.android.internal.R.drawable.ic_wifi_signal_3, - com.android.internal.R.drawable.ic_wifi_signal_4 + com.android.internal.R.drawable.ic_wifi_signal_0, + com.android.internal.R.drawable.ic_wifi_signal_1, + com.android.internal.R.drawable.ic_wifi_signal_2, + com.android.internal.R.drawable.ic_wifi_signal_3, + com.android.internal.R.drawable.ic_wifi_signal_4 }; public static void updateLocationEnabled(Context context, boolean enabled, int userId, @@ -262,7 +269,7 @@ public class Utils { */ public static boolean isSystemPackage(Resources resources, PackageManager pm, PackageInfo pkg) { if (sSystemSignature == null) { - sSystemSignature = new Signature[]{ getSystemSignature(pm) }; + sSystemSignature = new Signature[]{getSystemSignature(pm)}; } if (sPermissionControllerPackageName == null) { sPermissionControllerPackageName = pm.getPermissionControllerPackageName(); @@ -274,7 +281,7 @@ public class Utils { sSharedSystemSharedLibPackageName = pm.getSharedSystemSharedLibraryPackageName(); } return (sSystemSignature[0] != null - && sSystemSignature[0].equals(getFirstSignature(pkg))) + && sSystemSignature[0].equals(getFirstSignature(pkg))) || pkg.packageName.equals(sPermissionControllerPackageName) || pkg.packageName.equals(sServicesSystemSharedLibPackageName) || pkg.packageName.equals(sSharedSystemSharedLibPackageName) @@ -312,7 +319,6 @@ public class Utils { * Returns the Wifi icon resource for a given RSSI level. * * @param level The number of bars to show (0-4) - * * @throws IllegalArgumentException if an invalid RSSI level is given. */ public static int getWifiIconResource(int level) { @@ -342,4 +348,19 @@ public class Utils { return !context.getSystemService(ConnectivityManager.class) .isNetworkSupported(ConnectivityManager.TYPE_MOBILE); } + + /** Returns if the automatic storage management feature is turned on or not. **/ + public static boolean isStorageManagerEnabled(Context context) { + boolean isDefaultOn; + try { + // Turn off by default if the opt-in was shown. + isDefaultOn = !SystemProperties.getBoolean(STORAGE_MANAGER_SHOW_OPT_IN_PROPERTY, true); + } catch (Resources.NotFoundException e) { + isDefaultOn = false; + } + return Settings.Secure.getInt(context.getContentResolver(), + Settings.Secure.AUTOMATIC_STORAGE_MANAGER_ENABLED, + isDefaultOn ? 1 : 0) + != 0; + } } diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java index f69944006a87..b380ac53ddad 100644 --- a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java +++ b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java @@ -17,6 +17,7 @@ package com.android.settingslib.wifi; import android.annotation.IntDef; +import android.annotation.MainThread; import android.annotation.Nullable; import android.app.AppGlobals; import android.content.Context; @@ -42,6 +43,8 @@ import android.net.wifi.WifiManager; import android.net.wifi.WifiNetworkScoreCache; import android.net.wifi.hotspot2.PasspointConfiguration; import android.os.Bundle; +import android.os.Handler; +import android.os.Looper; import android.os.Parcelable; import android.os.RemoteException; import android.os.ServiceManager; @@ -57,6 +60,7 @@ import android.util.Log; import com.android.internal.annotations.VisibleForTesting; import com.android.settingslib.R; +import com.android.settingslib.utils.ThreadUtils; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -68,7 +72,14 @@ import java.util.Map; import java.util.Set; import java.util.concurrent.atomic.AtomicInteger; - +/** + * Represents a selectable Wifi Network for use in various wifi selection menus backed by + * {@link WifiTracker}. + * + * <p>An AccessPoint, which would be more fittingly named "WifiNetwork", is an aggregation of + * {@link ScanResult ScanResults} along with pertinent metadata (e.g. current connection info, + * network scores) required to successfully render the network to the user. + */ public class AccessPoint implements Comparable<AccessPoint> { static final String TAG = "SettingsLib.AccessPoint"; @@ -288,11 +299,6 @@ public class AccessPoint implements Comparable<AccessPoint> { mId = sLastId.incrementAndGet(); } - AccessPoint(Context context, AccessPoint other) { - mContext = context; - copyFrom(other); - } - AccessPoint(Context context, Collection<ScanResult> results) { mContext = context; @@ -346,33 +352,6 @@ public class AccessPoint implements Comparable<AccessPoint> { } /** - * Copy accesspoint information. NOTE: We do not copy tag information because that is never - * set on the internal copy. - */ - void copyFrom(AccessPoint that) { - this.ssid = that.ssid; - this.bssid = that.bssid; - this.security = that.security; - this.mKey = that.mKey; - this.networkId = that.networkId; - this.pskType = that.pskType; - this.mConfig = that.mConfig; //TODO: Watch out, this object is mutated. - this.mRssi = that.mRssi; - this.mInfo = that.mInfo; - this.mNetworkInfo = that.mNetworkInfo; - this.mScanResults.clear(); - this.mScanResults.addAll(that.mScanResults); - this.mScoredNetworkCache.clear(); - this.mScoredNetworkCache.putAll(that.mScoredNetworkCache); - this.mId = that.mId; - this.mSpeed = that.mSpeed; - this.mIsScoredNetworkMetered = that.mIsScoredNetworkMetered; - this.mIsCarrierAp = that.mIsCarrierAp; - this.mCarrierApEapType = that.mCarrierApEapType; - this.mCarrierName = that.mCarrierName; - } - - /** * Returns a negative integer, zero, or a positive integer if this AccessPoint is less than, * equal to, or greater than the other AccessPoint. * @@ -467,7 +446,7 @@ public class AccessPoint implements Comparable<AccessPoint> { } builder.append(",metered=").append(isMetered()); - if (WifiTracker.sVerboseLogging) { + if (isVerboseLoggingEnabled()) { builder.append(",rssi=").append(mRssi); builder.append(",scan cache size=").append(mScanResults.size()); } @@ -546,7 +525,7 @@ public class AccessPoint implements Comparable<AccessPoint> { mSpeed = generateAverageSpeedForSsid(); boolean changed = oldSpeed != mSpeed; - if(WifiTracker.sVerboseLogging && changed) { + if(isVerboseLoggingEnabled() && changed) { Log.i(TAG, String.format("%s: Set speed to %d", ssid, mSpeed)); } return changed; @@ -577,7 +556,7 @@ public class AccessPoint implements Comparable<AccessPoint> { } } int speed = count == 0 ? Speed.NONE : totalSpeed / count; - if (WifiTracker.sVerboseLogging) { + if (isVerboseLoggingEnabled()) { Log.i(TAG, String.format("%s generated fallback speed is: %d", getSsidStr(), speed)); } return roundToClosestSpeedEnum(speed); @@ -913,7 +892,7 @@ public class AccessPoint implements Comparable<AccessPoint> { } } - if (WifiTracker.sVerboseLogging) { + if (isVerboseLoggingEnabled()) { summary.append(WifiUtils.buildLoggingSummary(this, config)); } @@ -1070,12 +1049,12 @@ public class AccessPoint implements Comparable<AccessPoint> { // Only update labels on visible rssi changes updateSpeed(); if (mAccessPointListener != null) { - mAccessPointListener.onLevelChanged(this); + ThreadUtils.postOnMainThread(() -> mAccessPointListener.onLevelChanged(this)); } } if (mAccessPointListener != null) { - mAccessPointListener.onAccessPointChanged(this); + ThreadUtils.postOnMainThread(() -> mAccessPointListener.onAccessPointChanged(this)); } if (!scanResults.isEmpty()) { @@ -1123,10 +1102,10 @@ public class AccessPoint implements Comparable<AccessPoint> { mNetworkInfo = null; } if (updated && mAccessPointListener != null) { - mAccessPointListener.onAccessPointChanged(this); + ThreadUtils.postOnMainThread(() -> mAccessPointListener.onAccessPointChanged(this)); if (oldLevel != getLevel() /* current level */) { - mAccessPointListener.onLevelChanged(this); + ThreadUtils.postOnMainThread(() -> mAccessPointListener.onLevelChanged(this)); } } @@ -1137,7 +1116,7 @@ public class AccessPoint implements Comparable<AccessPoint> { mConfig = config; networkId = config != null ? config.networkId : WifiConfiguration.INVALID_NETWORK_ID; if (mAccessPointListener != null) { - mAccessPointListener.onAccessPointChanged(this); + ThreadUtils.postOnMainThread(() -> mAccessPointListener.onAccessPointChanged(this)); } } @@ -1333,8 +1312,44 @@ public class AccessPoint implements Comparable<AccessPoint> { return string; } + /** + * Callbacks relaying changes to the AccessPoint representation. + * + * <p>All methods are invoked on the Main Thread. + */ public interface AccessPointListener { - void onAccessPointChanged(AccessPoint accessPoint); - void onLevelChanged(AccessPoint accessPoint); + /** + * Indicates a change to the externally visible state of the AccessPoint trigger by an + * update of ScanResults, saved configuration state, connection state, or score + * (labels/metered) state. + * + * <p>Clients should refresh their view of the AccessPoint to match the updated state when + * this is invoked. Overall this method is extraneous if clients are listening to + * {@link WifiTracker.WifiListener#onAccessPointsChanged()} callbacks. + * + * <p>Examples of changes include signal strength, connection state, speed label, and + * generally anything that would impact the summary string. + * + * @param accessPoint The accessPoint object the listener was registered on which has + * changed + */ + @MainThread void onAccessPointChanged(AccessPoint accessPoint); + + /** + * Indicates the "wifi pie signal level" has changed, retrieved via calls to + * {@link AccessPoint#getLevel()}. + * + * <p>This call is a subset of {@link #onAccessPointChanged(AccessPoint)} , hence is also + * extraneous if the client is already reacting to that or the + * {@link WifiTracker.WifiListener#onAccessPointsChanged()} callbacks. + * + * @param accessPoint The accessPoint object the listener was registered on whose level has + * changed + */ + @MainThread void onLevelChanged(AccessPoint accessPoint); + } + + private static boolean isVerboseLoggingEnabled() { + return WifiTracker.sVerboseLogging || Log.isLoggable(TAG, Log.VERBOSE); } } diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java index fac585e06306..ae544dd6dbe8 100644 --- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java +++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java @@ -15,6 +15,7 @@ */ package com.android.settingslib.wifi; +import android.annotation.AnyThread; import android.annotation.MainThread; import android.content.BroadcastReceiver; import android.content.Context; @@ -48,8 +49,6 @@ import android.text.format.DateUtils; import android.util.ArrayMap; import android.util.ArraySet; import android.util.Log; -import android.util.SparseArray; -import android.util.SparseIntArray; import android.widget.Toast; import com.android.settingslib.R; @@ -58,6 +57,7 @@ import com.android.settingslib.core.lifecycle.LifecycleObserver; import com.android.settingslib.core.lifecycle.events.OnDestroy; import com.android.settingslib.core.lifecycle.events.OnStart; import com.android.settingslib.core.lifecycle.events.OnStop; +import com.android.settingslib.utils.ThreadUtils; import java.io.PrintWriter; import java.util.ArrayList; @@ -88,8 +88,17 @@ public class WifiTracker implements LifecycleObserver, OnStart, OnStop, OnDestro return Log.isLoggable(TAG, Log.DEBUG); } - /** verbose logging flag. this flag is set thru developer debugging options - * and used so as to assist with in-the-field WiFi connectivity debugging */ + private static boolean isVerboseLoggingEnabled() { + return WifiTracker.sVerboseLogging || Log.isLoggable(TAG, Log.VERBOSE); + } + + /** + * Verbose logging flag set thru developer debugging options and used so as to assist with + * in-the-field WiFi connectivity debugging. + * + * <p>{@link #isVerboseLoggingEnabled()} should be read rather than referencing this value + * directly, to ensure adb TAG level verbose settings are respected. + */ public static boolean sVerboseLogging; // TODO: Allow control of this? @@ -104,7 +113,6 @@ public class WifiTracker implements LifecycleObserver, OnStart, OnStop, OnDestro private final NetworkRequest mNetworkRequest; private final AtomicBoolean mConnected = new AtomicBoolean(false); private final WifiListener mListener; - @VisibleForTesting MainHandler mMainHandler; @VisibleForTesting WorkHandler mWorkHandler; private HandlerThread mWorkThread; @@ -113,35 +121,17 @@ public class WifiTracker implements LifecycleObserver, OnStart, OnStop, OnDestro @GuardedBy("mLock") private boolean mRegistered; - /** - * The externally visible access point list. - * - * Updated using main handler. Clone of this collection is returned from - * {@link #getAccessPoints()} - */ - private final List<AccessPoint> mAccessPoints = new ArrayList<>(); - - /** - * The internal list of access points, synchronized on itself. - * - * Never exposed outside this class. - */ + /** The list of AccessPoints, aggregated visible ScanResults with metadata. */ @GuardedBy("mLock") private final List<AccessPoint> mInternalAccessPoints = new ArrayList<>(); /** * Synchronization lock for managing concurrency between main and worker threads. * - * <p>This lock should be held for all background work. - * TODO(b/37674366): Remove the worker thread so synchronization is no longer necessary. + * <p>This lock should be held for all modifications to {@link #mInternalAccessPoints}. */ private final Object mLock = new Object(); - //visible to both worker and main thread. - @GuardedBy("mLock") - private final AccessPointListenerAdapter mAccessPointListenerAdapter - = new AccessPointListenerAdapter(); - private final HashMap<String, Integer> mSeenBssids = new HashMap<>(); // TODO(sghuman): Change this to be keyed on AccessPoint.getKey @@ -161,6 +151,12 @@ public class WifiTracker implements LifecycleObserver, OnStart, OnStop, OnDestro @VisibleForTesting Scanner mScanner; + /** + * Tracks whether fresh scan results have been received since scanning start. + * + * <p>If this variable is false, we will not evict the scan result cache or invoke callbacks + * so that we do not update the UI with stale data / clear out existing UI elements prematurely. + */ @GuardedBy("mLock") private boolean mStaleScanResults = true; @@ -209,12 +205,11 @@ public class WifiTracker implements LifecycleObserver, OnStart, OnStop, OnDestro NetworkScoreManager networkScoreManager, IntentFilter filter) { mContext = context; - mMainHandler = new MainHandler(Looper.getMainLooper()); mWifiManager = wifiManager; mListener = new WifiListenerWrapper(wifiListener); mConnectivityManager = connectivityManager; - // check if verbose logging has been turned on or off + // check if verbose logging developer option has been turned on or off sVerboseLogging = (mWifiManager.getVerboseLoggingLevel() > 0); mFilter = filter; @@ -226,6 +221,7 @@ public class WifiTracker implements LifecycleObserver, OnStart, OnStop, OnDestro mNetworkScoreManager = networkScoreManager; + // TODO(sghuman): Remove this and create less hacky solution for testing final HandlerThread workThread = new HandlerThread(TAG + "{" + Integer.toHexString(System.identityHashCode(this)) + "}", Process.THREAD_PRIORITY_BACKGROUND); @@ -238,6 +234,8 @@ public class WifiTracker implements LifecycleObserver, OnStart, OnStop, OnDestro * @param workThread substitute Handler thread, for testing purposes only */ @VisibleForTesting + // TODO(sghuman): Remove this method, this needs to happen in a factory method and be passed in + // during construction void setWorkThread(HandlerThread workThread) { mWorkThread = workThread; mWorkHandler = new WorkHandler(workThread.getLooper()); @@ -270,32 +268,29 @@ public class WifiTracker implements LifecycleObserver, OnStart, OnStop, OnDestro mLastNetworkInfo = mConnectivityManager.getNetworkInfo(mWifiManager.getCurrentNetwork()); final List<ScanResult> newScanResults = mWifiManager.getScanResults(); - if (sVerboseLogging) { + if (isVerboseLoggingEnabled()) { Log.i(TAG, "Fetched scan results: " + newScanResults); } List<WifiConfiguration> configs = mWifiManager.getConfiguredNetworks(); mInternalAccessPoints.clear(); updateAccessPointsLocked(newScanResults, configs); - - // Synchronously copy access points - mMainHandler.removeMessages(MainHandler.MSG_ACCESS_POINT_CHANGED); - mMainHandler.handleMessage( - Message.obtain(mMainHandler, MainHandler.MSG_ACCESS_POINT_CHANGED)); - if (sVerboseLogging) { - Log.i(TAG, "force update - external access point list:\n" + mAccessPoints); - } } } /** * Temporarily stop scanning for wifi networks. + * + * <p>Sets {@link #mStaleScanResults} to true. */ - public void pauseScanning() { + private void pauseScanning() { if (mScanner != null) { mScanner.pause(); mScanner = null; } + synchronized (mLock) { + mStaleScanResults = true; + } } /** @@ -387,11 +382,9 @@ public class WifiTracker implements LifecycleObserver, OnStart, OnStop, OnDestro mRegistered = false; } unregisterScoreCache(); - pauseScanning(); + pauseScanning(); // and set mStaleScanResults mWorkHandler.removePendingMessages(); - mMainHandler.removePendingMessages(); - mStaleScanResults = true; } } @@ -409,12 +402,19 @@ public class WifiTracker implements LifecycleObserver, OnStart, OnStop, OnDestro } /** - * Gets the current list of access points. Should be called from main thread, otherwise - * expect inconsistencies + * Gets the current list of access points. + * + * <p>This method is can be called on an abitrary thread by clients, but is normally called on + * the UI Thread by the rendering App. */ - @MainThread + @AnyThread public List<AccessPoint> getAccessPoints() { - return new ArrayList<>(mAccessPoints); + // TODO(sghuman): Investigate how to eliminate or reduce the need for locking now that we + // have transitioned to a single worker thread model. + + synchronized (mLock) { + return new ArrayList<>(mInternalAccessPoints); + } } public WifiManager getManager() { @@ -447,6 +447,8 @@ public class WifiTracker implements LifecycleObserver, OnStart, OnStop, OnDestro } private void handleResume() { + // TODO(sghuman): Investigate removing this and replacing it with a cache eviction call + // instead. mScanResultCache.clear(); mSeenBssids.clear(); } @@ -509,7 +511,7 @@ public class WifiTracker implements LifecycleObserver, OnStart, OnStop, OnDestro private void updateAccessPoints() { List<WifiConfiguration> configs = mWifiManager.getConfiguredNetworks(); final List<ScanResult> newScanResults = mWifiManager.getScanResults(); - if (sVerboseLogging) { + if (isVerboseLoggingEnabled()) { Log.i(TAG, "Fetched scan results: " + newScanResults); } @@ -524,11 +526,15 @@ public class WifiTracker implements LifecycleObserver, OnStart, OnStop, OnDestro * Update the internal list of access points. * * <p>Do not call directly (except for forceUpdate), use {@link #updateAccessPoints()} which - * respects {@link #mStaleScanResults}. + * acquires the lock first. */ @GuardedBy("mLock") private void updateAccessPointsLocked(final List<ScanResult> newScanResults, List<WifiConfiguration> configs) { + // TODO(sghuman): Reduce the synchronization time by only holding the lock when + // modifying lists exposed to operations on the MainThread (getAccessPoints, stopTracking, + // startTracking, etc). + WifiConfiguration connectionConfig = null; if (mLastInfo != null) { connectionConfig = getWifiConfigurationForNetworkId( @@ -634,7 +640,7 @@ public class WifiTracker implements LifecycleObserver, OnStart, OnStop, OnDestro mInternalAccessPoints.clear(); mInternalAccessPoints.addAll(accessPoints); - mMainHandler.sendEmptyMessage(MainHandler.MSG_ACCESS_POINT_CHANGED); + conditionallyNotifyListeners(); } @VisibleForTesting @@ -650,7 +656,6 @@ public class WifiTracker implements LifecycleObserver, OnStart, OnStop, OnDestro } } final AccessPoint accessPoint = new AccessPoint(mContext, scanResults); - accessPoint.setListener(mAccessPointListenerAdapter); return accessPoint; } @@ -661,16 +666,17 @@ public class WifiTracker implements LifecycleObserver, OnStart, OnStop, OnDestro if (cache.get(i).matches(config)) { AccessPoint ret = cache.remove(i); ret.loadConfig(config); + return ret; } } final AccessPoint accessPoint = new AccessPoint(mContext, config); - accessPoint.setListener(mAccessPointListenerAdapter); return accessPoint; } private void updateNetworkInfo(NetworkInfo networkInfo) { - /* sticky broadcasts can call this when wifi is disabled */ + + /* Sticky broadcasts can call this when wifi is disabled */ if (!mWifiManager.isWifiEnabled()) { clearAccessPointsAndConditionallyUpdate(); return; @@ -681,6 +687,10 @@ public class WifiTracker implements LifecycleObserver, OnStart, OnStop, OnDestro if (DBG()) { Log.d(TAG, "mLastNetworkInfo set: " + mLastNetworkInfo); } + + if(networkInfo.isConnected() != mConnected.getAndSet(networkInfo.isConnected())) { + mListener.onConnectedChanged(); + } } WifiConfiguration connectionConfig = null; @@ -711,18 +721,25 @@ public class WifiTracker implements LifecycleObserver, OnStart, OnStop, OnDestro } } - if (reorder) Collections.sort(mInternalAccessPoints); - if (updated) mMainHandler.sendEmptyMessage(MainHandler.MSG_ACCESS_POINT_CHANGED); + if (reorder) { + Collections.sort(mInternalAccessPoints); + } + if (updated) { + conditionallyNotifyListeners(); + } } } + /** + * Clears the access point list and conditionally invokes + * {@link WifiListener#onAccessPointsChanged()} if required (i.e. the list was not already + * empty). + */ private void clearAccessPointsAndConditionallyUpdate() { synchronized (mLock) { if (!mInternalAccessPoints.isEmpty()) { mInternalAccessPoints.clear(); - if (!mMainHandler.hasMessages(MainHandler.MSG_ACCESS_POINT_CHANGED)) { - mMainHandler.sendEmptyMessage(MainHandler.MSG_ACCESS_POINT_CHANGED); - } + mListener.onAccessPointsChanged(); } } } @@ -745,27 +762,26 @@ public class WifiTracker implements LifecycleObserver, OnStart, OnStop, OnDestro } if (updated) { Collections.sort(mInternalAccessPoints); - mMainHandler.sendEmptyMessage(MainHandler.MSG_ACCESS_POINT_CHANGED); + conditionallyNotifyListeners(); } } } - private void updateWifiState(int state) { - mWorkHandler.obtainMessage(WorkHandler.MSG_UPDATE_WIFI_STATE, state, 0).sendToTarget(); - if (!mWifiManager.isWifiEnabled()) { - clearAccessPointsAndConditionallyUpdate(); - } - } - @VisibleForTesting final BroadcastReceiver mReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { + // No work should be performed in this Receiver, instead all operations should be passed + // off to the WorkHandler to avoid concurrent modification exceptions. + String action = intent.getAction(); if (WifiManager.WIFI_STATE_CHANGED_ACTION.equals(action)) { - updateWifiState(intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE, - WifiManager.WIFI_STATE_UNKNOWN)); + mWorkHandler.obtainMessage( + WorkHandler.MSG_UPDATE_WIFI_STATE, + intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE, + WifiManager.WIFI_STATE_UNKNOWN), + 0).sendToTarget(); } else if (WifiManager.SCAN_RESULTS_AVAILABLE_ACTION.equals(action)) { mWorkHandler .obtainMessage( @@ -778,12 +794,6 @@ public class WifiTracker implements LifecycleObserver, OnStart, OnStop, OnDestro mWorkHandler.sendEmptyMessage(WorkHandler.MSG_UPDATE_ACCESS_POINTS); } else if (WifiManager.NETWORK_STATE_CHANGED_ACTION.equals(action)) { NetworkInfo info = intent.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO); - - if(mConnected.get() != info.isConnected()) { - mConnected.set(info.isConnected()); - mMainHandler.sendEmptyMessage(MainHandler.MSG_CONNECTED_CHANGED); - } - mWorkHandler.obtainMessage(WorkHandler.MSG_UPDATE_NETWORK_INFO, info) .sendToTarget(); mWorkHandler.sendEmptyMessage(WorkHandler.MSG_UPDATE_ACCESS_POINTS); @@ -808,68 +818,8 @@ public class WifiTracker implements LifecycleObserver, OnStart, OnStop, OnDestro } @VisibleForTesting - final class MainHandler extends Handler { - @VisibleForTesting static final int MSG_CONNECTED_CHANGED = 0; - @VisibleForTesting static final int MSG_WIFI_STATE_CHANGED = 1; - @VisibleForTesting static final int MSG_ACCESS_POINT_CHANGED = 2; - private static final int MSG_RESUME_SCANNING = 3; - private static final int MSG_PAUSE_SCANNING = 4; - - public MainHandler(Looper looper) { - super(looper); - } - - @Override - public void handleMessage(Message msg) { - if (mListener == null) { - return; - } - switch (msg.what) { - case MSG_CONNECTED_CHANGED: - mListener.onConnectedChanged(); - break; - case MSG_WIFI_STATE_CHANGED: - mListener.onWifiStateChanged(msg.arg1); - break; - case MSG_ACCESS_POINT_CHANGED: - // Only notify listeners of changes if we have fresh scan results, otherwise the - // UI will be updated with stale results. We want to copy the APs regardless, - // for instances where forceUpdate was invoked by the caller. - if (mStaleScanResults) { - copyAndNotifyListeners(false /*notifyListeners*/); - } else { - copyAndNotifyListeners(true /*notifyListeners*/); - mListener.onAccessPointsChanged(); - } - break; - case MSG_RESUME_SCANNING: - if (mScanner != null) { - mScanner.resume(); - } - break; - case MSG_PAUSE_SCANNING: - if (mScanner != null) { - mScanner.pause(); - } - synchronized (mLock) { - mStaleScanResults = true; - } - break; - } - } - - void removePendingMessages() { - removeMessages(MSG_ACCESS_POINT_CHANGED); - removeMessages(MSG_CONNECTED_CHANGED); - removeMessages(MSG_WIFI_STATE_CHANGED); - removeMessages(MSG_PAUSE_SCANNING); - removeMessages(MSG_RESUME_SCANNING); - } - } - - @VisibleForTesting final class WorkHandler extends Handler { - private static final int MSG_UPDATE_ACCESS_POINTS = 0; + @VisibleForTesting static final int MSG_UPDATE_ACCESS_POINTS = 0; private static final int MSG_UPDATE_NETWORK_INFO = 1; private static final int MSG_RESUME = 2; private static final int MSG_UPDATE_WIFI_STATE = 3; @@ -882,6 +832,8 @@ public class WifiTracker implements LifecycleObserver, OnStart, OnStop, OnDestro @Override public void handleMessage(Message msg) { + // TODO(sghuman): Clean up synchronization to only be used when modifying collections + // exposed to the MainThread (through onStart, onStop, forceUpdate). synchronized (mLock) { processMessage(msg); } @@ -911,6 +863,7 @@ public class WifiTracker implements LifecycleObserver, OnStart, OnStop, OnDestro mScanner.resume(); } } else { + clearAccessPointsAndConditionallyUpdate(); mLastInfo = null; mLastNetworkInfo = null; if (mScanner != null) { @@ -920,8 +873,7 @@ public class WifiTracker implements LifecycleObserver, OnStart, OnStop, OnDestro mStaleScanResults = true; } } - mMainHandler.obtainMessage(MainHandler.MSG_WIFI_STATE_CHANGED, msg.arg1, 0) - .sendToTarget(); + mListener.onWifiStateChanged(msg.arg1); break; } } @@ -1010,16 +962,26 @@ public class WifiTracker implements LifecycleObserver, OnStart, OnStop, OnDestro @Override public void onWifiStateChanged(int state) { + if (isVerboseLoggingEnabled()) { + Log.i(TAG, + String.format("Invoking onWifiStateChanged callback with state %d", state)); + } mHandler.post(() -> mDelegatee.onWifiStateChanged(state)); } @Override public void onConnectedChanged() { + if (isVerboseLoggingEnabled()) { + Log.i(TAG, "Invoking onConnectedChanged callback"); + } mHandler.post(() -> mDelegatee.onConnectedChanged()); } @Override public void onAccessPointsChanged() { + if (isVerboseLoggingEnabled()) { + Log.i(TAG, "Invoking onAccessPointsChanged callback"); + } mHandler.post(() -> mDelegatee.onAccessPointsChanged()); } } @@ -1041,101 +1003,27 @@ public class WifiTracker implements LifecycleObserver, OnStart, OnStop, OnDestro void onWifiStateChanged(int state); /** - * Called when the connection state of wifi has changed and isConnected - * should be called to get the updated state. + * Called when the connection state of wifi has changed and + * {@link WifiTracker#isConnected()} should be called to get the updated state. */ void onConnectedChanged(); /** * Called to indicate the list of AccessPoints has been updated and - * getAccessPoints should be called to get the latest information. + * {@link WifiTracker#getAccessPoints()} should be called to get the updated list. */ void onAccessPointsChanged(); } /** - * Helps capture notifications that were generated during AccessPoint modification. Used later - * on by {@link #copyAndNotifyListeners(boolean)} to send notifications. + * Invokes {@link WifiListenerWrapper#onAccessPointsChanged()} if {@link #mStaleScanResults} + * is false. */ - private static class AccessPointListenerAdapter implements AccessPoint.AccessPointListener { - static final int AP_CHANGED = 1; - static final int LEVEL_CHANGED = 2; - - final SparseIntArray mPendingNotifications = new SparseIntArray(); - - @Override - public void onAccessPointChanged(AccessPoint accessPoint) { - int type = mPendingNotifications.get(accessPoint.mId); - mPendingNotifications.put(accessPoint.mId, type | AP_CHANGED); - } - - @Override - public void onLevelChanged(AccessPoint accessPoint) { - int type = mPendingNotifications.get(accessPoint.mId); - mPendingNotifications.put(accessPoint.mId, type | LEVEL_CHANGED); - } - } - - /** - * Responsible for copying access points from {@link #mInternalAccessPoints} and notifying - * accesspoint listeners. - * - * @param notifyListeners if true, accesspoint listeners are notified, otherwise notifications - * dropped. - */ - @MainThread - private void copyAndNotifyListeners(boolean notifyListeners) { - // Need to watch out for memory allocations on main thread. - SparseArray<AccessPoint> oldAccessPoints = new SparseArray<>(); - SparseIntArray notificationMap = null; - List<AccessPoint> updatedAccessPoints = new ArrayList<>(); - - for (AccessPoint accessPoint : mAccessPoints) { - oldAccessPoints.put(accessPoint.mId, accessPoint); - } - - synchronized (mLock) { - if (DBG()) { - Log.d(TAG, "Starting to copy AP items on the MainHandler. Internal APs: " - + mInternalAccessPoints); - } - - if (notifyListeners) { - notificationMap = mAccessPointListenerAdapter.mPendingNotifications.clone(); - } - - mAccessPointListenerAdapter.mPendingNotifications.clear(); - - for (AccessPoint internalAccessPoint : mInternalAccessPoints) { - AccessPoint accessPoint = oldAccessPoints.get(internalAccessPoint.mId); - if (accessPoint == null) { - accessPoint = new AccessPoint(mContext, internalAccessPoint); - } else { - accessPoint.copyFrom(internalAccessPoint); - } - updatedAccessPoints.add(accessPoint); - } + private void conditionallyNotifyListeners() { + if (mStaleScanResults) { + return; } - mAccessPoints.clear(); - mAccessPoints.addAll(updatedAccessPoints); - - if (notificationMap != null && notificationMap.size() > 0) { - for (AccessPoint accessPoint : updatedAccessPoints) { - int notificationType = notificationMap.get(accessPoint.mId); - AccessPoint.AccessPointListener listener = accessPoint.mAccessPointListener; - if (notificationType == 0 || listener == null) { - continue; - } - - if ((notificationType & AccessPointListenerAdapter.AP_CHANGED) != 0) { - listener.onAccessPointChanged(accessPoint); - } - - if ((notificationType & AccessPointListenerAdapter.LEVEL_CHANGED) != 0) { - listener.onLevelChanged(accessPoint); - } - } - } + ThreadUtils.postOnMainThread(() -> mListener.onAccessPointsChanged()); } } diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/AccessPointTest.java b/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/AccessPointTest.java index 144031108662..54c02a22e79f 100644 --- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/AccessPointTest.java +++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/AccessPointTest.java @@ -113,38 +113,6 @@ public class AccessPointTest { } @Test - public void testCopyAccessPoint_dataShouldMatch() { - WifiConfiguration configuration = createWifiConfiguration(); - configuration.meteredHint = true; - - NetworkInfo networkInfo = - new NetworkInfo(ConnectivityManager.TYPE_WIFI, 2, "WIFI", "WIFI_SUBTYPE"); - AccessPoint originalAccessPoint = new AccessPoint(mContext, configuration); - WifiInfo wifiInfo = new WifiInfo(); - wifiInfo.setSSID(WifiSsid.createFromAsciiEncoded(configuration.SSID)); - wifiInfo.setBSSID(configuration.BSSID); - originalAccessPoint.update(configuration, wifiInfo, networkInfo); - AccessPoint copy = new AccessPoint(mContext, originalAccessPoint); - - assertThat(originalAccessPoint.getSsid().toString()).isEqualTo(copy.getSsid().toString()); - assertThat(originalAccessPoint.getBssid()).isEqualTo(copy.getBssid()); - assertThat(originalAccessPoint.getConfig()).isEqualTo(copy.getConfig()); - assertThat(originalAccessPoint.getSecurity()).isEqualTo(copy.getSecurity()); - assertThat(originalAccessPoint.isMetered()).isEqualTo(copy.isMetered()); - assertThat(originalAccessPoint.compareTo(copy) == 0).isTrue(); - } - - @Test - public void testThatCopyAccessPoint_scanCacheShouldMatch() { - AccessPoint original = createAccessPointWithScanResultCache(); - assertThat(original.getRssi()).isEqualTo(4); - AccessPoint copy = new AccessPoint(mContext, createWifiConfiguration()); - assertThat(copy.getRssi()).isEqualTo(AccessPoint.UNREACHABLE_RSSI); - copy.copyFrom(original); - assertThat(original.getRssi()).isEqualTo(copy.getRssi()); - } - - @Test public void testCompareTo_GivesActiveBeforeInactive() { AccessPoint activeAp = new TestAccessPointBuilder(mContext).setActive(true).build(); AccessPoint inactiveAp = new TestAccessPointBuilder(mContext).setActive(false).build(); diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/WifiTrackerTest.java b/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/WifiTrackerTest.java index 6be4936413b7..0c49bb66a40e 100644 --- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/WifiTrackerTest.java +++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/WifiTrackerTest.java @@ -356,19 +356,14 @@ public class WifiTrackerTest { private void waitForHandlersToProcessCurrentlyEnqueuedMessages(WifiTracker tracker) throws InterruptedException { + // TODO(sghuman): This should no longer be necessary in a single work handler model + CountDownLatch workerLatch = new CountDownLatch(1); tracker.mWorkHandler.post(() -> { workerLatch.countDown(); }); assertTrue("Latch timed out while waiting for WorkerHandler", workerLatch.await(LATCH_TIMEOUT, TimeUnit.MILLISECONDS)); - - CountDownLatch mainLatch = new CountDownLatch(1); - tracker.mMainHandler.post(() -> { - mainLatch.countDown(); - }); - assertTrue("Latch timed out while waiting for MainHandler", - mainLatch.await(LATCH_TIMEOUT, TimeUnit.MILLISECONDS)); } private void switchToNetwork2(WifiTracker tracker) throws InterruptedException { @@ -390,38 +385,6 @@ public class WifiTrackerTest { } @Test - public void testAccessPointListenerSetWhenLookingUpUsingScanResults() { - ScanResult scanResult = new ScanResult(); - scanResult.level = 123; - scanResult.BSSID = "bssid-" + 111; - scanResult.timestamp = SystemClock.elapsedRealtime() * 1000; - scanResult.capabilities = ""; - - WifiTracker tracker = new WifiTracker( - InstrumentationRegistry.getTargetContext(), null, true, true); - - AccessPoint result = tracker.getCachedOrCreate( - Collections.singletonList(scanResult), new ArrayList<AccessPoint>()); - assertTrue(result.mAccessPointListener != null); - } - - @Test - public void testAccessPointListenerSetWhenLookingUpUsingWifiConfiguration() { - WifiConfiguration configuration = new WifiConfiguration(); - configuration.SSID = "test123"; - configuration.BSSID="bssid"; - configuration.networkId = 123; - configuration.allowedKeyManagement = new BitSet(); - configuration.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK); - - WifiTracker tracker = new WifiTracker( - InstrumentationRegistry.getTargetContext(), null, true, true); - - AccessPoint result = tracker.getCachedOrCreate(configuration, new ArrayList<AccessPoint>()); - assertTrue(result.mAccessPointListener != null); - } - - @Test public void startAndStopTrackingShouldRegisterAndUnregisterScoreCache() throws InterruptedException { WifiTracker tracker = createMockedWifiTracker(); @@ -534,7 +497,6 @@ public class WifiTrackerTest { waitForHandlersToProcessCurrentlyEnqueuedMessages(tracker); } - @FlakyTest @Test public void scoreCacheUpdateScoresShouldChangeSortOrder() throws InterruptedException { WifiTracker tracker = createTrackerWithImmediateBroadcastsAndInjectInitialScanResults(); @@ -634,9 +596,9 @@ public class WifiTrackerTest { public void scoresShouldBeRequestedForNewScanResultOnly() throws InterruptedException { // Scores can be requested together or serially depending on how the scan results are // processed. - mRequestScoresLatch = new CountDownLatch(2); + mRequestScoresLatch = new CountDownLatch(1); WifiTracker tracker = createTrackerWithImmediateBroadcastsAndInjectInitialScanResults(); - mRequestScoresLatch.await(LATCH_TIMEOUT, TimeUnit.MILLISECONDS); + assertTrue(mRequestScoresLatch.await(LATCH_TIMEOUT, TimeUnit.MILLISECONDS)); mRequestedKeys.clear(); String ssid = "ssid3"; @@ -770,7 +732,7 @@ public class WifiTrackerTest { CountDownLatch ready = new CountDownLatch(1); CountDownLatch latch = new CountDownLatch(1); CountDownLatch lock = new CountDownLatch(1); - tracker.mMainHandler.post(() -> { + tracker.mWorkHandler.post(() -> { try { ready.countDown(); lock.await(); @@ -781,12 +743,7 @@ public class WifiTrackerTest { }); // Enqueue messages - tracker.mMainHandler.sendEmptyMessage( - WifiTracker.MainHandler.MSG_ACCESS_POINT_CHANGED); - tracker.mMainHandler.sendEmptyMessage( - WifiTracker.MainHandler.MSG_CONNECTED_CHANGED); - tracker.mMainHandler.sendEmptyMessage( - WifiTracker.MainHandler.MSG_WIFI_STATE_CHANGED); + tracker.mWorkHandler.sendEmptyMessage(WifiTracker.WorkHandler.MSG_UPDATE_ACCESS_POINTS); try { ready.await(); // Make sure we have entered the first message handler @@ -800,12 +757,9 @@ public class WifiTrackerTest { lock.countDown(); assertTrue("Latch timed out", latch.await(LATCH_TIMEOUT, TimeUnit.MILLISECONDS)); - assertThat(tracker.mMainHandler.hasMessages( - WifiTracker.MainHandler.MSG_ACCESS_POINT_CHANGED)).isFalse(); - assertThat(tracker.mMainHandler.hasMessages( - WifiTracker.MainHandler.MSG_CONNECTED_CHANGED)).isFalse(); - assertThat(tracker.mMainHandler.hasMessages( - WifiTracker.MainHandler.MSG_WIFI_STATE_CHANGED)).isFalse(); + assertThat(tracker.mWorkHandler.hasMessages( + WifiTracker.WorkHandler.MSG_UPDATE_ACCESS_POINTS)).isFalse(); + waitForHandlersToProcessCurrentlyEnqueuedMessages(tracker); verifyNoMoreInteractions(mockWifiListener); } @@ -862,7 +816,7 @@ public class WifiTrackerTest { mAccessPointsChangedLatch = new CountDownLatch(1); tracker.mReceiver.onReceive(mContext, new Intent(WifiManager.WIFI_STATE_CHANGED_ACTION)); - mAccessPointsChangedLatch.await(LATCH_TIMEOUT, TimeUnit.MILLISECONDS); + assertTrue(mAccessPointsChangedLatch.await(LATCH_TIMEOUT, TimeUnit.MILLISECONDS)); waitForHandlersToProcessCurrentlyEnqueuedMessages(tracker); assertThat(tracker.getAccessPoints()).isEmpty(); diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/UtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/UtilsTest.java index 12d3106cfe61..706d0c0f51e5 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/UtilsTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/UtilsTest.java @@ -16,6 +16,9 @@ package com.android.settingslib; import static android.Manifest.permission.WRITE_SECURE_SETTINGS; + +import static com.android.settingslib.Utils.STORAGE_MANAGER_SHOW_OPT_IN_PROPERTY; + import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.Matchers.eq; @@ -30,6 +33,7 @@ import android.content.Context; import android.content.Intent; import android.content.res.Resources; import android.location.LocationManager; +import android.os.SystemProperties; import android.os.UserHandle; import android.provider.Settings; import android.provider.Settings.Secure; @@ -136,7 +140,7 @@ public class UtilsTest { } @Test - public void testStorageManagerDaysToRetainUsesResources() { + public void testGetDefaultStorageManagerDaysToRetain_storageManagerDaysToRetainUsesResources() { Resources resources = mock(Resources.class); when(resources.getInteger( eq( @@ -149,6 +153,12 @@ public class UtilsTest { assertThat(Utils.getDefaultStorageManagerDaysToRetain(resources)).isEqualTo(60); } + @Test + public void testIsStorageManagerEnabled_UsesSystemProperties() { + SystemProperties.set(STORAGE_MANAGER_SHOW_OPT_IN_PROPERTY, "false"); + assertThat(Utils.isStorageManagerEnabled(mContext)).isTrue(); + } + private static ArgumentMatcher<Intent> actionMatches(String expected) { return intent -> TextUtils.equals(expected, intent.getAction()); } diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java index 91957e1ff05c..ad422d8011d7 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java @@ -76,11 +76,10 @@ public class SettingsHelper { */ private static final ArraySet<String> sBroadcastOnRestore; static { - sBroadcastOnRestore = new ArraySet<String>(5); + sBroadcastOnRestore = new ArraySet<String>(4); sBroadcastOnRestore.add(Settings.Secure.ENABLED_NOTIFICATION_LISTENERS); sBroadcastOnRestore.add(Settings.Secure.ENABLED_VR_LISTENERS); sBroadcastOnRestore.add(Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES); - sBroadcastOnRestore.add(Settings.Secure.ENABLED_INPUT_METHODS); sBroadcastOnRestore.add(Settings.Global.BLUETOOTH_ON); } diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java index 85a579d2808d..87ea38202e2d 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java @@ -1591,6 +1591,7 @@ public class SettingsProvider extends ContentProvider { private boolean isGlobalOrSecureSettingRestrictedForUser(String setting, int userId, String value, int callingUid) { String restriction; + boolean checkAllUser = false; switch (setting) { case Settings.Secure.LOCATION_MODE: // Note LOCATION_MODE will be converted into LOCATION_PROVIDERS_ALLOWED @@ -1656,6 +1657,12 @@ public class SettingsProvider extends ContentProvider { restriction = UserManager.DISALLOW_AMBIENT_DISPLAY; break; + case Global.LOCATION_GLOBAL_KILL_SWITCH: + if ("0".equals(value)) return false; + restriction = UserManager.DISALLOW_CONFIG_LOCATION; + checkAllUser = true; + break; + default: if (setting != null && setting.startsWith(Settings.Global.DATA_ROAMING)) { if ("0".equals(value)) return false; @@ -1665,7 +1672,11 @@ public class SettingsProvider extends ContentProvider { return false; } - return mUserManager.hasUserRestriction(restriction, UserHandle.of(userId)); + if (checkAllUser) { + return mUserManager.hasUserRestrictionOnAnyUser(restriction); + } else { + return mUserManager.hasUserRestriction(restriction, UserHandle.of(userId)); + } } private int resolveOwningUserIdForSecureSettingLocked(int userId, String setting) { diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl index 4cf817e02fff..b8319a8e8822 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl @@ -44,4 +44,9 @@ interface ISystemUiProxy { * Specifies the text to be shown for onboarding the new swipe-up gesture to access recents. */ void setRecentsOnboardingText(CharSequence text); + + /** + * Enables/disables launcher/overview interaction features {@link InteractionType}. + */ + void setInteractionState(int flags); } diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/NavigationBarCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/NavigationBarCompat.java index f622d4a3338c..171918682099 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/NavigationBarCompat.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/NavigationBarCompat.java @@ -28,4 +28,33 @@ public class NavigationBarCompat { public static final int HIT_TARGET_NONE = 0; public static final int HIT_TARGET_BACK = 1; public static final int HIT_TARGET_HOME = 2; + + @Retention(RetentionPolicy.SOURCE) + @IntDef({FLAG_DISABLE_SWIPE_UP, + FLAG_DISABLE_QUICK_SCRUB, + FLAG_SHOW_OVERVIEW_BUTTON, + FLAG_HIDE_BACK_BUTTON + }) + public @interface InteractionType {} + + /** + * Interaction type: whether the gesture to swipe up from the navigation bar will trigger + * launcher to show overview + */ + + public static final int FLAG_DISABLE_SWIPE_UP = 0x1; + /** + * Interaction type: enable quick scrub and switch interaction on the home button + */ + public static final int FLAG_DISABLE_QUICK_SCRUB = 0x2; + + /** + * Interaction type: show/hide the overview button while this service is connected to launcher + */ + public static final int FLAG_SHOW_OVERVIEW_BUTTON = 0x4; + + /** + * Interaction type: show/hide the back button while this service is connected to launcher + */ + public static final int FLAG_HIDE_BACK_BUTTON = 0x8; } diff --git a/packages/SystemUI/src/com/android/systemui/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/OverviewProxyService.java index d0128efe2c44..1185f45469df 100644 --- a/packages/SystemUI/src/com/android/systemui/OverviewProxyService.java +++ b/packages/SystemUI/src/com/android/systemui/OverviewProxyService.java @@ -47,6 +47,8 @@ import java.io.PrintWriter; import java.util.ArrayList; import java.util.List; +import static com.android.systemui.shared.system.NavigationBarCompat.InteractionType; + /** * Class to send information from overview to launcher with a binder. */ @@ -67,6 +69,7 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis private IOverviewProxy mOverviewProxy; private int mConnectionBackoffAttempts; private CharSequence mOnboardingText; + private @InteractionType int mInteractionFlags; private ISystemUiProxy mSysUiProxy = new ISystemUiProxy.Stub() { @@ -108,6 +111,22 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis public void setRecentsOnboardingText(CharSequence text) { mOnboardingText = text; } + + public void setInteractionState(@InteractionType int flags) { + long token = Binder.clearCallingIdentity(); + try { + if (mInteractionFlags != flags) { + mInteractionFlags = flags; + mHandler.post(() -> { + for (int i = mConnectionCallbacks.size() - 1; i >= 0; --i) { + mConnectionCallbacks.get(i).onInteractionFlagsChanged(flags); + } + }); + } + } finally { + Binder.restoreCallingIdentity(token); + } + } }; private final BroadcastReceiver mLauncherAddedReceiver = new BroadcastReceiver() { @@ -230,6 +249,10 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis return mOnboardingText; } + public int getInteractionFlags() { + return mInteractionFlags; + } + private void disconnectFromLauncherService() { if (mOverviewProxy != null) { mOverviewProxy.asBinder().unlinkToDeath(mOverviewServiceDeathRcpt, 0); @@ -263,5 +286,6 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis public interface OverviewProxyListener { default void onConnectionChanged(boolean isConnected) {} default void onRecentsAnimationStarted() {} + default void onInteractionFlagsChanged(@InteractionType int flags) {} } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ActivityLaunchAnimator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ActivityLaunchAnimator.java index 11d20b221051..ef44ad17e1c7 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ActivityLaunchAnimator.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ActivityLaunchAnimator.java @@ -77,11 +77,11 @@ public class ActivityLaunchAnimator { mStatusBar = statusBar; } - public ActivityOptions getLaunchAnimation( - ExpandableNotificationRow sourceNofitication) { - AnimationRunner animationRunner = new AnimationRunner(sourceNofitication); - return ActivityOptions.makeRemoteAnimation( - new RemoteAnimationAdapter(animationRunner, 1000 /* Duration */, 0 /* delay */)); + public RemoteAnimationAdapter getLaunchAnimation( + ExpandableNotificationRow sourceNotification) { + AnimationRunner animationRunner = new AnimationRunner(sourceNotification); + return new RemoteAnimationAdapter(animationRunner, ANIMATION_DURATION, + 0 /* statusBarTransitionDelay */); } public boolean isAnimationPending() { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java index 72938c25b753..79c605e4ed23 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java @@ -19,6 +19,7 @@ import static android.app.StatusBarManager.NAVIGATION_HINT_IME_SHOWN; import static android.app.StatusBarManager.WINDOW_STATE_SHOWING; import static android.app.StatusBarManager.windowStateToString; +import static com.android.systemui.shared.system.NavigationBarCompat.InteractionType; import static com.android.systemui.statusbar.phone.BarTransitions.MODE_SEMI_TRANSPARENT; import static com.android.systemui.statusbar.phone.StatusBar.DEBUG_WINDOW_STATE; import static com.android.systemui.statusbar.phone.StatusBar.dumpBarTransitions; @@ -71,7 +72,6 @@ import android.view.WindowManagerGlobal; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityManager; import android.view.accessibility.AccessibilityManager.AccessibilityServicesStateChangeListener; -import android.widget.Button; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; @@ -165,6 +165,11 @@ public class NavigationBarFragment extends Fragment implements Callbacks { public void onRecentsAnimationStarted() { mNavigationBarView.setRecentsAnimationStarted(true); } + + @Override + public void onInteractionFlagsChanged(@InteractionType int flags) { + mNavigationBarView.updateStates(); + } }; // ----- Fragment Lifecycle Callbacks ----- diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarGestureHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarGestureHelper.java index 63f2cebb61cc..8970ea0c110b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarGestureHelper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarGestureHelper.java @@ -127,7 +127,7 @@ public class NavigationBarGestureHelper implements TunerService.Tunable, Gesture private boolean proxyMotionEvents(MotionEvent event) { final IOverviewProxy overviewProxy = mOverviewProxyService.getProxy(); - if (overviewProxy != null) { + if (overviewProxy != null && mNavigationBarView.isQuickStepSwipeUpEnabled()) { mNavigationBarView.requestUnbufferedDispatch(event); event.transform(mTransformGlobalMatrix); try { @@ -192,7 +192,7 @@ public class NavigationBarGestureHelper implements TunerService.Tunable, Gesture } public void onDraw(Canvas canvas) { - if (mOverviewProxyService.getProxy() != null) { + if (mNavigationBarView.isQuickScrubEnabled()) { mQuickScrubController.onDraw(canvas); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java index 53dc814726a2..cd4eb236c892 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java @@ -40,6 +40,7 @@ import android.graphics.drawable.AnimatedVectorDrawable; import android.os.Handler; import android.os.Message; import android.os.RemoteException; +import android.os.SystemProperties; import android.support.annotation.ColorInt; import android.util.AttributeSet; import android.util.Log; @@ -76,6 +77,11 @@ import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.function.Consumer; +import static com.android.systemui.shared.system.NavigationBarCompat.FLAG_DISABLE_QUICK_SCRUB; +import static com.android.systemui.shared.system.NavigationBarCompat.FLAG_DISABLE_SWIPE_UP; +import static com.android.systemui.shared.system.NavigationBarCompat.FLAG_HIDE_BACK_BUTTON; +import static com.android.systemui.shared.system.NavigationBarCompat.FLAG_SHOW_OVERVIEW_BUTTON; + public class NavigationBarView extends FrameLayout implements PluginListener<NavGesture> { final static boolean DEBUG = false; final static String TAG = "StatusBar/NavBarView"; @@ -372,6 +378,19 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav return getRecentsButton().getVisibility() == View.VISIBLE; } + public boolean isQuickStepSwipeUpEnabled() { + return mOverviewProxyService.getProxy() != null + && ((mOverviewProxyService.getInteractionFlags() + & FLAG_DISABLE_SWIPE_UP) == 0); + } + + public boolean isQuickScrubEnabled() { + return SystemProperties.getBoolean("persist.quickstep.scrub.enabled", true) + && mOverviewProxyService.getProxy() != null && !isRecentsButtonVisible() + && ((mOverviewProxyService.getInteractionFlags() + & FLAG_DISABLE_QUICK_SCRUB) == 0); + } + private void updateCarModeIcons(Context ctx) { mBackCarModeIcon = getDrawable(ctx, R.drawable.ic_sysbar_back_carmode, R.drawable.ic_sysbar_back_carmode); @@ -391,20 +410,20 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav } if (oldConfig.densityDpi != newConfig.densityDpi || oldConfig.getLayoutDirection() != newConfig.getLayoutDirection()) { - final boolean proxyAvailable = mOverviewProxyService.getProxy() != null; - mBackIcon = proxyAvailable + final boolean quickStepEnabled = isQuickStepSwipeUpEnabled() || isQuickScrubEnabled(); + mBackIcon = quickStepEnabled ? getDrawable(ctx, R.drawable.ic_sysbar_back_quick_step, R.drawable.ic_sysbar_back_quick_step_dark) : getDrawable(ctx, R.drawable.ic_sysbar_back, R.drawable.ic_sysbar_back_dark); mBackLandIcon = mBackIcon; - mBackAltIcon = proxyAvailable + mBackAltIcon = quickStepEnabled ? getDrawable(ctx, R.drawable.ic_sysbar_back_ime_quick_step, R.drawable.ic_sysbar_back_ime_quick_step_dark) : getDrawable(ctx, R.drawable.ic_sysbar_back_ime, R.drawable.ic_sysbar_back_ime_dark); mBackAltLandIcon = mBackAltIcon; - mHomeDefaultIcon = proxyAvailable + mHomeDefaultIcon = quickStepEnabled ? getDrawable(ctx, R.drawable.ic_sysbar_home_quick_step, R.drawable.ic_sysbar_home_quick_step_dark) : getDrawable(ctx, R.drawable.ic_sysbar_home, R.drawable.ic_sysbar_home_dark); @@ -555,9 +574,14 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav disableBack = false; disableRecent = false; } + if (mOverviewProxyService.getProxy() != null) { - // When overview is connected to the launcher service, disable the recents button - disableRecent = true; + // When overview is connected to the launcher service, disable the recents button by + // default unless overwritten by interaction flags. Similar with the back button but + // shown by default. + final int flags = mOverviewProxyService.getInteractionFlags(); + disableRecent |= (flags & FLAG_SHOW_OVERVIEW_BUTTON) == 0; + disableBack |= (flags & FLAG_HIDE_BACK_BUTTON) != 0; } ViewGroup navButtons = (ViewGroup) getCurrentView().findViewById(R.id.nav_buttons); @@ -635,6 +659,11 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav updateSlippery(); } + public void updateStates() { + updateSlippery(); + setDisabledFlags(mDisabledFlags, true); + } + private void updateSlippery() { setSlippery(mOverviewProxyService.getProxy() != null && mPanelView.isFullyExpanded()); } @@ -794,9 +823,8 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav } public void onOverviewProxyConnectionChanged(boolean isConnected) { - setSlippery(!isConnected); - setDisabledFlags(mDisabledFlags, true); - setUpSwipeUpOnboarding(isConnected); + updateStates(); + setUpSwipeUpOnboarding(isQuickStepSwipeUpEnabled()); updateIcons(getContext(), Configuration.EMPTY, mConfiguration); setNavigationIconHints(mNavigationIconHints, true); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickScrubController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickScrubController.java index f4da0c38338d..0bf01b0cb949 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickScrubController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickScrubController.java @@ -29,15 +29,12 @@ import android.graphics.Paint; import android.graphics.Rect; import android.os.Handler; import android.os.RemoteException; -import android.os.SystemProperties; import android.util.Log; import android.util.Slog; -import android.view.Display; import android.view.GestureDetector; import android.view.MotionEvent; import android.view.View; import android.view.ViewConfiguration; -import android.view.WindowManager; import android.view.WindowManagerGlobal; import android.view.animation.DecelerateInterpolator; import android.view.animation.Interpolator; @@ -138,8 +135,9 @@ public class QuickScrubController extends GestureDetector.SimpleOnGestureListene new GestureDetector.SimpleOnGestureListener() { @Override public boolean onFling(MotionEvent e1, MotionEvent e2, float velX, float velY) { - if (!isQuickScrubEnabled() || mQuickScrubActive || !mAllowQuickSwitch || - mNavigationBarView.getDownHitTarget() != HIT_TARGET_HOME) { + if (!mNavigationBarView.isQuickScrubEnabled() || mQuickScrubActive + || !mAllowQuickSwitch + || mNavigationBarView.getDownHitTarget() != HIT_TARGET_HOME) { return false; } float velocityX = mIsRTL ? -velX : velX; @@ -196,9 +194,8 @@ public class QuickScrubController extends GestureDetector.SimpleOnGestureListene */ @Override public boolean onInterceptTouchEvent(MotionEvent event) { - final IOverviewProxy overviewProxy = mOverviewEventSender.getProxy(); final ButtonDispatcher homeButton = mNavigationBarView.getHomeButton(); - if (overviewProxy == null) { + if (!mNavigationBarView.isQuickScrubEnabled()) { homeButton.setDelayTouchFeedback(false); return false; } @@ -228,7 +225,7 @@ public class QuickScrubController extends GestureDetector.SimpleOnGestureListene int x = (int) event.getX(); int y = (int) event.getY(); mHomeButtonView = homeButton.getCurrentView(); - if (isQuickScrubEnabled() + if (mNavigationBarView.isQuickScrubEnabled() && mNavigationBarView.getDownHitTarget() == HIT_TARGET_HOME) { mTouchDownX = x; mTouchDownY = y; @@ -296,7 +293,7 @@ public class QuickScrubController extends GestureDetector.SimpleOnGestureListene : Utilities.clamp(offset - mDownOffset, 0, trackSize); if (mQuickScrubActive) { try { - overviewProxy.onQuickScrubProgress(scrubFraction); + mOverviewEventSender.getProxy().onQuickScrubProgress(scrubFraction); if (DEBUG_OVERVIEW_PROXY) { Log.d(TAG_OPS, "Quick Scrub Progress:" + scrubFraction); } @@ -377,10 +374,6 @@ public class QuickScrubController extends GestureDetector.SimpleOnGestureListene } } - boolean isQuickScrubEnabled() { - return SystemProperties.getBoolean("persist.quickstep.scrub.enabled", true); - } - private void startQuickScrub() { if (!mQuickScrubActive) { mQuickScrubActive = true; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java index 94ebc1bac2b0..2b5085389804 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java @@ -138,8 +138,7 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, protected float mScrimBehindAlphaKeyguard = SCRIM_BEHIND_ALPHA_KEYGUARD; protected float mScrimBehindAlphaUnlocking = SCRIM_BEHIND_ALPHA_UNLOCKING; - // Assuming the shade is expanded during initialization - private float mExpansionFraction = 1f; + private float mFraction; private boolean mDarkenWhileDragging; protected boolean mAnimateChange; @@ -253,7 +252,6 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, mCurrentBehindTint = state.getBehindTint(); mCurrentInFrontAlpha = state.getFrontAlpha(); mCurrentBehindAlpha = state.getBehindAlpha(); - applyExpansionToAlpha(); // Cancel blanking transitions that were pending before we requested a new state if (mPendingFrameCallback != null) { @@ -365,50 +363,45 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, * @param fraction From 0 to 1 where 0 means collapse and 1 expanded. */ public void setPanelExpansion(float fraction) { - if (mExpansionFraction != fraction) { - mExpansionFraction = fraction; - - if (!(mState == ScrimState.UNLOCKED || mState == ScrimState.KEYGUARD)) { - return; - } - - applyExpansionToAlpha(); + if (mFraction != fraction) { + mFraction = fraction; + + if (mState == ScrimState.UNLOCKED) { + // Darken scrim as you pull down the shade when unlocked + float behindFraction = getInterpolatedFraction(); + behindFraction = (float) Math.pow(behindFraction, 0.8f); + mCurrentBehindAlpha = behindFraction * mScrimBehindAlphaKeyguard; + mCurrentInFrontAlpha = 0; + } else if (mState == ScrimState.KEYGUARD) { + if (mUpdatePending) { + return; + } - if (mUpdatePending) { + // Either darken of make the scrim transparent when you + // pull down the shade + float interpolatedFract = getInterpolatedFraction(); + if (mDarkenWhileDragging) { + mCurrentBehindAlpha = MathUtils.lerp(mScrimBehindAlphaUnlocking, + mScrimBehindAlphaKeyguard, interpolatedFract); + mCurrentInFrontAlpha = (1f - interpolatedFract) * SCRIM_IN_FRONT_ALPHA_LOCKED; + } else { + mCurrentBehindAlpha = MathUtils.lerp(0 /* start */, mScrimBehindAlphaKeyguard, + interpolatedFract); + mCurrentInFrontAlpha = 0; + } + } else { return; } if (mPinnedHeadsUpCount != 0) { updateHeadsUpScrim(false); } + updateScrim(false /* animate */, mScrimInFront, mCurrentInFrontAlpha); updateScrim(false /* animate */, mScrimBehind, mCurrentBehindAlpha); } } - private void applyExpansionToAlpha() { - if (mState == ScrimState.UNLOCKED) { - // Darken scrim as you pull down the shade when unlocked - float behindFraction = getInterpolatedFraction(); - behindFraction = (float) Math.pow(behindFraction, 0.8f); - mCurrentBehindAlpha = behindFraction * mScrimBehindAlphaKeyguard; - mCurrentInFrontAlpha = 0; - } else if (mState == ScrimState.KEYGUARD) { - // Either darken of make the scrim transparent when you - // pull down the shade - float interpolatedFract = getInterpolatedFraction(); - if (mDarkenWhileDragging) { - mCurrentBehindAlpha = MathUtils.lerp(mScrimBehindAlphaUnlocking, - mScrimBehindAlphaKeyguard, interpolatedFract); - mCurrentInFrontAlpha = (1f - interpolatedFract) * SCRIM_IN_FRONT_ALPHA_LOCKED; - } else { - mCurrentBehindAlpha = MathUtils.lerp(0 /* start */, mScrimBehindAlphaKeyguard, - interpolatedFract); - mCurrentInFrontAlpha = 0; - } - } - } - /** * Keyguard and shade scrim opacity varies according to how many notifications are visible. * @param notificationCount Number of visible notifications. @@ -504,7 +497,7 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, } private float getInterpolatedFraction() { - float frac = mExpansionFraction; + float frac = mFraction; // let's start this 20% of the way down the screen frac = frac * 1.2f - 0.2f; if (frac <= 0) { @@ -834,7 +827,7 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, } else { alpha = 1.0f - mTopHeadsUpDragAmount; } - float expandFactor = (1.0f - mExpansionFraction); + float expandFactor = (1.0f - mFraction); expandFactor = Math.max(expandFactor, 0.0f); return alpha * expandFactor; } 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 3b63d6c39146..24920cba21f5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java @@ -111,6 +111,7 @@ import android.view.IWindowManager; import android.view.KeyEvent; import android.view.LayoutInflater; import android.view.MotionEvent; +import android.view.RemoteAnimationAdapter; import android.view.ThreadedRenderer; import android.view.View; import android.view.ViewGroup; @@ -2849,7 +2850,7 @@ public class StatusBar extends SystemUI implements DemoMode, Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP); int result = ActivityManager.START_CANCELED; ActivityOptions options = new ActivityOptions(getActivityOptions( - null /* sourceNotification */)); + null /* remoteAnimation */)); options.setDisallowEnterPictureInPictureWhileLaunching( disallowEnterPictureInPictureWhileLaunching); if (intent == KeyguardBottomAreaView.INSECURE_CAMERA_INTENT) { @@ -5001,11 +5002,15 @@ public class StatusBar extends SystemUI implements DemoMode, fillInIntent = new Intent().putExtra(Notification.EXTRA_REMOTE_INPUT_DRAFT, remoteInputText.toString()); } + RemoteAnimationAdapter adapter = mActivityLaunchAnimator.getLaunchAnimation( + row); try { + ActivityManager.getService().registerRemoteAnimationForNextActivityStart( + intent.getCreatorPackage(), adapter); launchResult = intent.sendAndReturnResult(mContext, 0, fillInIntent, null, - null, null, getActivityOptions(row)); + null, null, getActivityOptions(adapter)); mActivityLaunchAnimator.setLaunchResult(launchResult); - } catch (PendingIntent.CanceledException e) { + } catch (RemoteException | PendingIntent.CanceledException e) { // the stack trace isn't very helpful here. // Just log the exception message. Log.w(TAG, "Sending contentIntent failed: " + e); @@ -5165,7 +5170,8 @@ public class StatusBar extends SystemUI implements DemoMode, AsyncTask.execute(() -> { int launchResult = TaskStackBuilder.create(mContext) .addNextIntentWithParentStack(intent) - .startActivities(getActivityOptions(row), + .startActivities(getActivityOptions( + mActivityLaunchAnimator.getLaunchAnimation(row)), new UserHandle(UserHandle.getUserId(appUid))); mActivityLaunchAnimator.setLaunchResult(launchResult); if (shouldCollapse()) { @@ -5300,7 +5306,7 @@ public class StatusBar extends SystemUI implements DemoMode, } try { intent.send(null, 0, null, null, null, null, getActivityOptions( - null /* sourceNotification */)); + null /* animationAdapter */)); } catch (PendingIntent.CanceledException e) { // the stack trace isn't very helpful here. // Just log the exception message. @@ -5328,10 +5334,10 @@ public class StatusBar extends SystemUI implements DemoMode, return true; } - protected Bundle getActivityOptions(ExpandableNotificationRow sourceNotification) { + protected Bundle getActivityOptions(@Nullable RemoteAnimationAdapter animationAdapter) { ActivityOptions options; - if (sourceNotification != null) { - options = mActivityLaunchAnimator.getLaunchAnimation(sourceNotification); + if (animationAdapter != null) { + options = ActivityOptions.makeRemoteAnimation(animationAdapter); } else { options = ActivityOptions.makeBasic(); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java index 47027935d8d7..43e16dbeaeed 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java @@ -180,7 +180,6 @@ public class ScrimControllerTest extends SysuiTestCase { @Test public void transitionToUnlocked() { - mScrimController.setPanelExpansion(0f); mScrimController.transitionTo(ScrimState.UNLOCKED); mScrimController.finishAnimationsImmediately(); // Front scrim should be transparent @@ -198,7 +197,6 @@ public class ScrimControllerTest extends SysuiTestCase { public void transitionToUnlockedFromAod() { // Simulate unlock with fingerprint mScrimController.transitionTo(ScrimState.AOD); - mScrimController.setPanelExpansion(0f); mScrimController.finishAnimationsImmediately(); mScrimController.transitionTo(ScrimState.UNLOCKED); // Immediately tinted after the transition starts @@ -326,23 +324,6 @@ public class ScrimControllerTest extends SysuiTestCase { verify(mAlarmManager).cancel(any(AlarmManager.OnAlarmListener.class)); } - @Test - public void testConservesExpansionOpacityAfterTransition() { - mScrimController.transitionTo(ScrimState.UNLOCKED); - mScrimController.setPanelExpansion(0.5f); - mScrimController.finishAnimationsImmediately(); - - final float expandedAlpha = mScrimBehind.getViewAlpha(); - - mScrimController.transitionTo(ScrimState.BRIGHTNESS_MIRROR); - mScrimController.finishAnimationsImmediately(); - mScrimController.transitionTo(ScrimState.UNLOCKED); - mScrimController.finishAnimationsImmediately(); - - Assert.assertEquals("Scrim expansion opacity wasn't conserved when transitioning back", - expandedAlpha, mScrimBehind.getViewAlpha(), 0.01f); - } - private void assertScrimTint(ScrimView scrimView, boolean tinted) { final boolean viewIsTinted = scrimView.getTint() != Color.TRANSPARENT; final String name = scrimView == mScrimInFront ? "front" : "back"; diff --git a/proto/src/metrics_constants.proto b/proto/src/metrics_constants.proto index ae5e133fa02f..7eebf5a3f94a 100644 --- a/proto/src/metrics_constants.proto +++ b/proto/src/metrics_constants.proto @@ -5213,6 +5213,22 @@ message MetricsEvent { // OS: P ACTION_OUTPUT_CHOOSER_DISCONNECT = 1297; + // OPEN: TV Settings > Home theater control + // OS: P + SETTINGS_TV_HOME_THEATER_CONTROL_CATEGORY = 1298; + + // OPEN: TV Settings > TV Inputs (Inputs & Devices) + // OS: P + SETTINGS_TV_INPUTS_CATEGORY = 1299; + + // OPEN: TV Settings > Device + // OS: P + SETTINGS_TV_DEVICE_CATEGORY = 1300; + + // OPEN: TV Settings > Network > Proxy settings + // OS: P + DIALOG_TV_NETWORK_PROXY = 1301; + // ---- End P Constants, all P constants go above this line ---- // Add new aosp constants above this line. // END OF AOSP CONSTANTS diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java index ebb5040a7073..4b3abeadaea1 100644 --- a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java +++ b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java @@ -24,7 +24,6 @@ import static com.android.server.autofill.Helper.sDebug; import static com.android.server.autofill.Helper.sPartitionMaxCount; import static com.android.server.autofill.Helper.sVerbose; -import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.UserIdInt; @@ -56,7 +55,12 @@ import android.os.UserManagerInternal; import android.provider.Settings; import android.service.autofill.FillEventHistory; import android.service.autofill.UserData; +import android.text.TextUtils; +import android.text.TextUtils.SimpleStringSplitter; +import android.util.ArrayMap; +import android.util.ArraySet; import android.util.LocalLog; +import android.util.Pair; import android.util.Slog; import android.util.SparseArray; import android.util.SparseBooleanArray; @@ -84,6 +88,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Objects; +import java.util.Set; /** * Entry point service for autofill management. @@ -98,6 +103,8 @@ public final class AutofillManagerService extends SystemService { static final String RECEIVER_BUNDLE_EXTRA_SESSIONS = "sessions"; + private static final char COMPAT_PACKAGE_DELIMITER = ':'; + private final Context mContext; private final AutoFillUI mUi; @@ -123,6 +130,9 @@ public final class AutofillManagerService extends SystemService { private final LocalLog mUiLatencyHistory = new LocalLog(20); private final LocalLog mWtfHistory = new LocalLog(50); + private final AutofillCompatState mAutofillCompatState = new AutofillCompatState(); + private final LocalService mLocalService = new LocalService(); + private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { @@ -271,7 +281,7 @@ public final class AutofillManagerService extends SystemService { @Override public void onStart() { publishBinderService(AUTOFILL_MANAGER_SERVICE, new AutoFillManagerServiceStub()); - publishLocalService(AutofillManagerInternal.class, new LocalService()); + publishLocalService(AutofillManagerInternal.class, mLocalService); } @Override @@ -317,6 +327,11 @@ public final class AutofillManagerService extends SystemService { mUiLatencyHistory, mWtfHistory, resolvedUserId, mUi, mDisabledUsers.get(resolvedUserId)); mServicesCache.put(userId, service); + final ArrayMap<String, Pair<Long, String>> compatPackages = + service.getCompatibilityPackagesLocked(); + if (compatPackages != null) { + addCompatibilityModeRequests(compatPackages, userId); + } } return service; } @@ -482,6 +497,7 @@ public final class AutofillManagerService extends SystemService { if (service != null) { mServicesCache.delete(userId); service.destroyLocked(); + mAutofillCompatState.removeCompatibilityModeRequests(userId); } } @@ -498,18 +514,60 @@ public final class AutofillManagerService extends SystemService { */ @GuardedBy("mLock") private void updateCachedServiceLocked(int userId, boolean disabled) { - AutofillManagerServiceImpl service = getServiceForUserLocked(userId); + AutofillManagerServiceImpl service = peekServiceForUserLocked(userId); if (service != null) { service.destroySessionsLocked(); service.updateLocked(disabled); if (!service.isEnabledLocked()) { removeCachedServiceLocked(userId); + } else { + final ArrayMap<String, Pair<Long, String>> compatPackages = + service.getCompatibilityPackagesLocked(); + if (compatPackages != null) { + addCompatibilityModeRequests(compatPackages, userId); + } } } } - private final class LocalService extends AutofillManagerInternal { + private void addCompatibilityModeRequests( + @NonNull ArrayMap<String, Pair<Long, String>> compatPackages, int userId) { + final Set<String> whiteListedPackages = Build.IS_ENG ? null + : getWhitelistedCompatModePackages(); + final int compatPackageCount = compatPackages.size(); + for (int i = 0; i < compatPackageCount; i++) { + final String packageName = compatPackages.keyAt(i); + if (!Build.IS_ENG && (whiteListedPackages == null + || !whiteListedPackages.contains(packageName))) { + Slog.w(TAG, "Ignoring not whitelisted compat package " + packageName); + continue; + } + final Long maxVersionCode = compatPackages.valueAt(i).first; + if (maxVersionCode != null) { + mAutofillCompatState.addCompatibilityModeRequest(packageName, + maxVersionCode, userId); + } + } + } + private @Nullable Set<String> getWhitelistedCompatModePackages() { + final String compatPackagesSetting = Settings.Global.getString( + mContext.getContentResolver(), + Settings.Global.AUTOFILL_COMPAT_ALLOWED_PACKAGES); + if (TextUtils.isEmpty(compatPackagesSetting)) { + return null; + } + final Set<String> compatPackages = new ArraySet<>(); + final SimpleStringSplitter splitter = new SimpleStringSplitter( + COMPAT_PACKAGE_DELIMITER); + splitter.setString(compatPackagesSetting); + while (splitter.hasNext()) { + compatPackages.add(splitter.next()); + } + return compatPackages; + } + + private final class LocalService extends AutofillManagerInternal { @Override public void onBackKeyPressed() { if (sDebug) Slog.d(TAG, "onBackKeyPressed()"); @@ -519,13 +577,59 @@ public final class AutofillManagerService extends SystemService { @Override public boolean isCompatibilityModeRequested(@NonNull String packageName, long versionCode, @UserIdInt int userId) { + return mAutofillCompatState.isCompatibilityModeRequested( + packageName, versionCode, userId); + } + } + + private static class AutofillCompatState { + private final Object mLock = new Object(); + + @GuardedBy("mLock") + private SparseArray<ArrayMap<String, Long>> mUserSpecs; + + boolean isCompatibilityModeRequested(@NonNull String packageName, + long versionCode, @UserIdInt int userId) { synchronized (mLock) { - final AutofillManagerServiceImpl service = getServiceForUserLocked(userId); - if (service != null) { - return service.isCompatibilityModeRequestedLocked(packageName, versionCode); + if (mUserSpecs == null) { + return false; + } + final ArrayMap<String, Long> userSpec = mUserSpecs.get(userId); + if (userSpec == null) { + return false; + } + final Long maxVersionCode = userSpec.get(packageName); + if (maxVersionCode == null) { + return false; + } + return versionCode <= maxVersionCode; + } + } + + void addCompatibilityModeRequest(@NonNull String packageName, + long versionCode, @UserIdInt int userId) { + synchronized (mLock) { + if (mUserSpecs == null) { + mUserSpecs = new SparseArray<>(); + } + ArrayMap<String, Long> userSpec = mUserSpecs.get(userId); + if (userSpec == null) { + userSpec = new ArrayMap<>(); + mUserSpecs.put(userId, userSpec); + } + userSpec.put(packageName, versionCode); + } + } + + void removeCompatibilityModeRequests(@UserIdInt int userId) { + synchronized (mLock) { + if (mUserSpecs != null) { + mUserSpecs.remove(userId); + if (mUserSpecs.size() <= 0) { + mUserSpecs = null; + } } } - return false; } } diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java index 75ae2dc0bb4b..31f293334c65 100644 --- a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java +++ b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java @@ -64,6 +64,7 @@ import android.util.ArrayMap; import android.util.ArraySet; import android.util.DebugUtils; import android.util.LocalLog; +import android.util.Pair; import android.util.Slog; import android.util.SparseArray; import android.util.TimeUtils; @@ -906,7 +907,10 @@ final class AutofillManagerServiceImpl { pw.print(prefix); pw.print("Disabled: "); pw.println(mDisabled); pw.print(prefix); pw.print("Field classification enabled: "); pw.println(isFieldClassificationEnabledLocked()); - pw.print(prefix); pw.print("Compat pkgs: "); pw.println(getWhitelistedCompatModePackages()); + final ArrayMap<String, Pair<Long, String>> compatPkgs = getCompatibilityPackagesLocked(); + if (compatPkgs != null) { + pw.print(prefix); pw.print("Compat pkgs: "); pw.println(compatPkgs.keySet()); + } pw.print(prefix); pw.print("Setup complete: "); pw.println(mSetupComplete); pw.print(prefix); pw.print("Last prune: "); pw.println(mLastPrune); @@ -1030,23 +1034,11 @@ final class AutofillManagerServiceImpl { } @GuardedBy("mLock") - boolean isCompatibilityModeRequestedLocked(@NonNull String packageName, - long versionCode) { - if (mInfo == null || !mInfo.isCompatibilityModeRequested(packageName, versionCode)) { - return false; + @Nullable ArrayMap<String, Pair<Long, String>> getCompatibilityPackagesLocked() { + if (mInfo != null) { + return mInfo.getCompatibilityPackages(); } - if (!Build.IS_ENG) { - // TODO: Build a map and watch for settings changes (this is called on app start) - final String whiteListedPackages = getWhitelistedCompatModePackages(); - return whiteListedPackages != null && whiteListedPackages.contains(packageName); - } - return true; - } - - private String getWhitelistedCompatModePackages() { - return Settings.Global.getString( - mContext.getContentResolver(), - Settings.Global.AUTOFILL_COMPAT_ALLOWED_PACKAGES); + return null; } private void sendStateToClients(boolean resetClient) { diff --git a/services/core/java/com/android/server/InputMethodManagerService.java b/services/core/java/com/android/server/InputMethodManagerService.java index bdeb23163e7e..7cd007bd0b8f 100644 --- a/services/core/java/com/android/server/InputMethodManagerService.java +++ b/services/core/java/com/android/server/InputMethodManagerService.java @@ -120,7 +120,6 @@ import android.service.vr.IVrManager; import android.service.vr.IVrStateCallbacks; import android.text.TextUtils; import android.text.style.SuggestionSpan; -import android.util.ArrayMap; import android.util.ArraySet; import android.util.AtomicFile; import android.util.EventLog; @@ -183,7 +182,6 @@ import java.util.concurrent.atomic.AtomicInteger; public class InputMethodManagerService extends IInputMethodManager.Stub implements ServiceConnection, Handler.Callback { static final boolean DEBUG = false; - static final boolean DEBUG_RESTORE = DEBUG || false; static final String TAG = "InputMethodManagerService"; @Retention(SOURCE) @@ -911,15 +909,6 @@ public class InputMethodManagerService extends IInputMethodManager.Stub || Intent.ACTION_USER_REMOVED.equals(action)) { updateCurrentProfileIds(); return; - } else if (Intent.ACTION_SETTING_RESTORED.equals(action)) { - final String name = intent.getStringExtra(Intent.EXTRA_SETTING_NAME); - if (Settings.Secure.ENABLED_INPUT_METHODS.equals(name)) { - final String prevValue = intent.getStringExtra( - Intent.EXTRA_SETTING_PREVIOUS_VALUE); - final String newValue = intent.getStringExtra( - Intent.EXTRA_SETTING_NEW_VALUE); - restoreEnabledInputMethods(mContext, prevValue, newValue); - } } else if (Intent.ACTION_LOCALE_CHANGED.equals(action)) { onActionLocaleChanged(); } else if (ACTION_SHOW_INPUT_METHOD_PICKER.equals(action)) { @@ -984,44 +973,6 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } } - // Apply the results of a restore operation to the set of enabled IMEs. Note that this - // does not attempt to validate on the fly with any installed device policy, so must only - // be run in the context of initial device setup. - // - // TODO: Move this method to InputMethodUtils with adding unit tests. - static void restoreEnabledInputMethods(Context context, String prevValue, String newValue) { - if (DEBUG_RESTORE) { - Slog.i(TAG, "Restoring enabled input methods:"); - Slog.i(TAG, "prev=" + prevValue); - Slog.i(TAG, " new=" + newValue); - } - // 'new' is the just-restored state, 'prev' is what was in settings prior to the restore - ArrayMap<String, ArraySet<String>> prevMap = - InputMethodUtils.parseInputMethodsAndSubtypesString(prevValue); - ArrayMap<String, ArraySet<String>> newMap = - InputMethodUtils.parseInputMethodsAndSubtypesString(newValue); - - // Merge the restored ime+subtype enabled states into the live state - for (ArrayMap.Entry<String, ArraySet<String>> entry : newMap.entrySet()) { - final String imeId = entry.getKey(); - ArraySet<String> prevSubtypes = prevMap.get(imeId); - if (prevSubtypes == null) { - prevSubtypes = new ArraySet<>(2); - prevMap.put(imeId, prevSubtypes); - } - prevSubtypes.addAll(entry.getValue()); - } - - final String mergedImesAndSubtypesString = - InputMethodUtils.buildInputMethodsAndSubtypesString(prevMap); - if (DEBUG_RESTORE) { - Slog.i(TAG, "Merged IME string:"); - Slog.i(TAG, " " + mergedImesAndSubtypesString); - } - Settings.Secure.putString(context.getContentResolver(), - Settings.Secure.ENABLED_INPUT_METHODS, mergedImesAndSubtypesString); - } - final class MyPackageMonitor extends PackageMonitor { /** * Package names that are known to contain {@link InputMethodService}. @@ -1577,7 +1528,6 @@ public class InputMethodManagerService extends IInputMethodManager.Stub broadcastFilter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS); broadcastFilter.addAction(Intent.ACTION_USER_ADDED); broadcastFilter.addAction(Intent.ACTION_USER_REMOVED); - broadcastFilter.addAction(Intent.ACTION_SETTING_RESTORED); broadcastFilter.addAction(Intent.ACTION_LOCALE_CHANGED); broadcastFilter.addAction(ACTION_SHOW_INPUT_METHOD_PICKER); mContext.registerReceiver(new ImmsBroadcastReceiver(), broadcastFilter); diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index e8b78394f901..d488e6f819eb 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -380,6 +380,7 @@ import android.util.proto.ProtoUtils; import android.view.Gravity; import android.view.IRecentsAnimationRunner; import android.view.LayoutInflater; +import android.view.RemoteAnimationAdapter; import android.view.RemoteAnimationDefinition; import android.view.View; import android.view.WindowManager; @@ -11362,9 +11363,6 @@ public class ActivityManagerService extends IActivityManager.Stub throw new IllegalArgumentException("Invalid task, not in foreground"); } - // When a task is locked, dismiss the pinned stack if it exists - mStackSupervisor.removeStacksInWindowingModes(WINDOWING_MODE_PINNED); - // {@code isSystemCaller} is used to distinguish whether this request is initiated by the // system or a specific app. // * System-initiated requests will only start the pinned mode (screen pinning) @@ -11374,6 +11372,9 @@ public class ActivityManagerService extends IActivityManager.Stub final int callingUid = Binder.getCallingUid(); long ident = Binder.clearCallingIdentity(); try { + // When a task is locked, dismiss the pinned stack if it exists + mStackSupervisor.removeStacksInWindowingModes(WINDOWING_MODE_PINNED); + mLockTaskController.startLockTaskMode(task, isSystemCaller, callingUid); } finally { Binder.restoreCallingIdentity(ident); @@ -26336,4 +26337,20 @@ public class ActivityManagerService extends IActivityManager.Stub } } } + + @Override + public void registerRemoteAnimationForNextActivityStart(String packageName, + RemoteAnimationAdapter adapter) throws RemoteException { + enforceCallingPermission(CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS, + "registerRemoteAnimationForNextActivityStart"); + synchronized (this) { + final long origId = Binder.clearCallingIdentity(); + try { + mActivityStartController.registerRemoteAnimationForNextActivityStart(packageName, + adapter); + } finally { + Binder.restoreCallingIdentity(origId); + } + } + } } diff --git a/services/core/java/com/android/server/am/ActivityRecord.java b/services/core/java/com/android/server/am/ActivityRecord.java index 9838851a56cf..ddba349dbe86 100644 --- a/services/core/java/com/android/server/am/ActivityRecord.java +++ b/services/core/java/com/android/server/am/ActivityRecord.java @@ -380,8 +380,9 @@ final class ActivityRecord extends ConfigurationContainer implements AppWindowCo } String getLifecycleDescription(String reason) { - return "component:" + intent.getComponent().flattenToShortString() + ", state=" + state - + ", reason=" + reason + ", time=" + System.currentTimeMillis(); + return "name= " + this + ", component=" + intent.getComponent().flattenToShortString() + + ", package=" + packageName + ", state=" + state + ", reason=" + reason + ", time=" + + System.currentTimeMillis(); } void dump(PrintWriter pw, String prefix) { @@ -2583,7 +2584,8 @@ final class ActivityRecord extends ConfigurationContainer implements AppWindowCo if (andResume) { lifecycleItem = ResumeActivityItem.obtain(service.isNextTransitionForward()); } else { - lifecycleItem = PauseActivityItem.obtain(); + lifecycleItem = PauseActivityItem.obtain() + .setDescription(getLifecycleDescription("relaunchActivityLocked")); } final ClientTransaction transaction = ClientTransaction.obtain(app.thread, appToken); transaction.addCallback(callbackItem); diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java index 055a1aa4bbca..812de88729de 100644 --- a/services/core/java/com/android/server/am/ActivityStack.java +++ b/services/core/java/com/android/server/am/ActivityStack.java @@ -1444,7 +1444,8 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai mService.mLifecycleManager.scheduleTransaction(prev.app.thread, prev.appToken, PauseActivityItem.obtain(prev.finishing, userLeaving, - prev.configChangeFlags, pauseImmediately)); + prev.configChangeFlags, pauseImmediately).setDescription( + prev.getLifecycleDescription("startPausingLocked"))); } catch (Exception e) { // Ignore exception, if process died other code will cleanup. Slog.w(TAG, "Exception thrown during pause", e); @@ -1524,7 +1525,8 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai if (r.finishing) { if (DEBUG_PAUSE) Slog.v(TAG, "Executing finish of failed to pause activity: " + r); - finishCurrentActivityLocked(r, FINISH_AFTER_VISIBLE, false); + finishCurrentActivityLocked(r, FINISH_AFTER_VISIBLE, false, + "activityPausedLocked"); } } } @@ -1541,7 +1543,8 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai prev.state = ActivityState.PAUSED; if (prev.finishing) { if (DEBUG_PAUSE) Slog.v(TAG_PAUSE, "Executing finish of activity: " + prev); - prev = finishCurrentActivityLocked(prev, FINISH_AFTER_VISIBLE, false); + prev = finishCurrentActivityLocked(prev, FINISH_AFTER_VISIBLE, false, + "completedPausedLocked"); } else if (prev.app != null) { if (DEBUG_PAUSE) Slog.v(TAG_PAUSE, "Enqueue pending stop if needed: " + prev + " wasStopping=" + wasStopping + " visible=" + prev.visible); @@ -3673,8 +3676,8 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai final int finishMode = (r.visible || r.nowVisible) ? FINISH_AFTER_VISIBLE : FINISH_AFTER_PAUSE; - final boolean removedActivity = finishCurrentActivityLocked(r, finishMode, oomAdj) - == null; + final boolean removedActivity = finishCurrentActivityLocked(r, finishMode, oomAdj, + "finishActivityLocked") == null; // The following code is an optimization. When the last non-task overlay activity // is removed from the task, we remove the entire task from the stack. However, @@ -3715,7 +3718,8 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai static final int FINISH_AFTER_PAUSE = 1; static final int FINISH_AFTER_VISIBLE = 2; - final ActivityRecord finishCurrentActivityLocked(ActivityRecord r, int mode, boolean oomAdj) { + final ActivityRecord finishCurrentActivityLocked(ActivityRecord r, int mode, boolean oomAdj, + String reason) { // First things first: if this activity is currently visible, // and the resumed activity is not yet visible, then hold off on // finishing until the resumed one becomes visible. @@ -3758,7 +3762,7 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai || prevState == STOPPED || prevState == ActivityState.INITIALIZING) { r.makeFinishingLocked(); - boolean activityRemoved = destroyActivityLocked(r, true, "finish-imm"); + boolean activityRemoved = destroyActivityLocked(r, true, "finish-imm:" + reason); if (finishingActivityInNonFocusedStack) { // Finishing activity that was in paused state and it was in not currently focused @@ -3794,7 +3798,8 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai continue; } Slog.d(TAG, "finishAllActivitiesLocked: finishing " + r + " immediately"); - finishCurrentActivityLocked(r, FINISH_IMMEDIATELY, false); + finishCurrentActivityLocked(r, FINISH_IMMEDIATELY, false, + "finishAllActivitiesLocked"); } } if (noActivitiesInStack) { @@ -4882,7 +4887,8 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai + r.intent.getComponent().flattenToShortString()); // Force the destroy to skip right to removal. r.app = null; - finishCurrentActivityLocked(r, FINISH_IMMEDIATELY, false); + finishCurrentActivityLocked(r, FINISH_IMMEDIATELY, false, + "handleAppCrashedLocked"); } } } diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java index 4928e908ce44..5c30764e1e85 100644 --- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java +++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java @@ -1453,7 +1453,8 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D lifecycleItem = ResumeActivityItem.obtain(mService.isNextTransitionForward()) .setDescription(r.getLifecycleDescription("realStartActivityLocked")); } else { - lifecycleItem = PauseActivityItem.obtain(); + lifecycleItem = PauseActivityItem.obtain() + .setDescription(r.getLifecycleDescription("realStartActivityLocked")); } clientTransaction.setLifecycleStateRequest(lifecycleItem); @@ -1955,7 +1956,8 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D final ActivityStack stack = r.getStack(); if (stack != null) { if (r.finishing) { - stack.finishCurrentActivityLocked(r, ActivityStack.FINISH_IMMEDIATELY, false); + stack.finishCurrentActivityLocked(r, ActivityStack.FINISH_IMMEDIATELY, false, + "activityIdleInternalLocked"); } else { stack.stopActivityLocked(r); } diff --git a/services/core/java/com/android/server/am/ActivityStartController.java b/services/core/java/com/android/server/am/ActivityStartController.java index da11f6861f54..868f90df5c1b 100644 --- a/services/core/java/com/android/server/am/ActivityStartController.java +++ b/services/core/java/com/android/server/am/ActivityStartController.java @@ -41,6 +41,7 @@ import android.os.Looper; import android.os.Message; import android.provider.Settings; import android.util.Slog; +import android.view.RemoteAnimationAdapter; import com.android.internal.annotations.VisibleForTesting; import com.android.server.am.ActivityStackSupervisor.PendingActivityLaunch; @@ -85,6 +86,8 @@ public class ActivityStartController { private final Handler mHandler; + private final PendingRemoteAnimationRegistry mPendingRemoteAnimationRegistry; + private final class StartHandler extends Handler { public StartHandler(Looper looper) { super(looper, null, true); @@ -123,6 +126,8 @@ public class ActivityStartController { mHandler = new StartHandler(mService.mHandlerThread.getLooper()); mFactory = factory; mFactory.setController(this); + mPendingRemoteAnimationRegistry = new PendingRemoteAnimationRegistry(service, + service.mHandler); } /** @@ -399,6 +404,15 @@ public class ActivityStartController { return mPendingActivityLaunches.size() < pendingLaunches; } + void registerRemoteAnimationForNextActivityStart(String packageName, + RemoteAnimationAdapter adapter) { + mPendingRemoteAnimationRegistry.addPendingAnimation(packageName, adapter); + } + + PendingRemoteAnimationRegistry getPendingRemoteAnimationRegistry() { + return mPendingRemoteAnimationRegistry; + } + void dump(PrintWriter pw, String prefix, String dumpPackage) { pw.print(prefix); pw.print("mLastHomeActivityStartResult="); diff --git a/services/core/java/com/android/server/am/ActivityStarter.java b/services/core/java/com/android/server/am/ActivityStarter.java index 055b89b6a6f0..0dcefbfd26a4 100644 --- a/services/core/java/com/android/server/am/ActivityStarter.java +++ b/services/core/java/com/android/server/am/ActivityStarter.java @@ -708,6 +708,8 @@ class ActivityStarter { ActivityOptions checkedOptions = options != null ? options.getOptions(intent, aInfo, callerApp, mSupervisor) : null; + checkedOptions = mService.getActivityStartController().getPendingRemoteAnimationRegistry() + .overrideOptionsIfNeeded(callingPackage, checkedOptions); if (mService.mController != null) { try { // The Intent we give to the watcher has the extra data diff --git a/services/core/java/com/android/server/am/PendingRemoteAnimationRegistry.java b/services/core/java/com/android/server/am/PendingRemoteAnimationRegistry.java new file mode 100644 index 000000000000..77713f57d017 --- /dev/null +++ b/services/core/java/com/android/server/am/PendingRemoteAnimationRegistry.java @@ -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 + */ + +package com.android.server.am; + +import android.annotation.Nullable; +import android.app.ActivityOptions; +import android.os.Handler; +import android.util.ArrayMap; +import android.view.RemoteAnimationAdapter; + +/** + * Registry to keep track of remote animations to be run for activity starts from a certain package. + * + * @see ActivityManagerService#registerRemoteAnimationForNextActivityStart + */ +class PendingRemoteAnimationRegistry { + + private static final long TIMEOUT_MS = 3000; + + private final ArrayMap<String, Entry> mEntries = new ArrayMap<>(); + private final Handler mHandler; + private final ActivityManagerService mService; + + PendingRemoteAnimationRegistry(ActivityManagerService service, Handler handler) { + mService = service; + mHandler = handler; + } + + /** + * Adds a remote animation to be run for all activity starts originating from a certain package. + */ + void addPendingAnimation(String packageName, RemoteAnimationAdapter adapter) { + mEntries.put(packageName, new Entry(packageName, adapter)); + } + + /** + * Overrides the activity options with a registered remote animation for a certain calling + * package if such a remote animation is registered. + */ + ActivityOptions overrideOptionsIfNeeded(String callingPackage, + @Nullable ActivityOptions options) { + final Entry entry = mEntries.get(callingPackage); + if (entry == null) { + return options; + } + if (options == null) { + options = ActivityOptions.makeRemoteAnimation(entry.adapter); + } else { + options.setRemoteAnimationAdapter(entry.adapter); + } + mEntries.remove(callingPackage); + return options; + } + + private class Entry { + final String packageName; + final RemoteAnimationAdapter adapter; + + Entry(String packageName, RemoteAnimationAdapter adapter) { + this.packageName = packageName; + this.adapter = adapter; + mHandler.postDelayed(() -> { + synchronized (mService) { + final Entry entry = mEntries.get(packageName); + if (entry == this) { + mEntries.remove(packageName); + } + } + }, TIMEOUT_MS); + } + } +} diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index c5424b7ddbcf..76e0d8984cb8 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -1028,14 +1028,16 @@ public class AudioService extends IAudioService.Stub } private void checkAllAliasStreamVolumes() { - synchronized (VolumeStreamState.class) { - int numStreamTypes = AudioSystem.getNumStreamTypes(); - for (int streamType = 0; streamType < numStreamTypes; streamType++) { - mStreamStates[streamType] - .setAllIndexes(mStreamStates[mStreamVolumeAlias[streamType]], TAG); - // apply stream volume - if (!mStreamStates[streamType].mIsMuted) { - mStreamStates[streamType].applyAllVolumes(); + synchronized (mSettingsLock) { + synchronized (VolumeStreamState.class) { + int numStreamTypes = AudioSystem.getNumStreamTypes(); + for (int streamType = 0; streamType < numStreamTypes; streamType++) { + mStreamStates[streamType] + .setAllIndexes(mStreamStates[mStreamVolumeAlias[streamType]], TAG); + // apply stream volume + if (!mStreamStates[streamType].mIsMuted) { + mStreamStates[streamType].applyAllVolumes(); + } } } } @@ -1141,13 +1143,16 @@ public class AudioService extends IAudioService.Stub if (updateVolumes && mStreamStates != null) { updateDefaultVolumes(); - mStreamStates[AudioSystem.STREAM_DTMF].setAllIndexes(mStreamStates[dtmfStreamAlias], - caller); - - mStreamStates[AudioSystem.STREAM_ACCESSIBILITY].mVolumeIndexSettingName = - System.VOLUME_SETTINGS_INT[a11yStreamAlias]; - mStreamStates[AudioSystem.STREAM_ACCESSIBILITY].setAllIndexes( - mStreamStates[a11yStreamAlias], caller); + synchronized (mSettingsLock) { + synchronized (VolumeStreamState.class) { + mStreamStates[AudioSystem.STREAM_DTMF] + .setAllIndexes(mStreamStates[dtmfStreamAlias], caller); + mStreamStates[AudioSystem.STREAM_ACCESSIBILITY].mVolumeIndexSettingName = + System.VOLUME_SETTINGS_INT[a11yStreamAlias]; + mStreamStates[AudioSystem.STREAM_ACCESSIBILITY].setAllIndexes( + mStreamStates[a11yStreamAlias], caller); + } + } if (sIndependentA11yVolume) { // restore the a11y values from the settings mStreamStates[AudioSystem.STREAM_ACCESSIBILITY].readSettings(); @@ -4590,39 +4595,36 @@ public class AudioService extends IAudioService.Stub * @param srcStream * @param caller */ + // must be sync'd on mSettingsLock before VolumeStreamState.class + @GuardedBy("VolumeStreamState.class") public void setAllIndexes(VolumeStreamState srcStream, String caller) { if (mStreamType == srcStream.mStreamType) { return; } - synchronized (mSettingsLock) { - synchronized (VolumeStreamState.class) { - int srcStreamType = srcStream.getStreamType(); - // apply default device volume from source stream to all devices first in case - // some devices are present in this stream state but not in source stream state - int index = srcStream.getIndex(AudioSystem.DEVICE_OUT_DEFAULT); - index = rescaleIndex(index, srcStreamType, mStreamType); - for (int i = 0; i < mIndexMap.size(); i++) { - mIndexMap.put(mIndexMap.keyAt(i), index); - } - // Now apply actual volume for devices in source stream state - SparseIntArray srcMap = srcStream.mIndexMap; - for (int i = 0; i < srcMap.size(); i++) { - int device = srcMap.keyAt(i); - index = srcMap.valueAt(i); - index = rescaleIndex(index, srcStreamType, mStreamType); - - setIndex(index, device, caller); - } - } + int srcStreamType = srcStream.getStreamType(); + // apply default device volume from source stream to all devices first in case + // some devices are present in this stream state but not in source stream state + int index = srcStream.getIndex(AudioSystem.DEVICE_OUT_DEFAULT); + index = rescaleIndex(index, srcStreamType, mStreamType); + for (int i = 0; i < mIndexMap.size(); i++) { + mIndexMap.put(mIndexMap.keyAt(i), index); + } + // Now apply actual volume for devices in source stream state + SparseIntArray srcMap = srcStream.mIndexMap; + for (int i = 0; i < srcMap.size(); i++) { + int device = srcMap.keyAt(i); + index = srcMap.valueAt(i); + index = rescaleIndex(index, srcStreamType, mStreamType); + + setIndex(index, device, caller); } } - @GuardedBy("mSettingsLock") + // must be sync'd on mSettingsLock before VolumeStreamState.class + @GuardedBy("VolumeStreamState.class") public void setAllIndexesToMax() { - synchronized (VolumeStreamState.class) { - for (int i = 0; i < mIndexMap.size(); i++) { - mIndexMap.put(mIndexMap.keyAt(i), mIndexMax); - } + for (int i = 0; i < mIndexMap.size(); i++) { + mIndexMap.put(mIndexMap.keyAt(i), mIndexMax); } } @@ -6201,15 +6203,17 @@ public class AudioService extends IAudioService.Stub mCameraSoundForced = cameraSoundForced; if (cameraSoundForcedChanged) { if (!mIsSingleVolume) { - VolumeStreamState s = mStreamStates[AudioSystem.STREAM_SYSTEM_ENFORCED]; - if (cameraSoundForced) { - s.setAllIndexesToMax(); - mRingerModeAffectedStreams &= - ~(1 << AudioSystem.STREAM_SYSTEM_ENFORCED); - } else { - s.setAllIndexes(mStreamStates[AudioSystem.STREAM_SYSTEM], TAG); - mRingerModeAffectedStreams |= - (1 << AudioSystem.STREAM_SYSTEM_ENFORCED); + synchronized (VolumeStreamState.class) { + VolumeStreamState s = mStreamStates[AudioSystem.STREAM_SYSTEM_ENFORCED]; + if (cameraSoundForced) { + s.setAllIndexesToMax(); + mRingerModeAffectedStreams &= + ~(1 << AudioSystem.STREAM_SYSTEM_ENFORCED); + } else { + s.setAllIndexes(mStreamStates[AudioSystem.STREAM_SYSTEM], TAG); + mRingerModeAffectedStreams |= + (1 << AudioSystem.STREAM_SYSTEM_ENFORCED); + } } // take new state into account for streams muted by ringer mode setRingerModeInt(getRingerModeInternal(), false); diff --git a/services/core/java/com/android/server/locksettings/LockSettingsStrongAuth.java b/services/core/java/com/android/server/locksettings/LockSettingsStrongAuth.java index c9c93293e2ee..c4f1f3d7369d 100644 --- a/services/core/java/com/android/server/locksettings/LockSettingsStrongAuth.java +++ b/services/core/java/com/android/server/locksettings/LockSettingsStrongAuth.java @@ -19,9 +19,6 @@ package com.android.server.locksettings; import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED; import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_TIMEOUT; -import com.android.internal.widget.LockPatternUtils; -import com.android.internal.widget.LockPatternUtils.StrongAuthTracker; - import android.app.AlarmManager; import android.app.AlarmManager.OnAlarmListener; import android.app.admin.DevicePolicyManager; @@ -29,10 +26,9 @@ import android.app.trust.IStrongAuthTracker; import android.content.Context; import android.content.pm.PackageManager; import android.hardware.fingerprint.FingerprintManager; -import android.os.Binder; -import android.os.DeadObjectException; import android.os.Handler; import android.os.Message; +import android.os.RemoteCallbackList; import android.os.RemoteException; import android.os.SystemClock; import android.os.UserHandle; @@ -40,7 +36,7 @@ import android.util.ArrayMap; import android.util.Slog; import android.util.SparseIntArray; -import java.util.ArrayList; +import com.android.internal.widget.LockPatternUtils.StrongAuthTracker; /** * Keeps track of requests for strong authentication. @@ -58,7 +54,7 @@ public class LockSettingsStrongAuth { private static final String STRONG_AUTH_TIMEOUT_ALARM_TAG = "LockSettingsStrongAuth.timeoutForUser"; - private final ArrayList<IStrongAuthTracker> mStrongAuthTrackers = new ArrayList<>(); + private final RemoteCallbackList<IStrongAuthTracker> mTrackers = new RemoteCallbackList<>(); private final SparseIntArray mStrongAuthForUser = new SparseIntArray(); private final ArrayMap<Integer, StrongAuthTimeoutAlarmListener> mStrongAuthTimeoutAlarmListenerForUser = new ArrayMap<>(); @@ -82,12 +78,7 @@ public class LockSettingsStrongAuth { } private void handleAddStrongAuthTracker(IStrongAuthTracker tracker) { - for (int i = 0; i < mStrongAuthTrackers.size(); i++) { - if (mStrongAuthTrackers.get(i).asBinder() == tracker.asBinder()) { - return; - } - } - mStrongAuthTrackers.add(tracker); + mTrackers.register(tracker); for (int i = 0; i < mStrongAuthForUser.size(); i++) { int key = mStrongAuthForUser.keyAt(i); @@ -101,12 +92,7 @@ public class LockSettingsStrongAuth { } private void handleRemoveStrongAuthTracker(IStrongAuthTracker tracker) { - for (int i = 0; i < mStrongAuthTrackers.size(); i++) { - if (mStrongAuthTrackers.get(i).asBinder() == tracker.asBinder()) { - mStrongAuthTrackers.remove(i); - return; - } - } + mTrackers.unregister(tracker); } private void handleRequireStrongAuth(int strongAuthReason, int userId) { @@ -157,16 +143,19 @@ public class LockSettingsStrongAuth { } private void notifyStrongAuthTrackers(int strongAuthReason, int userId) { - for (int i = 0; i < mStrongAuthTrackers.size(); i++) { - try { - mStrongAuthTrackers.get(i).onStrongAuthRequiredChanged(strongAuthReason, userId); - } catch (DeadObjectException e) { - Slog.d(TAG, "Removing dead StrongAuthTracker."); - mStrongAuthTrackers.remove(i); + int i = mTrackers.beginBroadcast(); + try { + while (i > 0) { i--; - } catch (RemoteException e) { - Slog.e(TAG, "Exception while notifying StrongAuthTracker.", e); + try { + mTrackers.getBroadcastItem(i).onStrongAuthRequiredChanged( + strongAuthReason, userId); + } catch (RemoteException e) { + Slog.e(TAG, "Exception while notifying StrongAuthTracker.", e); + } } + } finally { + mTrackers.finishBroadcast(); } } @@ -243,4 +232,5 @@ public class LockSettingsStrongAuth { } } }; + } diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/OWNERS b/services/core/java/com/android/server/locksettings/recoverablekeystore/OWNERS new file mode 100644 index 000000000000..bb487fb52c9f --- /dev/null +++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/OWNERS @@ -0,0 +1,4 @@ +aseemk@google.com +bozhu@google.com +dementyev@google.com +robertberry@google.com diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java index f34662909a85..e2b2d46eb4a6 100644 --- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java +++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java @@ -145,6 +145,7 @@ import android.net.TrafficStats; import android.net.wifi.WifiConfiguration; import android.net.wifi.WifiManager; import android.os.Binder; +import android.os.Build; import android.os.Environment; import android.os.Handler; import android.os.HandlerThread; @@ -2759,6 +2760,13 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { return; } + // Fourth check: is caller a testing app on a debug build? + final boolean enableDebug = Build.IS_USERDEBUG || Build.IS_ENG; + if (enableDebug && callingPackage + .equals(SystemProperties.get("fw.sub_plan_owner." + subId, null))) { + return; + } + // Final check: does the caller hold a permission? mContext.enforceCallingOrSelfPermission(MANAGE_SUBSCRIPTION_PLANS, TAG); } diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java index a0577b15736b..0eeaf661635d 100644 --- a/services/core/java/com/android/server/pm/UserManagerService.java +++ b/services/core/java/com/android/server/pm/UserManagerService.java @@ -1486,6 +1486,23 @@ public class UserManagerService extends IUserManager.Stub { return restrictions != null && restrictions.getBoolean(restrictionKey); } + /** @return if any user has the given restriction. */ + @Override + public boolean hasUserRestrictionOnAnyUser(String restrictionKey) { + if (!UserRestrictionsUtils.isValidRestriction(restrictionKey)) { + return false; + } + final List<UserInfo> users = getUsers(/* excludeDying= */ true); + for (int i = 0; i < users.size(); i++) { + final int userId = users.get(i).id; + Bundle restrictions = getEffectiveUserRestrictions(userId); + if (restrictions != null && restrictions.getBoolean(restrictionKey)) { + return true; + } + } + return false; + } + /** * @hide * diff --git a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java index 23185d7fb5ab..41570c48c474 100644 --- a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java +++ b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java @@ -33,6 +33,7 @@ import android.os.UserHandle; import android.os.UserManager; import android.os.UserManagerInternal; import android.provider.Settings; +import android.provider.Settings.Global; import android.telephony.SubscriptionInfo; import android.telephony.SubscriptionManager; import android.util.Log; @@ -581,6 +582,15 @@ public class UserRestrictionsUtils { Settings.Secure.DOZE_PULSE_ON_DOUBLE_TAP, "0"); } break; + case UserManager.DISALLOW_CONFIG_LOCATION: + // When DISALLOW_CONFIG_LOCATION is set on any user, we undo the global + // kill switch. + if (newValue) { + android.provider.Settings.Global.putString( + context.getContentResolver(), + Global.LOCATION_GLOBAL_KILL_SWITCH, "0"); + } + break; } } finally { Binder.restoreCallingIdentity(id); diff --git a/services/tests/servicestests/src/com/android/server/am/PendingRemoteAnimationRegistryTest.java b/services/tests/servicestests/src/com/android/server/am/PendingRemoteAnimationRegistryTest.java new file mode 100644 index 000000000000..2baf9952cb9e --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/am/PendingRemoteAnimationRegistryTest.java @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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.am; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; + +import android.annotation.Nullable; +import android.app.ActivityOptions; +import android.os.Handler; +import android.platform.test.annotations.Presubmit; +import android.support.test.filters.FlakyTest; +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; +import android.util.ArrayMap; +import android.view.RemoteAnimationAdapter; + +import com.android.server.testutils.OffsettableClock; +import com.android.server.testutils.TestHandler; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +/** + * atest PendingRemoteAnimationRegistryTest + */ +@SmallTest +@Presubmit +@FlakyTest +@RunWith(AndroidJUnit4.class) +public class PendingRemoteAnimationRegistryTest extends ActivityTestsBase { + + @Mock RemoteAnimationAdapter mAdapter; + private PendingRemoteAnimationRegistry mRegistry; + private final OffsettableClock mClock = new OffsettableClock.Stopped(); + private TestHandler mHandler; + private ActivityManagerService mService; + + @Before + public void setUp() throws Exception { + super.setUp(); + MockitoAnnotations.initMocks(this); + mService = createActivityManagerService(); + mService.mHandlerThread.getThreadHandler().runWithScissors(() -> { + mHandler = new TestHandler(null, mClock); + }, 0); + mRegistry = new PendingRemoteAnimationRegistry(mService, mHandler); + } + + @Test + public void testOverrideActivityOptions() { + mRegistry.addPendingAnimation("com.android.test", mAdapter); + ActivityOptions opts = ActivityOptions.makeBasic(); + opts = mRegistry.overrideOptionsIfNeeded("com.android.test", opts); + assertEquals(mAdapter, opts.getRemoteAnimationAdapter()); + } + + @Test + public void testOverrideActivityOptions_null() { + mRegistry.addPendingAnimation("com.android.test", mAdapter); + final ActivityOptions opts = mRegistry.overrideOptionsIfNeeded("com.android.test", null); + assertNotNull(opts); + assertEquals(mAdapter, opts.getRemoteAnimationAdapter()); + } + + @Test + public void testTimeout() { + mRegistry.addPendingAnimation("com.android.test", mAdapter); + mClock.fastForward(5000); + mHandler.timeAdvance(); + assertNull(mRegistry.overrideOptionsIfNeeded("com.android.test", null)); + } + + @Test + public void testTimeout_overridenEntry() { + mRegistry.addPendingAnimation("com.android.test", mAdapter); + mClock.fastForward(2500); + mHandler.timeAdvance(); + mRegistry.addPendingAnimation("com.android.test", mAdapter); + mClock.fastForward(1000); + mHandler.timeAdvance(); + final ActivityOptions opts = mRegistry.overrideOptionsIfNeeded("com.android.test", null); + assertEquals(mAdapter, opts.getRemoteAnimationAdapter()); + } +} diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/OWNERS b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/OWNERS new file mode 100644 index 000000000000..bb487fb52c9f --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/OWNERS @@ -0,0 +1,4 @@ +aseemk@google.com +bozhu@google.com +dementyev@google.com +robertberry@google.com diff --git a/tests/net/java/android/net/IpSecConfigTest.java b/tests/net/java/android/net/IpSecConfigTest.java index f6c5532363e8..f186ee55d2c7 100644 --- a/tests/net/java/android/net/IpSecConfigTest.java +++ b/tests/net/java/android/net/IpSecConfigTest.java @@ -17,6 +17,7 @@ package android.net; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; @@ -48,18 +49,12 @@ public class IpSecConfigTest { assertEquals(IpSecManager.INVALID_RESOURCE_ID, c.getSpiResourceId()); } - @Test - public void testParcelUnparcel() throws Exception { - assertParcelingIsLossless(new IpSecConfig()); - + private IpSecConfig getSampleConfig() { IpSecConfig c = new IpSecConfig(); c.setMode(IpSecTransform.MODE_TUNNEL); c.setSourceAddress("0.0.0.0"); c.setDestinationAddress("1.2.3.4"); - c.setEncapType(android.system.OsConstants.UDP_ENCAP_ESPINUDP); - c.setEncapSocketResourceId(7); - c.setEncapRemotePort(22); - c.setNattKeepaliveInterval(42); + c.setSpiResourceId(1984); c.setEncryption( new IpSecAlgorithm( IpSecAlgorithm.CRYPT_AES_CBC, @@ -68,7 +63,37 @@ public class IpSecConfigTest { new IpSecAlgorithm( IpSecAlgorithm.AUTH_HMAC_MD5, new byte[] {1, 2, 3, 4, 5, 6, 7, 8, 9, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF, 0})); - c.setSpiResourceId(1984); + c.setAuthenticatedEncryption( + new IpSecAlgorithm( + IpSecAlgorithm.AUTH_CRYPT_AES_GCM, + new byte[] { + 1, 2, 3, 4, 5, 6, 7, 8, 9, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF, 0, 1, 2, 3, 4 + }, + 128)); + c.setEncapType(android.system.OsConstants.UDP_ENCAP_ESPINUDP); + c.setEncapSocketResourceId(7); + c.setEncapRemotePort(22); + c.setNattKeepaliveInterval(42); + c.setMarkValue(12); + c.setMarkMask(23); + + return c; + } + + @Test + public void testCopyConstructor() { + IpSecConfig original = getSampleConfig(); + IpSecConfig copy = new IpSecConfig(original); + + assertTrue(IpSecConfig.equals(original, copy)); + assertFalse(original == copy); + } + + @Test + public void testParcelUnparcel() throws Exception { + assertParcelingIsLossless(new IpSecConfig()); + + IpSecConfig c = getSampleConfig(); assertParcelingIsLossless(c); } diff --git a/tests/net/java/android/net/IpSecTransformTest.java b/tests/net/java/android/net/IpSecTransformTest.java new file mode 100644 index 000000000000..b4342df58549 --- /dev/null +++ b/tests/net/java/android/net/IpSecTransformTest.java @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net; + +import static org.junit.Assert.assertFalse; + +import android.support.test.filters.SmallTest; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Unit tests for {@link IpSecTransform}. */ +@SmallTest +@RunWith(JUnit4.class) +public class IpSecTransformTest { + + @Test + public void testCreateTransformCopiesConfig() { + // Create a config with a few parameters to make sure it's not empty + IpSecConfig config = new IpSecConfig(); + config.setSourceAddress("0.0.0.0"); + config.setDestinationAddress("1.2.3.4"); + config.setSpiResourceId(1984); + + IpSecTransform preModification = new IpSecTransform(null, config); + + config.setSpiResourceId(1985); + IpSecTransform postModification = new IpSecTransform(null, config); + + assertFalse(IpSecTransform.equals(preModification, postModification)); + } + + @Test + public void testCreateTransformsWithSameConfigEqual() { + // Create a config with a few parameters to make sure it's not empty + IpSecConfig config = new IpSecConfig(); + config.setSourceAddress("0.0.0.0"); + config.setDestinationAddress("1.2.3.4"); + config.setSpiResourceId(1984); + + IpSecTransform config1 = new IpSecTransform(null, config); + IpSecTransform config2 = new IpSecTransform(null, config); + + assertFalse(IpSecTransform.equals(config1, config2)); + } +} diff --git a/tools/aapt2/Debug.cpp b/tools/aapt2/Debug.cpp index 5831875680ac..249557af921e 100644 --- a/tools/aapt2/Debug.cpp +++ b/tools/aapt2/Debug.cpp @@ -414,59 +414,70 @@ class XmlPrinter : public xml::ConstVisitor { public: using xml::ConstVisitor::Visit; - void Visit(const xml::Element* el) override { - const size_t previous_size = prefix_.size(); + XmlPrinter(Printer* printer) : printer_(printer) { + } + void Visit(const xml::Element* el) override { for (const xml::NamespaceDecl& decl : el->namespace_decls) { - std::cerr << prefix_ << "N: " << decl.prefix << "=" << decl.uri - << " (line=" << decl.line_number << ")\n"; - prefix_ += " "; + printer_->Println(StringPrintf("N: %s=%s (line=%zu)", decl.prefix.c_str(), decl.uri.c_str(), + decl.line_number)); + printer_->Indent(); } - std::cerr << prefix_ << "E: "; + printer_->Print("E: "); if (!el->namespace_uri.empty()) { - std::cerr << el->namespace_uri << ":"; + printer_->Print(el->namespace_uri); + printer_->Print(":"); } - std::cerr << el->name << " (line=" << el->line_number << ")\n"; + printer_->Println(StringPrintf("%s (line=%zu)", el->name.c_str(), el->line_number)); + printer_->Indent(); for (const xml::Attribute& attr : el->attributes) { - std::cerr << prefix_ << " A: "; + printer_->Print("A: "); if (!attr.namespace_uri.empty()) { - std::cerr << attr.namespace_uri << ":"; + printer_->Print(attr.namespace_uri); + printer_->Print(":"); } - std::cerr << attr.name; + printer_->Print(attr.name); if (attr.compiled_attribute) { - std::cerr << "(" << attr.compiled_attribute.value().id.value_or_default(ResourceId(0x0)) - << ")"; + printer_->Print("("); + printer_->Print( + attr.compiled_attribute.value().id.value_or_default(ResourceId(0)).to_string()); + printer_->Print(")"); } - std::cerr << "="; + printer_->Print("="); if (attr.compiled_value != nullptr) { - std::cerr << *attr.compiled_value; + attr.compiled_value->PrettyPrint(printer_); } else { - std::cerr << attr.value; + printer_->Print(attr.value); } - std::cerr << "\n"; + printer_->Println(); } - prefix_ += " "; + printer_->Indent(); xml::ConstVisitor::Visit(el); - prefix_.resize(previous_size); + printer_->Undent(); + printer_->Undent(); + + for (size_t i = 0; i < el->namespace_decls.size(); i++) { + printer_->Undent(); + } } void Visit(const xml::Text* text) override { - std::cerr << prefix_ << "T: '" << text->text << "'\n"; + printer_->Println(StringPrintf("T: '%s'", text->text.c_str())); } private: - std::string prefix_; + Printer* printer_; }; } // namespace -void Debug::DumpXml(const xml::XmlResource& doc) { - XmlPrinter printer; - doc.root->Accept(&printer); +void Debug::DumpXml(const xml::XmlResource& doc, Printer* printer) { + XmlPrinter xml_visitor(printer); + doc.root->Accept(&xml_visitor); } } // namespace aapt diff --git a/tools/aapt2/Debug.h b/tools/aapt2/Debug.h index 6209a04c3c2d..382707e1d4cd 100644 --- a/tools/aapt2/Debug.h +++ b/tools/aapt2/Debug.h @@ -37,7 +37,7 @@ struct Debug { text::Printer* printer); static void PrintStyleGraph(ResourceTable* table, const ResourceName& target_style); static void DumpHex(const void* data, size_t len); - static void DumpXml(const xml::XmlResource& doc); + static void DumpXml(const xml::XmlResource& doc, text::Printer* printer); }; } // namespace aapt diff --git a/tools/aapt2/LoadedApk.cpp b/tools/aapt2/LoadedApk.cpp index 20a9f417228c..ac28227b8778 100644 --- a/tools/aapt2/LoadedApk.cpp +++ b/tools/aapt2/LoadedApk.cpp @@ -222,7 +222,9 @@ bool LoadedApk::WriteToArchive(IAaptContext* context, ResourceTable* split_table } else if (manifest != nullptr && path == "AndroidManifest.xml") { BigBuffer buffer(8192); - XmlFlattener xml_flattener(&buffer, {}); + XmlFlattenerOptions xml_flattener_options; + xml_flattener_options.use_utf16 = true; + XmlFlattener xml_flattener(&buffer, xml_flattener_options); if (!xml_flattener.Consume(context, manifest)) { context->GetDiagnostics()->Error(DiagMessage(path) << "flattening failed"); return false; diff --git a/tools/aapt2/ResourceUtils.cpp b/tools/aapt2/ResourceUtils.cpp index 02ac86c94b46..628466d0a281 100644 --- a/tools/aapt2/ResourceUtils.cpp +++ b/tools/aapt2/ResourceUtils.cpp @@ -520,6 +520,10 @@ std::unique_ptr<BinaryPrimitive> TryParseInt(const StringPiece& str) { return util::make_unique<BinaryPrimitive>(value); } +std::unique_ptr<BinaryPrimitive> MakeInt(uint32_t val) { + return util::make_unique<BinaryPrimitive>(android::Res_value::TYPE_INT_DEC, val); +} + std::unique_ptr<BinaryPrimitive> TryParseFloat(const StringPiece& str) { std::u16string str16 = util::Utf8ToUtf16(util::TrimWhitespace(str)); android::Res_value value; diff --git a/tools/aapt2/ResourceUtils.h b/tools/aapt2/ResourceUtils.h index 36f6c2bda8f8..f83d49ee5591 100644 --- a/tools/aapt2/ResourceUtils.h +++ b/tools/aapt2/ResourceUtils.h @@ -165,6 +165,9 @@ std::unique_ptr<BinaryPrimitive> MakeBool(bool val); */ std::unique_ptr<BinaryPrimitive> TryParseInt(const android::StringPiece& str); +// Returns an integer BinaryPrimitive. +std::unique_ptr<BinaryPrimitive> MakeInt(uint32_t value); + /* * Returns a BinaryPrimitve object representing a floating point number * (float, dimension, etc) if the string was parsed as one. diff --git a/tools/aapt2/cmd/Dump.cpp b/tools/aapt2/cmd/Dump.cpp index 3d2fb556cf59..8e7e5e59bc31 100644 --- a/tools/aapt2/cmd/Dump.cpp +++ b/tools/aapt2/cmd/Dump.cpp @@ -38,6 +38,13 @@ using ::android::base::StringPrintf; namespace aapt { +struct DumpOptions { + DebugPrintTableOptions print_options; + + // The path to a file within an APK to dump. + Maybe<std::string> file_to_dump_path; +}; + static const char* ResourceFileTypeToString(const ResourceFile::Type& type) { switch (type) { case ResourceFile::Type::kPng: @@ -69,8 +76,52 @@ static void DumpCompiledFile(const ResourceFile& file, const Source& source, off printer->Println(StringPrintf("Data: offset=%" PRIi64 " length=%zd", offset, len)); } +static bool DumpXmlFile(IAaptContext* context, io::IFile* file, bool proto, + text::Printer* printer) { + std::unique_ptr<xml::XmlResource> doc; + if (proto) { + std::unique_ptr<io::InputStream> in = file->OpenInputStream(); + if (in == nullptr) { + context->GetDiagnostics()->Error(DiagMessage() << "failed to open file"); + return false; + } + + io::ZeroCopyInputAdaptor adaptor(in.get()); + pb::XmlNode pb_node; + if (!pb_node.ParseFromZeroCopyStream(&adaptor)) { + context->GetDiagnostics()->Error(DiagMessage() << "failed to parse file as proto XML"); + return false; + } + + std::string err; + doc = DeserializeXmlResourceFromPb(pb_node, &err); + if (doc == nullptr) { + context->GetDiagnostics()->Error(DiagMessage() << "failed to deserialize proto XML"); + return false; + } + printer->Println("Proto XML"); + } else { + std::unique_ptr<io::IData> data = file->OpenAsData(); + if (data == nullptr) { + context->GetDiagnostics()->Error(DiagMessage() << "failed to open file"); + return false; + } + + std::string err; + doc = xml::Inflate(data->data(), data->size(), &err); + if (doc == nullptr) { + context->GetDiagnostics()->Error(DiagMessage() << "failed to parse file as binary XML"); + return false; + } + printer->Println("Binary XML"); + } + + Debug::DumpXml(*doc, printer); + return true; +} + static bool TryDumpFile(IAaptContext* context, const std::string& file_path, - const DebugPrintTableOptions& print_options) { + const DumpOptions& options) { // Use a smaller buffer so that there is less latency for dumping to stdout. constexpr size_t kStdOutBufferSize = 1024u; io::FileOutputStream fout(STDOUT_FILENO, kStdOutBufferSize); @@ -80,7 +131,10 @@ static bool TryDumpFile(IAaptContext* context, const std::string& file_path, std::unique_ptr<io::ZipFileCollection> zip = io::ZipFileCollection::Create(file_path, &err); if (zip) { ResourceTable table; + bool proto = false; if (io::IFile* file = zip->FindFile("resources.pb")) { + proto = true; + std::unique_ptr<io::IData> data = file->OpenAsData(); if (data == nullptr) { context->GetDiagnostics()->Error(DiagMessage(file_path) << "failed to open resources.pb"); @@ -98,8 +152,6 @@ static bool TryDumpFile(IAaptContext* context, const std::string& file_path, << "failed to parse table: " << err); return false; } - - printer.Println("Proto APK"); } else if (io::IFile* file = zip->FindFile("resources.arsc")) { std::unique_ptr<io::IData> data = file->OpenAsData(); if (!data) { @@ -112,12 +164,26 @@ static bool TryDumpFile(IAaptContext* context, const std::string& file_path, if (!parser.Parse()) { return false; } + } - printer.Println("Binary APK"); + if (!options.file_to_dump_path) { + if (proto) { + printer.Println("Proto APK"); + } else { + printer.Println("Binary APK"); + } + Debug::PrintTable(table, options.print_options, &printer); + return true; } - Debug::PrintTable(table, print_options, &printer); - return true; + io::IFile* file = zip->FindFile(options.file_to_dump_path.value()); + if (file == nullptr) { + context->GetDiagnostics()->Error(DiagMessage(file_path) + << "file '" << options.file_to_dump_path.value() + << "' not found in APK"); + return false; + } + return DumpXmlFile(context, file, proto, &printer); } err.clear(); @@ -159,7 +225,7 @@ static bool TryDumpFile(IAaptContext* context, const std::string& file_path, } printer.Indent(); - Debug::PrintTable(table, print_options, &printer); + Debug::PrintTable(table, options.print_options, &printer); printer.Undent(); } else if (entry->Type() == ContainerEntryType::kResFile) { printer.Println("kResFile"); @@ -243,10 +309,13 @@ class DumpContext : public IAaptContext { int Dump(const std::vector<StringPiece>& args) { bool verbose = false; bool no_values = false; + DumpOptions options; Flags flags = Flags() .OptionalSwitch("--no-values", "Suppresses output of values when displaying resource tables.", &no_values) + .OptionalFlag("--file", "Dumps the specified file from the APK passed as arg.", + &options.file_to_dump_path) .OptionalSwitch("-v", "increase verbosity of output", &verbose); if (!flags.Parse("aapt2 dump", args, &std::cerr)) { return 1; @@ -255,11 +324,10 @@ int Dump(const std::vector<StringPiece>& args) { DumpContext context; context.SetVerbose(verbose); - DebugPrintTableOptions dump_table_options; - dump_table_options.show_sources = true; - dump_table_options.show_values = !no_values; + options.print_options.show_sources = true; + options.print_options.show_values = !no_values; for (const std::string& arg : flags.GetArgs()) { - if (!TryDumpFile(&context, arg, dump_table_options)) { + if (!TryDumpFile(&context, arg, options)) { return 1; } } diff --git a/tools/aapt2/configuration/ConfigurationParser.cpp b/tools/aapt2/configuration/ConfigurationParser.cpp index eabeb47fccec..902334b98d00 100644 --- a/tools/aapt2/configuration/ConfigurationParser.cpp +++ b/tools/aapt2/configuration/ConfigurationParser.cpp @@ -20,6 +20,7 @@ #include <functional> #include <map> #include <memory> +#include <string> #include <utility> #include "android-base/file.h" @@ -93,6 +94,7 @@ class NoopDiagnostics : public IDiagnostics { }; NoopDiagnostics noop_; +/** Returns the value of the label attribute for a given element. */ std::string GetLabel(const Element* element, IDiagnostics* diag) { std::string label; for (const auto& attr : element->attributes) { @@ -108,6 +110,18 @@ std::string GetLabel(const Element* element, IDiagnostics* diag) { return label; } +/** Returns the value of the version-code-order attribute for a given element. */ +Maybe<int32_t> GetVersionCodeOrder(const Element* element, IDiagnostics* diag) { + const xml::Attribute* version = element->FindAttribute("", "version-code-order"); + if (version == nullptr) { + std::string label = GetLabel(element, diag); + diag->Error(DiagMessage() << "No version-code-order found for element '" << element->name + << "' with label '" << label << "'"); + return {}; + } + return std::stoi(version->value); +} + /** XML node visitor that removes all of the namespace URIs from the node and all children. */ class NamespaceVisitor : public xml::Visitor { public: @@ -437,26 +451,37 @@ Maybe<std::vector<OutputArtifact>> ConfigurationParser::Parse( // Convert from a parsed configuration to a list of artifacts for processing. const std::string& apk_name = file::GetFilename(apk_path).to_string(); std::vector<OutputArtifact> output_artifacts; - bool has_errors = false; PostProcessingConfiguration& config = maybe_config.value(); - config.SortArtifacts(); + bool valid = true; int version = 1; + for (const ConfiguredArtifact& artifact : config.artifacts) { Maybe<OutputArtifact> output_artifact = ToOutputArtifact(artifact, apk_name, config, diag_); if (!output_artifact) { // Defer return an error condition so that all errors are reported. - has_errors = true; + valid = false; } else { output_artifact.value().version = version++; output_artifacts.push_back(std::move(output_artifact.value())); } } - if (has_errors) { + if (!config.ValidateVersionCodeOrdering(diag_)) { + diag_->Error(DiagMessage() << "could not validate post processing configuration"); + valid = false; + } + + if (valid) { + // Sorting artifacts requires that all references are valid as it uses them to determine order. + config.SortArtifacts(); + } + + if (!valid) { return {}; } + return {output_artifacts}; } @@ -509,8 +534,15 @@ bool AbiGroupTagHandler(PostProcessingConfiguration* config, Element* root_eleme return false; } - auto& group = GetOrCreateGroup(label, &config->abi_groups); bool valid = true; + OrderedEntry<Abi>& entry = config->abi_groups[label]; + Maybe<int32_t> order = GetVersionCodeOrder(root_element, diag); + if (!order) { + valid = false; + } else { + entry.order = order.value(); + } + auto& group = entry.entry; // Special case for empty abi-group tag. Label will be used as the ABI. if (root_element->GetChildElements().empty()) { @@ -519,7 +551,7 @@ bool AbiGroupTagHandler(PostProcessingConfiguration* config, Element* root_eleme return false; } group.push_back(abi->second); - return true; + return valid; } for (auto* child : root_element->GetChildElements()) { @@ -553,8 +585,15 @@ bool ScreenDensityGroupTagHandler(PostProcessingConfiguration* config, Element* return false; } - auto& group = GetOrCreateGroup(label, &config->screen_density_groups); bool valid = true; + OrderedEntry<ConfigDescription>& entry = config->screen_density_groups[label]; + Maybe<int32_t> order = GetVersionCodeOrder(root_element, diag); + if (!order) { + valid = false; + } else { + entry.order = order.value(); + } + auto& group = entry.entry; // Special case for empty screen-density-group tag. Label will be used as the screen density. if (root_element->GetChildElements().empty()) { @@ -613,8 +652,15 @@ bool LocaleGroupTagHandler(PostProcessingConfiguration* config, Element* root_el return false; } - auto& group = GetOrCreateGroup(label, &config->locale_groups); bool valid = true; + OrderedEntry<ConfigDescription>& entry = config->locale_groups[label]; + Maybe<int32_t> order = GetVersionCodeOrder(root_element, diag); + if (!order) { + valid = false; + } else { + entry.order = order.value(); + } + auto& group = entry.entry; // Special case to auto insert a locale for an empty group. Label will be used for locale. if (root_element->GetChildElements().empty()) { @@ -728,8 +774,15 @@ bool GlTextureGroupTagHandler(PostProcessingConfiguration* config, Element* root return false; } - auto& group = GetOrCreateGroup(label, &config->gl_texture_groups); bool valid = true; + OrderedEntry<GlTexture>& entry = config->gl_texture_groups[label]; + Maybe<int32_t> order = GetVersionCodeOrder(root_element, diag); + if (!order) { + valid = false; + } else { + entry.order = order.value(); + } + auto& group = entry.entry; GlTexture result; for (auto* child : root_element->GetChildElements()) { @@ -771,8 +824,15 @@ bool DeviceFeatureGroupTagHandler(PostProcessingConfiguration* config, Element* return false; } - auto& group = GetOrCreateGroup(label, &config->device_feature_groups); bool valid = true; + OrderedEntry<DeviceFeature>& entry = config->device_feature_groups[label]; + Maybe<int32_t> order = GetVersionCodeOrder(root_element, diag); + if (!order) { + valid = false; + } else { + entry.order = order.value(); + } + auto& group = entry.entry; for (auto* child : root_element->GetChildElements()) { if (child->name != "supports-feature") { diff --git a/tools/aapt2/configuration/ConfigurationParser.internal.h b/tools/aapt2/configuration/ConfigurationParser.internal.h index a583057427e6..f071a69fc9e3 100644 --- a/tools/aapt2/configuration/ConfigurationParser.internal.h +++ b/tools/aapt2/configuration/ConfigurationParser.internal.h @@ -33,18 +33,31 @@ namespace configuration { template <typename T> struct OrderedEntry { - size_t order; + int32_t order; std::vector<T> entry; }; -/** A mapping of group labels to group of configuration items. */ -template <class T> -using Group = std::unordered_map<std::string, OrderedEntry<T>>; - /** A mapping of group label to a single configuration item. */ template <class T> using Entry = std::unordered_map<std::string, T>; +/** A mapping of group labels to group of configuration items. */ +template <class T> +using Group = Entry<OrderedEntry<T>>; + +template<typename T> +bool IsGroupValid(const Group<T>& group, const std::string& name, IDiagnostics* diag) { + std::set<int32_t> orders; + for (const auto& p : group) { + orders.insert(p.second.order); + } + bool valid = orders.size() == group.size(); + if (!valid) { + diag->Error(DiagMessage() << name << " have overlapping version-code-order attributes"); + } + return valid; +} + /** Retrieves an entry from the provided Group, creating a new instance if one does not exist. */ template <typename T> std::vector<T>& GetOrCreateGroup(std::string label, Group<T>* group) { @@ -93,7 +106,7 @@ class ComparisonChain { private: template <typename T> - inline size_t GetGroupOrder(const Group<T>& groups, const Maybe<std::string>& label) { + inline size_t GetGroupOrder(const Entry<T>& groups, const Maybe<std::string>& label) { if (!label) { return std::numeric_limits<size_t>::max(); } @@ -141,6 +154,15 @@ struct PostProcessingConfiguration { Group<GlTexture> gl_texture_groups; Entry<AndroidSdk> android_sdks; + bool ValidateVersionCodeOrdering(IDiagnostics* diag) { + bool valid = IsGroupValid(abi_groups, "abi-groups", diag); + valid &= IsGroupValid(screen_density_groups, "screen-density-groups", diag); + valid &= IsGroupValid(locale_groups, "locale-groups", diag); + valid &= IsGroupValid(device_feature_groups, "device-feature-groups", diag); + valid &= IsGroupValid(gl_texture_groups, "gl-texture-groups", diag); + return valid; + } + /** * Sorts the configured artifacts based on the ordering of the groups in the configuration file. * The only exception to this rule is Android SDK versions. Larger SDK versions will have a larger diff --git a/tools/aapt2/configuration/ConfigurationParser_test.cpp b/tools/aapt2/configuration/ConfigurationParser_test.cpp index 0329846a5bb5..febbb2ed11a0 100644 --- a/tools/aapt2/configuration/ConfigurationParser_test.cpp +++ b/tools/aapt2/configuration/ConfigurationParser_test.cpp @@ -82,22 +82,22 @@ using ::testing::StrEq; constexpr const char* kValidConfig = R"(<?xml version="1.0" encoding="utf-8" ?> <post-process xmlns="http://schemas.android.com/tools/aapt"> <abi-groups> - <abi-group label="arm"> - <abi>armeabi-v7a</abi> - <abi>arm64-v8a</abi> - </abi-group> - <abi-group label="other"> + <abi-group label="other" version-code-order="2"> <abi>x86</abi> <abi>mips</abi> </abi-group> + <abi-group label="arm" version-code-order="1"> + <abi>armeabi-v7a</abi> + <abi>arm64-v8a</abi> + </abi-group> </abi-groups> <screen-density-groups> - <screen-density-group label="large"> + <screen-density-group label="large" version-code-order="2"> <screen-density>xhdpi</screen-density> <screen-density>xxhdpi</screen-density> <screen-density>xxxhdpi</screen-density> </screen-density-group> - <screen-density-group label="alldpi"> + <screen-density-group label="alldpi" version-code-order="1"> <screen-density>ldpi</screen-density> <screen-density>mdpi</screen-density> <screen-density>hdpi</screen-density> @@ -107,17 +107,20 @@ constexpr const char* kValidConfig = R"(<?xml version="1.0" encoding="utf-8" ?> </screen-density-group> </screen-density-groups> <locale-groups> - <locale-group label="europe"> + <locale-group label="europe" version-code-order="1"> <locale>en</locale> <locale>es</locale> <locale>fr</locale> <locale>de</locale> </locale-group> - <locale-group label="north-america"> + <locale-group label="north-america" version-code-order="2"> <locale>en</locale> <locale>es-rMX</locale> <locale>fr-rCA</locale> </locale-group> + <locale-group label="all" version-code-order="-1"> + <locale /> + </locale-group> </locale-groups> <android-sdks> <android-sdk @@ -131,14 +134,14 @@ constexpr const char* kValidConfig = R"(<?xml version="1.0" encoding="utf-8" ?> </android-sdk> </android-sdks> <gl-texture-groups> - <gl-texture-group label="dxt1"> + <gl-texture-group label="dxt1" version-code-order="2"> <gl-texture name="GL_EXT_texture_compression_dxt1"> <texture-path>assets/dxt1/*</texture-path> </gl-texture> </gl-texture-group> </gl-texture-groups> <device-feature-groups> - <device-feature-group label="low-latency"> + <device-feature-group label="low-latency" version-code-order="2"> <supports-feature>android.hardware.audio.low_latency</supports-feature> </device-feature-group> </device-feature-groups> @@ -188,19 +191,22 @@ TEST_F(ConfigurationParserTest, ExtractConfiguration) { auto& arm = config.abi_groups["arm"]; auto& other = config.abi_groups["other"]; - EXPECT_EQ(arm.order, 1ul); - EXPECT_EQ(other.order, 2ul); + EXPECT_EQ(arm.order, 1); + EXPECT_EQ(other.order, 2); auto& large = config.screen_density_groups["large"]; auto& alldpi = config.screen_density_groups["alldpi"]; - EXPECT_EQ(large.order, 1ul); - EXPECT_EQ(alldpi.order, 2ul); + EXPECT_EQ(large.order, 2); + EXPECT_EQ(alldpi.order, 1); auto& north_america = config.locale_groups["north-america"]; auto& europe = config.locale_groups["europe"]; + auto& all = config.locale_groups["all"]; // Checked in reverse to make sure access order does not matter. - EXPECT_EQ(north_america.order, 2ul); - EXPECT_EQ(europe.order, 1ul); + EXPECT_EQ(north_america.order, 2); + EXPECT_EQ(europe.order, 1); + EXPECT_EQ(all.order, -1); + EXPECT_EQ(3ul, config.locale_groups.size()); } TEST_F(ConfigurationParserTest, ValidateFile) { @@ -392,7 +398,7 @@ TEST_F(ConfigurationParserTest, ArtifactFormatAction) { TEST_F(ConfigurationParserTest, AbiGroupAction) { static constexpr const char* xml = R"xml( - <abi-group label="arm"> + <abi-group label="arm" version-code-order="2"> <!-- First comment. --> <abi> armeabi-v7a @@ -415,7 +421,8 @@ TEST_F(ConfigurationParserTest, AbiGroupAction) { } TEST_F(ConfigurationParserTest, AbiGroupAction_EmptyGroup) { - static constexpr const char* xml = R"xml(<abi-group label="arm64-v8a"/>)xml"; + static constexpr const char* xml = + R"xml(<abi-group label="arm64-v8a" version-code-order="3"/>)xml"; auto doc = test::BuildXmlDom(xml); @@ -426,12 +433,23 @@ TEST_F(ConfigurationParserTest, AbiGroupAction_EmptyGroup) { EXPECT_THAT(config.abi_groups, SizeIs(1ul)); ASSERT_EQ(1u, config.abi_groups.count("arm64-v8a")); - auto& out = config.abi_groups["arm64-v8a"].entry; - ASSERT_THAT(out, ElementsAre(Abi::kArm64V8a)); + auto& out = config.abi_groups["arm64-v8a"]; + ASSERT_THAT(out.entry, ElementsAre(Abi::kArm64V8a)); + EXPECT_EQ(3, out.order); +} + +TEST_F(ConfigurationParserTest, AbiGroupAction_EmptyGroup_NoOrder) { + static constexpr const char* xml = R"xml(<abi-group label="arm64-v8a"/>)xml"; + + auto doc = test::BuildXmlDom(xml); + + PostProcessingConfiguration config; + bool ok = AbiGroupTagHandler(&config, NodeCast<Element>(doc.get()->root.get()), &diag_); + ASSERT_FALSE(ok); } TEST_F(ConfigurationParserTest, AbiGroupAction_InvalidEmptyGroup) { - static constexpr const char* xml = R"xml(<abi-group label="arm"/>)xml"; + static constexpr const char* xml = R"xml(<abi-group label="arm" order="2"/>)xml"; auto doc = test::BuildXmlDom(xml); @@ -442,7 +460,7 @@ TEST_F(ConfigurationParserTest, AbiGroupAction_InvalidEmptyGroup) { TEST_F(ConfigurationParserTest, ScreenDensityGroupAction) { static constexpr const char* xml = R"xml( - <screen-density-group label="large"> + <screen-density-group label="large" version-code-order="2"> <screen-density>xhdpi</screen-density> <screen-density> xxhdpi @@ -471,7 +489,8 @@ TEST_F(ConfigurationParserTest, ScreenDensityGroupAction) { } TEST_F(ConfigurationParserTest, ScreenDensityGroupAction_EmtpyGroup) { - static constexpr const char* xml = R"xml(<screen-density-group label="xhdpi"/>)xml"; + static constexpr const char* xml = + R"xml(<screen-density-group label="xhdpi" version-code-order="4"/>)xml"; auto doc = test::BuildXmlDom(xml); @@ -485,8 +504,19 @@ TEST_F(ConfigurationParserTest, ScreenDensityGroupAction_EmtpyGroup) { ConfigDescription xhdpi; xhdpi.density = ResTable_config::DENSITY_XHIGH; - auto& out = config.screen_density_groups["xhdpi"].entry; - ASSERT_THAT(out, ElementsAre(xhdpi)); + auto& out = config.screen_density_groups["xhdpi"]; + EXPECT_THAT(out.entry, ElementsAre(xhdpi)); + EXPECT_EQ(4, out.order); +} + +TEST_F(ConfigurationParserTest, ScreenDensityGroupAction_EmtpyGroup_NoVersion) { + static constexpr const char* xml = R"xml(<screen-density-group label="xhdpi"/>)xml"; + + auto doc = test::BuildXmlDom(xml); + + PostProcessingConfiguration config; + bool ok = ScreenDensityGroupTagHandler(&config, NodeCast<Element>(doc.get()->root.get()), &diag_); + ASSERT_FALSE(ok); } TEST_F(ConfigurationParserTest, ScreenDensityGroupAction_InvalidEmtpyGroup) { @@ -501,7 +531,7 @@ TEST_F(ConfigurationParserTest, ScreenDensityGroupAction_InvalidEmtpyGroup) { TEST_F(ConfigurationParserTest, LocaleGroupAction) { static constexpr const char* xml = R"xml( - <locale-group label="europe"> + <locale-group label="europe" version-code-order="2"> <locale>en</locale> <locale>es</locale> <locale>fr</locale> @@ -528,7 +558,7 @@ TEST_F(ConfigurationParserTest, LocaleGroupAction) { } TEST_F(ConfigurationParserTest, LocaleGroupAction_EmtpyGroup) { - static constexpr const char* xml = R"xml(<locale-group label="en"/>)xml"; + static constexpr const char* xml = R"xml(<locale-group label="en" version-code-order="6"/>)xml"; auto doc = test::BuildXmlDom(xml); @@ -539,11 +569,22 @@ TEST_F(ConfigurationParserTest, LocaleGroupAction_EmtpyGroup) { ASSERT_EQ(1ul, config.locale_groups.size()); ASSERT_EQ(1u, config.locale_groups.count("en")); - const auto& out = config.locale_groups["en"].entry; + const auto& out = config.locale_groups["en"]; ConfigDescription en = test::ParseConfigOrDie("en"); - ASSERT_THAT(out, ElementsAre(en)); + EXPECT_THAT(out.entry, ElementsAre(en)); + EXPECT_EQ(6, out.order); +} + +TEST_F(ConfigurationParserTest, LocaleGroupAction_EmtpyGroup_NoOrder) { + static constexpr const char* xml = R"xml(<locale-group label="en"/>)xml"; + + auto doc = test::BuildXmlDom(xml); + + PostProcessingConfiguration config; + bool ok = LocaleGroupTagHandler(&config, NodeCast<Element>(doc.get()->root.get()), &diag_); + ASSERT_FALSE(ok); } TEST_F(ConfigurationParserTest, LocaleGroupAction_InvalidEmtpyGroup) { @@ -695,7 +736,7 @@ TEST_F(ConfigurationParserTest, AndroidSdkGroupAction_NonNumeric) { TEST_F(ConfigurationParserTest, GlTextureGroupAction) { static constexpr const char* xml = R"xml( - <gl-texture-group label="dxt1"> + <gl-texture-group label="dxt1" version-code-order="2"> <gl-texture name="GL_EXT_texture_compression_dxt1"> <texture-path>assets/dxt1/main/*</texture-path> <texture-path> @@ -726,7 +767,7 @@ TEST_F(ConfigurationParserTest, GlTextureGroupAction) { TEST_F(ConfigurationParserTest, DeviceFeatureGroupAction) { static constexpr const char* xml = R"xml( - <device-feature-group label="low-latency"> + <device-feature-group label="low-latency" version-code-order="2"> <supports-feature>android.hardware.audio.low_latency</supports-feature> <supports-feature> android.hardware.audio.pro @@ -749,6 +790,30 @@ TEST_F(ConfigurationParserTest, DeviceFeatureGroupAction) { ASSERT_THAT(out, ElementsAre(low_latency, pro)); } +TEST_F(ConfigurationParserTest, Group_Valid) { + Group<int32_t> group; + group["item1"].order = 1; + group["item2"].order = 2; + group["item3"].order = 3; + group["item4"].order = 4; + group["item5"].order = 5; + group["item6"].order = 6; + + EXPECT_TRUE(IsGroupValid(group, "test", &diag_)); +} + +TEST_F(ConfigurationParserTest, Group_OverlappingOrder) { + Group<int32_t> group; + group["item1"].order = 1; + group["item2"].order = 2; + group["item3"].order = 3; + group["item4"].order = 2; + group["item5"].order = 5; + group["item6"].order = 1; + + EXPECT_FALSE(IsGroupValid(group, "test", &diag_)); +} + // Artifact name parser test cases. TEST(ArtifactTest, Simple) { diff --git a/tools/aapt2/configuration/aapt2.xsd b/tools/aapt2/configuration/aapt2.xsd index fb2f49bae7b7..a28e28be48f4 100644 --- a/tools/aapt2/configuration/aapt2.xsd +++ b/tools/aapt2/configuration/aapt2.xsd @@ -81,6 +81,7 @@ <xsd:element name="gl-texture" type="gl-texture" maxOccurs="unbounded"/> </xsd:sequence> <xsd:attribute name="label" type="xsd:string"/> + <xsd:attribute name="version-code-order" type="xsd:unsignedInt" use="required"/> </xsd:complexType> <xsd:complexType name="gl-texture"> @@ -95,6 +96,7 @@ <xsd:element name="supports-feature" type="xsd:string" maxOccurs="unbounded"/> </xsd:sequence> <xsd:attribute name="label" type="xsd:string"/> + <xsd:attribute name="version-code-order" type="xsd:unsignedInt" use="required"/> </xsd:complexType> <xsd:complexType name="abi-group"> @@ -102,6 +104,7 @@ <xsd:element name="abi" type="abi-name" maxOccurs="unbounded"/> </xsd:sequence> <xsd:attribute name="label" type="xsd:string"/> + <xsd:attribute name="version-code-order" type="xsd:unsignedInt" use="required"/> </xsd:complexType> <xsd:simpleType name="abi-name"> @@ -122,6 +125,7 @@ <xsd:element name="screen-density" type="screen-density" maxOccurs="unbounded"/> </xsd:sequence> <xsd:attribute name="label" type="xsd:string"/> + <xsd:attribute name="version-code-order" type="xsd:unsignedInt" use="required"/> </xsd:complexType> <xsd:simpleType name="screen-density"> @@ -158,6 +162,7 @@ <xsd:element name="locale" type="locale" maxOccurs="unbounded"/> </xsd:sequence> <xsd:attribute name="label" type="xsd:string"/> + <xsd:attribute name="version-code-order" type="xsd:unsignedInt" use="required"/> </xsd:complexType> <xsd:complexType name="locale"> diff --git a/tools/aapt2/configuration/example/config.xml b/tools/aapt2/configuration/example/config.xml index d8aba09e836d..e6db2a0f461b 100644 --- a/tools/aapt2/configuration/example/config.xml +++ b/tools/aapt2/configuration/example/config.xml @@ -36,28 +36,28 @@ </android-sdks> <abi-groups> - <abi-group label="arm"> + <abi-group label="arm" version-code-order="1"> <abi>armeabi-v7a</abi> <abi>arm64-v8a</abi> </abi-group> - <abi-group label="other"> + <abi-group label="other" version-code-order="2"> <abi>x86</abi> <abi>mips</abi> </abi-group> </abi-groups> <screen-density-groups> - <screen-density-group label="large"> + <screen-density-group label="alldpi" version-code-order="1"> + <screen-density>ldpi</screen-density> + <screen-density>mdpi</screen-density> + <screen-density>hdpi</screen-density> <screen-density>xhdpi</screen-density> <screen-density>xxhdpi</screen-density> <screen-density>xxxhdpi</screen-density> </screen-density-group> - <screen-density-group label="alldpi"> - <screen-density>ldpi</screen-density> - <screen-density>mdpi</screen-density> - <screen-density>hdpi</screen-density> + <screen-density-group label="large" version-code-order="2"> <screen-density>xhdpi</screen-density> <screen-density>xxhdpi</screen-density> <screen-density>xxxhdpi</screen-density> @@ -65,26 +65,26 @@ </screen-density-groups> <locale-groups> - <locale-group label="europe"> + <locale-group label="europe" version-code-order="1"> <locale lang="en"/> <locale lang="es"/> <locale lang="fr"/> <locale lang="de" compressed="true"/> </locale-group> - <locale-group label="north-america"> + <locale-group label="north-america" version-code-order="2"> <locale lang="en"/> <locale lang="es" region="MX"/> <locale lang="fr" region="CA" compressed="true"/> </locale-group> - <locale-group label="all"> + <locale-group label="all" version-code-order="0"> <locale compressed="true"/> </locale-group> </locale-groups> <gl-texture-groups> - <gl-texture-group label="dxt1"> + <gl-texture-group label="dxt1" version-code-order="1"> <gl-texture name="GL_EXT_texture_compression_dxt1"> <texture-path>assets/dxt1/*</texture-path> </gl-texture> @@ -92,7 +92,7 @@ </gl-texture-groups> <device-feature-groups> - <device-feature-group label="low-latency"> + <device-feature-group label="low-latency" version-code-order="1"> <supports-feature>android.hardware.audio.low_latency</supports-feature> </device-feature-group> </device-feature-groups> diff --git a/tools/aapt2/optimize/MultiApkGenerator.cpp b/tools/aapt2/optimize/MultiApkGenerator.cpp index 991faadcafc4..588b3316e6fa 100644 --- a/tools/aapt2/optimize/MultiApkGenerator.cpp +++ b/tools/aapt2/optimize/MultiApkGenerator.cpp @@ -322,22 +322,56 @@ bool MultiApkGenerator::UpdateManifest(const OutputArtifact& artifact, std::unique_ptr<xml::Element> new_screens_el = util::make_unique<xml::Element>(); new_screens_el->name = "compatible-screens"; screens_el = new_screens_el.get(); - manifest_el->InsertChild(0, std::move(new_screens_el)); + manifest_el->AppendChild(std::move(new_screens_el)); } else { // clear out the old element. screens_el->GetChildElements().clear(); } for (const auto& density : artifact.screen_densities) { - std::unique_ptr<xml::Element> screen_el = util::make_unique<xml::Element>(); - screen_el->name = "screen"; - const char* density_str = density.toString().string(); - screen_el->attributes.push_back(xml::Attribute{kSchemaAndroid, "screenDensity", density_str}); - screens_el->AppendChild(std::move(screen_el)); + AddScreens(density, screens_el); } } return true; } +/** + * Adds a screen element with both screenSize and screenDensity set. Since we only know the density + * we add it for all screen sizes. + * + * This requires the resource IDs for the attributes from the framework library. Since these IDs are + * a part of the public API (and in public.xml) we hard code the values. + * + * The excert from the framework is as follows: + * <public type="attr" name="screenSize" id="0x010102ca" /> + * <public type="attr" name="screenDensity" id="0x010102cb" /> + */ +void MultiApkGenerator::AddScreens(const ConfigDescription& config, xml::Element* parent) { + // Hard coded integer representation of the supported screen sizes: + // small = 200 + // normal = 300 + // large = 400 + // xlarge = 500 + constexpr const uint32_t kScreenSizes[4] = {200, 300, 400, 500,}; + constexpr const uint32_t kScreenSizeResourceId = 0x010102ca; + constexpr const uint32_t kScreenDensityResourceId = 0x010102cb; + + for (uint32_t screen_size : kScreenSizes) { + std::unique_ptr<xml::Element> screen = util::make_unique<xml::Element>(); + screen->name = "screen"; + + xml::Attribute* size = screen->FindOrCreateAttribute(kSchemaAndroid, "screenSize"); + size->compiled_attribute = xml::AaptAttribute(Attribute(), {kScreenSizeResourceId}); + size->compiled_value = ResourceUtils::MakeInt(screen_size); + + xml::Attribute* density = screen->FindOrCreateAttribute(kSchemaAndroid, "screenDensity"); + density->compiled_attribute = xml::AaptAttribute(Attribute(), {kScreenDensityResourceId}); + density->compiled_value = ResourceUtils::MakeInt(config.density); + + + parent->AppendChild(std::move(screen)); + } +} + } // namespace aapt diff --git a/tools/aapt2/optimize/MultiApkGenerator.h b/tools/aapt2/optimize/MultiApkGenerator.h index 19f64cc0e588..c8588791662a 100644 --- a/tools/aapt2/optimize/MultiApkGenerator.h +++ b/tools/aapt2/optimize/MultiApkGenerator.h @@ -63,6 +63,11 @@ class MultiApkGenerator { bool UpdateManifest(const configuration::OutputArtifact& artifact, std::unique_ptr<xml::XmlResource>* updated_manifest, IDiagnostics* diag); + /** + * Adds the <screen> elements to the parent node for the provided density configuration. + */ + void AddScreens(const ConfigDescription& config, xml::Element* parent); + LoadedApk* apk_; IAaptContext* context_; }; |