diff options
547 files changed, 22627 insertions, 8787 deletions
diff --git a/Android.bp b/Android.bp index 28e51e733895..129d6769cb98 100644 --- a/Android.bp +++ b/Android.bp @@ -319,6 +319,10 @@ java_library { "core/java/android/service/chooser/IChooserTargetResult.aidl", "core/java/android/service/resolver/IResolverRankerService.aidl", "core/java/android/service/resolver/IResolverRankerResult.aidl", + "core/java/android/service/textclassifier/ITextClassificationCallback.aidl", + "core/java/android/service/textclassifier/ITextClassifierService.aidl", + "core/java/android/service/textclassifier/ITextLinksCallback.aidl", + "core/java/android/service/textclassifier/ITextSelectionCallback.aidl", "core/java/android/view/accessibility/IAccessibilityInteractionConnection.aidl", "core/java/android/view/accessibility/IAccessibilityInteractionConnectionCallback.aidl", "core/java/android/view/accessibility/IAccessibilityManager.aidl", @@ -429,8 +433,6 @@ java_library { "media/java/android/media/IMediaRouterService.aidl", "media/java/android/media/IMediaScannerListener.aidl", "media/java/android/media/IMediaScannerService.aidl", - "media/java/android/media/IMediaSession2.aidl", - "media/java/android/media/IMediaSession2Callback.aidl", "media/java/android/media/IPlaybackConfigDispatcher.aidl", "media/java/android/media/ISessionTokensListener.aidl", ":libaudioclient_aidl", @@ -485,16 +487,19 @@ java_library { "telecomm/java/com/android/internal/telecom/RemoteServiceCallback.aidl", "telephony/java/android/telephony/data/IDataService.aidl", "telephony/java/android/telephony/data/IDataServiceCallback.aidl", - "telephony/java/android/telephony/ims/internal/aidl/IImsCallSessionListener.aidl", - "telephony/java/android/telephony/ims/internal/aidl/IImsCapabilityCallback.aidl", - "telephony/java/android/telephony/ims/internal/aidl/IImsConfig.aidl", - "telephony/java/android/telephony/ims/internal/aidl/IImsConfigCallback.aidl", - "telephony/java/android/telephony/ims/internal/aidl/IImsMmTelFeature.aidl", - "telephony/java/android/telephony/ims/internal/aidl/IImsMmTelListener.aidl", - "telephony/java/android/telephony/ims/internal/aidl/IImsRcsFeature.aidl", - "telephony/java/android/telephony/ims/internal/aidl/IImsServiceController.aidl", - "telephony/java/android/telephony/ims/internal/aidl/IImsServiceControllerListener.aidl", - "telephony/java/android/telephony/mbms/IMbmsDownloadSessionCallback.aidl", + "telephony/java/android/telephony/ims/aidl/IImsCallSessionListener.aidl", + "telephony/java/android/telephony/ims/aidl/IImsCapabilityCallback.aidl", + "telephony/java/android/telephony/ims/aidl/IImsConfig.aidl", + "telephony/java/android/telephony/ims/aidl/IImsConfigCallback.aidl", + "telephony/java/android/telephony/ims/aidl/IImsMmTelFeature.aidl", + "telephony/java/android/telephony/ims/aidl/IImsMmTelListener.aidl", + "telephony/java/android/telephony/ims/aidl/IImsRegistration.aidl", + "telephony/java/android/telephony/ims/aidl/IImsRegistrationCallback.aidl", + "telephony/java/android/telephony/ims/aidl/IImsRcsFeature.aidl", + "telephony/java/android/telephony/ims/aidl/IImsServiceController.aidl", + "telephony/java/android/telephony/ims/aidl/IImsServiceControllerListener.aidl", + "telephony/java/android/telephony/ims/aidl/IImsSmsListener.aidl", + "telephony/java/android/telephony/mbms/IMbmsDownloadSessionCallback.aidl", "telephony/java/android/telephony/mbms/IMbmsStreamingSessionCallback.aidl", "telephony/java/android/telephony/mbms/IDownloadStateCallback.aidl", "telephony/java/android/telephony/mbms/IStreamingServiceCallback.aidl", @@ -512,13 +517,10 @@ java_library { "telephony/java/com/android/ims/internal/IImsFeatureStatusCallback.aidl", "telephony/java/com/android/ims/internal/IImsMMTelFeature.aidl", "telephony/java/com/android/ims/internal/IImsMultiEndpoint.aidl", - "telephony/java/com/android/ims/internal/IImsRegistration.aidl", - "telephony/java/com/android/ims/internal/IImsRegistrationCallback.aidl", "telephony/java/com/android/ims/internal/IImsRcsFeature.aidl", "telephony/java/com/android/ims/internal/IImsService.aidl", "telephony/java/com/android/ims/internal/IImsServiceController.aidl", "telephony/java/com/android/ims/internal/IImsServiceFeatureCallback.aidl", - "telephony/java/com/android/ims/internal/IImsSmsListener.aidl", "telephony/java/com/android/ims/internal/IImsStreamMediaSession.aidl", "telephony/java/com/android/ims/internal/IImsUt.aidl", "telephony/java/com/android/ims/internal/IImsUtListener.aidl", @@ -645,8 +647,10 @@ java_library { ], }, - // See comment on framework-oahl-backward-compatibility module below exclude_srcs: [ + // See comment on framework-atb-backward-compatibility module below + "core/java/android/content/pm/AndroidTestBaseUpdater.java", + // See comment on framework-oahl-backward-compatibility module below "core/java/android/content/pm/OrgApacheHttpLegacyUpdater.java", ], @@ -699,6 +703,18 @@ java_library { ], } +// A temporary build target that is conditionally included on the bootclasspath if +// android.test.base library has been removed and which provides support for +// maintaining backwards compatibility for APKs that target pre-P and depend on +// android.test.base classes. This is used iff REMOVE_ATB_FROM_BCP=true is +// specified on the build command line. +java_library { + name: "framework-atb-backward-compatibility", + srcs: [ + "core/java/android/content/pm/AndroidTestBaseUpdater.java", + ], +} + genrule { name: "framework-statslog-gen", tools: ["stats-log-api-gen"], diff --git a/apct-tests/perftests/core/src/android/text/MeasuredTextMemoryUsageTest.java b/apct-tests/perftests/core/src/android/text/MeasuredTextMemoryUsageTest.java new file mode 100644 index 000000000000..fc6302ea9394 --- /dev/null +++ b/apct-tests/perftests/core/src/android/text/MeasuredTextMemoryUsageTest.java @@ -0,0 +1,132 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * 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 static android.text.TextDirectionHeuristics.LTR; + +import android.perftests.utils.BenchmarkState; +import android.perftests.utils.PerfStatusReporter; + +import android.support.test.filters.LargeTest; +import android.support.test.runner.AndroidJUnit4; + +import android.app.Activity; +import android.os.Bundle; +import android.support.test.InstrumentationRegistry; +import android.content.res.ColorStateList; +import android.graphics.Canvas; +import android.graphics.Typeface; +import android.text.Layout; +import android.text.style.TextAppearanceSpan; +import android.view.DisplayListCanvas; +import android.view.RenderNode; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.nio.CharBuffer; +import java.util.Random; + +@LargeTest +@RunWith(AndroidJUnit4.class) +public class MeasuredTextMemoryUsageTest { + private static final int WORD_LENGTH = 9; // Random word has 9 characters. + private static final boolean NO_STYLE_TEXT = false; + + private static TextPaint PAINT = new TextPaint(); + + private static int TRIAL_COUNT = 100; + + public MeasuredTextMemoryUsageTest() {} + + private TextPerfUtils mTextUtil = new TextPerfUtils(); + + @Before + public void setUp() { + mTextUtil.resetRandom(0 /* seed */); + } + + private void reportMemoryUsage(int memoryUsage, String key) { + Bundle status = new Bundle(); + status.putInt(key + "_median", memoryUsage); + InstrumentationRegistry.getInstrumentation().sendStatus(Activity.RESULT_OK, status); + } + + private int median(int[] values) { + return values.length % 2 == 0 ? + (values[values.length / 2] + values[values.length / 2 - 1]) / 2: + values[values.length / 2]; + } + + @Test + public void testMemoryUsage_NoHyphenation() { + int[] memories = new int[TRIAL_COUNT]; + // 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().getMemoryUsage(); + } + reportMemoryUsage(median(memories), "MemoryUsage_NoHyphenation"); + } + + @Test + public void testMemoryUsage_Hyphenation() { + int[] memories = new int[TRIAL_COUNT]; + // 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().getMemoryUsage(); + } + reportMemoryUsage(median(memories), "MemoryUsage_Hyphenation"); + } + + @Test + public void testMemoryUsage_NoHyphenation_WidthOnly() { + int[] memories = new int[TRIAL_COUNT]; + // 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(false /* width only */).getMemoryUsage(); + } + reportMemoryUsage(median(memories), "MemoryUsage_NoHyphenation_WidthOnly"); + } + + @Test + public void testMemoryUsage_Hyphenatation_WidthOnly() { + int[] memories = new int[TRIAL_COUNT]; + // 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(false /* width only */).getMemoryUsage(); + } + reportMemoryUsage(median(memories), "MemoryUsage_Hyphenation_WidthOnly"); + } +} diff --git a/apct-tests/perftests/core/src/android/text/MeasuredTextPerfTest.java b/apct-tests/perftests/core/src/android/text/MeasuredTextPerfTest.java new file mode 100644 index 000000000000..98f2bd5e5736 --- /dev/null +++ b/apct-tests/perftests/core/src/android/text/MeasuredTextPerfTest.java @@ -0,0 +1,185 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * 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 static android.text.TextDirectionHeuristics.LTR; + +import android.perftests.utils.BenchmarkState; +import android.perftests.utils.PerfStatusReporter; + +import android.support.test.filters.LargeTest; +import android.support.test.runner.AndroidJUnit4; + +import android.content.res.ColorStateList; +import android.graphics.Canvas; +import android.graphics.Typeface; +import android.text.Layout; +import android.text.style.TextAppearanceSpan; +import android.view.DisplayListCanvas; +import android.view.RenderNode; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.nio.CharBuffer; +import java.util.Random; + +@LargeTest +@RunWith(AndroidJUnit4.class) +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; + private static final boolean STYLE_TEXT = true; + + private static TextPaint PAINT = new TextPaint(); + private static final int TEXT_WIDTH = WORDS_IN_LINE * WORD_LENGTH * (int) PAINT.getTextSize(); + + public MeasuredTextPerfTest() {} + + @Rule + public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter(); + + private TextPerfUtils mTextUtil = new TextPerfUtils(); + + @Before + public void setUp() { + mTextUtil.resetRandom(0 /* seed */); + } + + @Test + public void testCreate_NoStyled_Hyphenation() { + final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + while (state.keepRunning()) { + state.pauseTiming(); + final CharSequence text = mTextUtil.nextRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT); + state.resumeTiming(); + + 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(); + while (state.keepRunning()) { + state.pauseTiming(); + final CharSequence text = mTextUtil.nextRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT); + state.resumeTiming(); + + 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(); + while (state.keepRunning()) { + state.pauseTiming(); + final CharSequence text = mTextUtil.nextRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT); + state.resumeTiming(); + + 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(); + while (state.keepRunning()) { + state.pauseTiming(); + final CharSequence text = mTextUtil.nextRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT); + state.resumeTiming(); + + 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(); + while (state.keepRunning()) { + state.pauseTiming(); + final CharSequence text = mTextUtil.nextRandomParagraph(WORD_LENGTH, STYLE_TEXT); + state.resumeTiming(); + + 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(); + while (state.keepRunning()) { + state.pauseTiming(); + final CharSequence text = mTextUtil.nextRandomParagraph(WORD_LENGTH, STYLE_TEXT); + state.resumeTiming(); + + 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(); + while (state.keepRunning()) { + state.pauseTiming(); + final CharSequence text = mTextUtil.nextRandomParagraph(WORD_LENGTH, STYLE_TEXT); + state.resumeTiming(); + + 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(); + while (state.keepRunning()) { + state.pauseTiming(); + final CharSequence text = mTextUtil.nextRandomParagraph(WORD_LENGTH, STYLE_TEXT); + state.resumeTiming(); + + 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 682885b3120d..bab2a859698c 100644 --- a/apct-tests/perftests/core/src/android/text/StaticLayoutPerfTest.java +++ b/apct-tests/perftests/core/src/android/text/StaticLayoutPerfTest.java @@ -43,74 +43,30 @@ import java.util.Random; @LargeTest @RunWith(AndroidJUnit4.class) public class StaticLayoutPerfTest { - - public StaticLayoutPerfTest() {} - - @Rule - public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter(); - 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 int PARA_LENGTH = 500; // Number of characters in a paragraph. - private static final boolean NO_STYLE_TEXT = false; private static final boolean STYLE_TEXT = true; - private Random mRandom; - - private static final String ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; - private static final int ALPHABET_LENGTH = ALPHABET.length(); - - private static final ColorStateList TEXT_COLOR = ColorStateList.valueOf(0x00000000); - private static final String[] FAMILIES = { "sans-serif", "serif", "monospace" }; - private static final int[] STYLES = { - Typeface.NORMAL, Typeface.BOLD, Typeface.ITALIC, Typeface.BOLD_ITALIC - }; - - private final char[] mBuffer = new char[PARA_LENGTH]; - private static TextPaint PAINT = new TextPaint(); private static final int TEXT_WIDTH = WORDS_IN_LINE * WORD_LENGTH * (int) PAINT.getTextSize(); - private CharSequence generateRandomParagraph(int wordLen, boolean applyRandomStyle) { - for (int i = 0; i < PARA_LENGTH; i++) { - if (i % (wordLen + 1) == wordLen) { - mBuffer[i] = ' '; - } else { - mBuffer[i] = ALPHABET.charAt(mRandom.nextInt(ALPHABET_LENGTH)); - } - } - - CharSequence cs = CharBuffer.wrap(mBuffer); - if (!applyRandomStyle) { - return cs; - } - - SpannableStringBuilder ssb = new SpannableStringBuilder(cs); - for (int i = 0; i < ssb.length(); i += WORD_LENGTH) { - final int spanStart = i; - final int spanEnd = (i + WORD_LENGTH) > ssb.length() ? ssb.length() : i + WORD_LENGTH; + public StaticLayoutPerfTest() {} - final TextAppearanceSpan span = new TextAppearanceSpan( - FAMILIES[mRandom.nextInt(FAMILIES.length)], - STYLES[mRandom.nextInt(STYLES.length)], - 24 + mRandom.nextInt(32), // text size. min 24 max 56 - TEXT_COLOR, TEXT_COLOR); + @Rule + public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter(); - ssb.setSpan(span, spanStart, spanEnd, Spanned.SPAN_INCLUSIVE_EXCLUSIVE); - } - return ssb; - } + private TextPerfUtils mTextUtil = new TextPerfUtils(); @Before public void setUp() { - mRandom = new Random(0); + mTextUtil.resetRandom(0 /* seed */); } @Test public void testCreate_FixedText_NoStyle_Greedy_NoHyphenation() { final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); - final CharSequence text = generateRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT); + final CharSequence text = mTextUtil.nextRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT); while (state.keepRunning()) { StaticLayout.Builder.obtain(text, 0, text.length(), PAINT, TEXT_WIDTH) .setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NONE) @@ -124,7 +80,7 @@ public class StaticLayoutPerfTest { final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); while (state.keepRunning()) { state.pauseTiming(); - final CharSequence text = generateRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT); + final CharSequence text = mTextUtil.nextRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT); state.resumeTiming(); StaticLayout.Builder.obtain(text, 0, text.length(), PAINT, TEXT_WIDTH) @@ -139,7 +95,7 @@ public class StaticLayoutPerfTest { final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); while (state.keepRunning()) { state.pauseTiming(); - final CharSequence text = generateRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT); + final CharSequence text = mTextUtil.nextRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT); state.resumeTiming(); StaticLayout.Builder.obtain(text, 0, text.length(), PAINT, TEXT_WIDTH) @@ -154,7 +110,7 @@ public class StaticLayoutPerfTest { final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); while (state.keepRunning()) { state.pauseTiming(); - final CharSequence text = generateRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT); + final CharSequence text = mTextUtil.nextRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT); state.resumeTiming(); StaticLayout.Builder.obtain(text, 0, text.length(), PAINT, TEXT_WIDTH) @@ -169,7 +125,7 @@ public class StaticLayoutPerfTest { final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); while (state.keepRunning()) { state.pauseTiming(); - final CharSequence text = generateRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT); + final CharSequence text = mTextUtil.nextRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT); state.resumeTiming(); StaticLayout.Builder.obtain(text, 0, text.length(), PAINT, TEXT_WIDTH) @@ -184,7 +140,7 @@ public class StaticLayoutPerfTest { final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); while (state.keepRunning()) { state.pauseTiming(); - final CharSequence text = generateRandomParagraph(WORD_LENGTH, STYLE_TEXT); + final CharSequence text = mTextUtil.nextRandomParagraph(WORD_LENGTH, STYLE_TEXT); state.resumeTiming(); StaticLayout.Builder.obtain(text, 0, text.length(), PAINT, TEXT_WIDTH) @@ -200,7 +156,7 @@ public class StaticLayoutPerfTest { while (state.keepRunning()) { state.pauseTiming(); final MeasuredText text = new MeasuredText.Builder( - generateRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT), PAINT) + mTextUtil.nextRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT), PAINT) .setBreakStrategy(Layout.BREAK_STRATEGY_SIMPLE) .setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NONE) .build(); @@ -219,7 +175,7 @@ public class StaticLayoutPerfTest { while (state.keepRunning()) { state.pauseTiming(); final MeasuredText text = new MeasuredText.Builder( - generateRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT), PAINT) + mTextUtil.nextRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT), PAINT) .setBreakStrategy(Layout.BREAK_STRATEGY_SIMPLE) .setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NORMAL) .build(); @@ -238,7 +194,7 @@ public class StaticLayoutPerfTest { while (state.keepRunning()) { state.pauseTiming(); final MeasuredText text = new MeasuredText.Builder( - generateRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT), PAINT) + mTextUtil.nextRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT), PAINT) .setBreakStrategy(Layout.BREAK_STRATEGY_BALANCED) .setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NONE) .build(); @@ -257,7 +213,7 @@ public class StaticLayoutPerfTest { while (state.keepRunning()) { state.pauseTiming(); final MeasuredText text = new MeasuredText.Builder( - generateRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT), PAINT) + mTextUtil.nextRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT), PAINT) .setBreakStrategy(Layout.BREAK_STRATEGY_BALANCED) .setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NORMAL) .build(); @@ -276,7 +232,7 @@ public class StaticLayoutPerfTest { while (state.keepRunning()) { state.pauseTiming(); final MeasuredText text = new MeasuredText.Builder( - generateRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT), PAINT) + mTextUtil.nextRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT), PAINT) .setBreakStrategy(Layout.BREAK_STRATEGY_SIMPLE) .setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NONE) .build(); @@ -292,7 +248,7 @@ public class StaticLayoutPerfTest { @Test public void testDraw_FixedText_NoStyled() { final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); - final CharSequence text = generateRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT); + final CharSequence text = mTextUtil.nextRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT); final RenderNode node = RenderNode.create("benchmark", null); while (state.keepRunning()) { state.pauseTiming(); @@ -311,7 +267,7 @@ public class StaticLayoutPerfTest { final RenderNode node = RenderNode.create("benchmark", null); while (state.keepRunning()) { state.pauseTiming(); - final CharSequence text = generateRandomParagraph(WORD_LENGTH, STYLE_TEXT); + final CharSequence text = mTextUtil.nextRandomParagraph(WORD_LENGTH, STYLE_TEXT); final StaticLayout layout = StaticLayout.Builder.obtain(text, 0, text.length(), PAINT, TEXT_WIDTH).build(); final DisplayListCanvas c = node.start(1200, 200); @@ -327,7 +283,7 @@ public class StaticLayoutPerfTest { final RenderNode node = RenderNode.create("benchmark", null); while (state.keepRunning()) { state.pauseTiming(); - final CharSequence text = generateRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT); + final CharSequence text = mTextUtil.nextRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT); final StaticLayout layout = StaticLayout.Builder.obtain(text, 0, text.length(), PAINT, TEXT_WIDTH).build(); final DisplayListCanvas c = node.start(1200, 200); @@ -343,7 +299,7 @@ public class StaticLayoutPerfTest { final RenderNode node = RenderNode.create("benchmark", null); while (state.keepRunning()) { state.pauseTiming(); - final CharSequence text = generateRandomParagraph(WORD_LENGTH, STYLE_TEXT); + final CharSequence text = mTextUtil.nextRandomParagraph(WORD_LENGTH, STYLE_TEXT); final StaticLayout layout = StaticLayout.Builder.obtain(text, 0, text.length(), PAINT, TEXT_WIDTH).build(); final DisplayListCanvas c = node.start(1200, 200); @@ -360,7 +316,7 @@ public class StaticLayoutPerfTest { final RenderNode node = RenderNode.create("benchmark", null); while (state.keepRunning()) { state.pauseTiming(); - final CharSequence text = generateRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT); + final CharSequence text = mTextUtil.nextRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT); final StaticLayout layout = StaticLayout.Builder.obtain(text, 0, text.length(), PAINT, TEXT_WIDTH).build(); final DisplayListCanvas c = node.start(1200, 200); @@ -378,7 +334,7 @@ public class StaticLayoutPerfTest { while (state.keepRunning()) { state.pauseTiming(); final MeasuredText text = new MeasuredText.Builder( - generateRandomParagraph(WORD_LENGTH, STYLE_TEXT), PAINT).build(); + mTextUtil.nextRandomParagraph(WORD_LENGTH, STYLE_TEXT), PAINT).build(); final StaticLayout layout = StaticLayout.Builder.obtain(text, 0, text.length(), PAINT, TEXT_WIDTH).build(); final DisplayListCanvas c = node.start(1200, 200); @@ -395,7 +351,7 @@ public class StaticLayoutPerfTest { while (state.keepRunning()) { state.pauseTiming(); final MeasuredText text = new MeasuredText.Builder( - generateRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT), PAINT).build(); + mTextUtil.nextRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT), PAINT).build(); final StaticLayout layout = StaticLayout.Builder.obtain(text, 0, text.length(), PAINT, TEXT_WIDTH).build(); final DisplayListCanvas c = node.start(1200, 200); @@ -412,7 +368,7 @@ public class StaticLayoutPerfTest { while (state.keepRunning()) { state.pauseTiming(); final MeasuredText text = new MeasuredText.Builder( - generateRandomParagraph(WORD_LENGTH, STYLE_TEXT), PAINT).build(); + mTextUtil.nextRandomParagraph(WORD_LENGTH, STYLE_TEXT), PAINT).build(); final StaticLayout layout = StaticLayout.Builder.obtain(text, 0, text.length(), PAINT, TEXT_WIDTH).build(); final DisplayListCanvas c = node.start(1200, 200); @@ -430,7 +386,7 @@ public class StaticLayoutPerfTest { while (state.keepRunning()) { state.pauseTiming(); final MeasuredText text = new MeasuredText.Builder( - generateRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT), PAINT).build(); + mTextUtil.nextRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT), PAINT).build(); final StaticLayout layout = StaticLayout.Builder.obtain(text, 0, text.length(), PAINT, TEXT_WIDTH).build(); final DisplayListCanvas c = node.start(1200, 200); diff --git a/apct-tests/perftests/core/src/android/text/TextPerfUtils.java b/apct-tests/perftests/core/src/android/text/TextPerfUtils.java new file mode 100644 index 000000000000..dccb34be9d07 --- /dev/null +++ b/apct-tests/perftests/core/src/android/text/TextPerfUtils.java @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * 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 static android.text.TextDirectionHeuristics.LTR; + +import android.perftests.utils.BenchmarkState; +import android.perftests.utils.PerfStatusReporter; + +import android.support.test.filters.LargeTest; +import android.support.test.runner.AndroidJUnit4; + +import android.content.res.ColorStateList; +import android.graphics.Canvas; +import android.graphics.Typeface; +import android.text.Layout; +import android.text.style.TextAppearanceSpan; +import android.view.DisplayListCanvas; +import android.view.RenderNode; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.nio.CharBuffer; +import java.util.Random; + +public class TextPerfUtils { + + private static final int PARA_LENGTH = 500; // Number of characters in a paragraph. + + private Random mRandom = new Random(0); + + private static final String ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; + private static final int ALPHABET_LENGTH = ALPHABET.length(); + + private static final ColorStateList TEXT_COLOR = ColorStateList.valueOf(0x00000000); + private static final String[] FAMILIES = { "sans-serif", "serif", "monospace" }; + private static final int[] STYLES = { + Typeface.NORMAL, Typeface.BOLD, Typeface.ITALIC, Typeface.BOLD_ITALIC + }; + + private final char[] mBuffer = new char[PARA_LENGTH]; + + public void resetRandom(long seed) { + mRandom = new Random(seed); + } + + public CharSequence nextRandomParagraph(int wordLen, boolean applyRandomStyle) { + for (int i = 0; i < PARA_LENGTH; i++) { + if (i % (wordLen + 1) == wordLen) { + mBuffer[i] = ' '; + } else { + mBuffer[i] = ALPHABET.charAt(mRandom.nextInt(ALPHABET_LENGTH)); + } + } + + CharSequence cs = CharBuffer.wrap(mBuffer); + if (!applyRandomStyle) { + return cs; + } + + SpannableStringBuilder ssb = new SpannableStringBuilder(cs); + for (int i = 0; i < ssb.length(); i += wordLen) { + final int spanStart = i; + final int spanEnd = (i + wordLen) > ssb.length() ? ssb.length() : i + wordLen; + + final TextAppearanceSpan span = new TextAppearanceSpan( + FAMILIES[mRandom.nextInt(FAMILIES.length)], + STYLES[mRandom.nextInt(STYLES.length)], + 24 + mRandom.nextInt(32), // text size. min 24 max 56 + TEXT_COLOR, TEXT_COLOR); + + ssb.setSpan(span, spanStart, spanEnd, Spanned.SPAN_INCLUSIVE_EXCLUSIVE); + } + return ssb; + } +} diff --git a/api/current.txt b/api/current.txt index c36fe0c852d2..44f2f0836049 100644 --- a/api/current.txt +++ b/api/current.txt @@ -94,6 +94,7 @@ package android { field public static final java.lang.String MOUNT_FORMAT_FILESYSTEMS = "android.permission.MOUNT_FORMAT_FILESYSTEMS"; field public static final java.lang.String MOUNT_UNMOUNT_FILESYSTEMS = "android.permission.MOUNT_UNMOUNT_FILESYSTEMS"; field public static final java.lang.String NFC = "android.permission.NFC"; + field public static final java.lang.String NFC_TRANSACTION_EVENT = "android.permission.NFC_TRANSACTION_EVENT"; field public static final java.lang.String PACKAGE_USAGE_STATS = "android.permission.PACKAGE_USAGE_STATS"; field public static final deprecated java.lang.String PERSISTENT_ACTIVITY = "android.permission.PERSISTENT_ACTIVITY"; field public static final java.lang.String PROCESS_OUTGOING_CALLS = "android.permission.PROCESS_OUTGOING_CALLS"; @@ -6390,7 +6391,7 @@ package android.app.admin { method public void addPersistentPreferredActivity(android.content.ComponentName, android.content.IntentFilter, android.content.ComponentName); method public void addUserRestriction(android.content.ComponentName, java.lang.String); method public boolean bindDeviceAdminServiceAsUser(android.content.ComponentName, android.content.Intent, android.content.ServiceConnection, int, android.os.UserHandle); - method public boolean clearApplicationUserData(android.content.ComponentName, java.lang.String, java.util.concurrent.Executor, android.app.admin.DevicePolicyManager.OnClearApplicationUserDataListener); + method public void clearApplicationUserData(android.content.ComponentName, java.lang.String, java.util.concurrent.Executor, android.app.admin.DevicePolicyManager.OnClearApplicationUserDataListener); method public void clearCrossProfileIntentFilters(android.content.ComponentName); method public deprecated void clearDeviceOwnerApp(java.lang.String); method public void clearPackagePersistentPreferredActivities(android.content.ComponentName, java.lang.String); @@ -6497,7 +6498,7 @@ package android.app.admin { method public boolean isUsingUnifiedPassword(android.content.ComponentName); method public void lockNow(); method public void lockNow(int); - method public boolean logoutUser(android.content.ComponentName); + method public int logoutUser(android.content.ComponentName); method public void reboot(android.content.ComponentName); method public void removeActiveAdmin(android.content.ComponentName); method public boolean removeCrossProfileWidgetProvider(android.content.ComponentName, java.lang.String); @@ -6582,8 +6583,8 @@ package android.app.admin { method public void setTrustAgentConfiguration(android.content.ComponentName, android.content.ComponentName, android.os.PersistableBundle); method public void setUninstallBlocked(android.content.ComponentName, java.lang.String, boolean); method public void setUserIcon(android.content.ComponentName, android.graphics.Bitmap); - method public boolean startUserInBackground(android.content.ComponentName, android.os.UserHandle); - method public boolean stopUser(android.content.ComponentName, android.os.UserHandle); + method public int startUserInBackground(android.content.ComponentName, android.os.UserHandle); + method public int stopUser(android.content.ComponentName, android.os.UserHandle); method public boolean switchUser(android.content.ComponentName, android.os.UserHandle); method public void transferOwnership(android.content.ComponentName, android.content.ComponentName, android.os.PersistableBundle); method public void uninstallAllUserCaCerts(android.content.ComponentName); @@ -6697,6 +6698,11 @@ package android.app.admin { field public static final int RESET_PASSWORD_DO_NOT_ASK_CREDENTIALS_ON_BOOT = 2; // 0x2 field public static final int RESET_PASSWORD_REQUIRE_ENTRY = 1; // 0x1 field public static final int SKIP_SETUP_WIZARD = 1; // 0x1 + field public static final int USER_OPERATION_ERROR_CURRENT_USER = 4; // 0x4 + field public static final int USER_OPERATION_ERROR_MANAGED_PROFILE = 2; // 0x2 + field public static final int USER_OPERATION_ERROR_MAX_RUNNING_USERS = 3; // 0x3 + field public static final int USER_OPERATION_ERROR_UNKNOWN = 1; // 0x1 + field public static final int USER_OPERATION_SUCCESS = 0; // 0x0 field public static final int WIPE_EXTERNAL_STORAGE = 1; // 0x1 field public static final int WIPE_RESET_PROTECTION_DATA = 2; // 0x2 } @@ -6723,20 +6729,46 @@ package android.app.admin { public class SecurityLog { ctor public SecurityLog(); + field public static final int LEVEL_ERROR = 3; // 0x3 + field public static final int LEVEL_INFO = 1; // 0x1 + field public static final int LEVEL_WARNING = 2; // 0x2 field public static final int TAG_ADB_SHELL_CMD = 210002; // 0x33452 field public static final int TAG_ADB_SHELL_INTERACTIVE = 210001; // 0x33451 field public static final int TAG_APP_PROCESS_START = 210005; // 0x33455 + field public static final int TAG_CERT_AUTHORITY_INSTALLED = 210029; // 0x3346d + field public static final int TAG_CERT_AUTHORITY_REMOVED = 210030; // 0x3346e + field public static final int TAG_KEYGUARD_DISABLED_FEATURES_SET = 210021; // 0x33465 field public static final int TAG_KEYGUARD_DISMISSED = 210006; // 0x33456 field public static final int TAG_KEYGUARD_DISMISS_AUTH_ATTEMPT = 210007; // 0x33457 field public static final int TAG_KEYGUARD_SECURED = 210008; // 0x33458 + field public static final int TAG_KEY_DESTRUCTION = 210026; // 0x3346a + field public static final int TAG_KEY_GENERATED = 210024; // 0x33468 + field public static final int TAG_KEY_IMPORT = 210025; // 0x33469 + field public static final int TAG_LOGGING_STARTED = 210011; // 0x3345b + field public static final int TAG_LOGGING_STOPPED = 210012; // 0x3345c + field public static final int TAG_LOG_BUFFER_SIZE_CRITICAL = 210015; // 0x3345f + field public static final int TAG_MAX_PASSWORD_ATTEMPTS_SET = 210020; // 0x33464 + field public static final int TAG_MAX_SCREEN_LOCK_TIMEOUT_SET = 210019; // 0x33463 + field public static final int TAG_MEDIA_MOUNT = 210013; // 0x3345d + field public static final int TAG_MEDIA_UNMOUNT = 210014; // 0x3345e + field public static final int TAG_OS_SHUTDOWN = 210010; // 0x3345a + field public static final int TAG_OS_STARTUP = 210009; // 0x33459 + field public static final int TAG_PASSWORD_COMPLEXITY_SET = 210017; // 0x33461 + field public static final int TAG_PASSWORD_EXPIRATION_SET = 210016; // 0x33460 + field public static final int TAG_PASSWORD_HISTORY_LENGTH_SET = 210018; // 0x33462 + field public static final int TAG_REMOTE_LOCK = 210022; // 0x33466 field public static final int TAG_SYNC_RECV_FILE = 210003; // 0x33453 field public static final int TAG_SYNC_SEND_FILE = 210004; // 0x33454 + field public static final int TAG_USER_RESTRICTION_ADDED = 210027; // 0x3346b + field public static final int TAG_USER_RESTRICTION_REMOVED = 210028; // 0x3346c + field public static final int TAG_WIPE_FAILURE = 210023; // 0x33467 } public static final class SecurityLog.SecurityEvent implements android.os.Parcelable { method public int describeContents(); method public java.lang.Object getData(); method public long getId(); + method public int getLogLevel(); method public int getTag(); method public long getTimeNanos(); method public void writeToParcel(android.os.Parcel, int); @@ -7134,6 +7166,8 @@ package android.app.slice { method public android.net.Uri getUri(); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator<android.app.slice.Slice> CREATOR; + field public static final java.lang.String EXTRA_RANGE_VALUE = "android.app.slice.extra.RANGE_VALUE"; + field public static final deprecated java.lang.String EXTRA_SLIDER_VALUE = "android.app.slice.extra.SLIDER_VALUE"; field public static final java.lang.String EXTRA_TOGGLE_STATE = "android.app.slice.extra.TOGGLE_STATE"; field public static final java.lang.String HINT_ACTIONS = "actions"; field public static final java.lang.String HINT_CALLER_NEEDED = "caller_needed"; @@ -7141,7 +7175,6 @@ package android.app.slice { field public static final java.lang.String HINT_LARGE = "large"; field public static final java.lang.String HINT_LIST = "list"; field public static final java.lang.String HINT_LIST_ITEM = "list_item"; - field public static final java.lang.String HINT_MAX = "max"; field public static final java.lang.String HINT_NO_TINT = "no_tint"; field public static final java.lang.String HINT_PARTIAL = "partial"; field public static final java.lang.String HINT_SEE_MORE = "see_more"; @@ -7151,11 +7184,14 @@ package android.app.slice { field public static final java.lang.String HINT_TITLE = "title"; field public static final java.lang.String SUBTYPE_COLOR = "color"; field public static final java.lang.String SUBTYPE_CONTENT_DESCRIPTION = "content_description"; + field public static final java.lang.String SUBTYPE_MAX = "max"; field public static final java.lang.String SUBTYPE_MESSAGE = "message"; field public static final java.lang.String SUBTYPE_PRIORITY = "priority"; - field public static final java.lang.String SUBTYPE_SLIDER = "slider"; + field public static final java.lang.String SUBTYPE_RANGE = "range"; + field public static final deprecated java.lang.String SUBTYPE_SLIDER = "slider"; field public static final java.lang.String SUBTYPE_SOURCE = "source"; field public static final java.lang.String SUBTYPE_TOGGLE = "toggle"; + field public static final java.lang.String SUBTYPE_VALUE = "value"; } public static class Slice.Builder { @@ -23287,6 +23323,7 @@ package android.media { method public java.lang.String getPropertyString(java.lang.String); method public android.media.MediaDrm.ProvisionRequest getProvisionRequest(); method public byte[] getSecureStop(byte[]); + method public java.util.List<byte[]> getSecureStopIds(); method public java.util.List<byte[]> getSecureStops(); method public int getSecurityLevel(byte[]); method public static final boolean isCryptoSchemeSupported(java.util.UUID); @@ -23296,9 +23333,11 @@ package android.media { method public void provideProvisionResponse(byte[]) throws android.media.DeniedByServerException; method public java.util.HashMap<java.lang.String, java.lang.String> queryKeyStatus(byte[]); method public deprecated void release(); - method public void releaseAllSecureStops(); + method public deprecated void releaseAllSecureStops(); method public void releaseSecureStops(byte[]); + method public void removeAllSecureStops(); method public void removeKeys(byte[]); + method public void removeSecureStop(byte[]); method public void restoreKeys(byte[], byte[]); method public void setOnEventListener(android.media.MediaDrm.OnEventListener); method public void setOnExpirationUpdateListener(android.media.MediaDrm.OnExpirationUpdateListener, android.os.Handler); @@ -38498,6 +38537,20 @@ package android.service.autofill { method public android.service.autofill.Dataset.Builder setValue(android.view.autofill.AutofillId, android.view.autofill.AutofillValue, java.util.regex.Pattern, android.widget.RemoteViews); } + public final class DateTransformation implements android.os.Parcelable android.service.autofill.Transformation { + ctor public DateTransformation(android.view.autofill.AutofillId, java.text.DateFormat); + method public int describeContents(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator<android.service.autofill.DateTransformation> CREATOR; + } + + public final class DateValueSanitizer implements android.os.Parcelable android.service.autofill.Sanitizer { + ctor public DateValueSanitizer(java.text.DateFormat); + method public int describeContents(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator<android.service.autofill.DateValueSanitizer> CREATOR; + } + public final class FieldClassification { method public java.util.List<android.service.autofill.FieldClassification.Match> getMatches(); } @@ -38667,6 +38720,7 @@ package android.service.autofill { public final class UserData implements android.os.Parcelable { method public int describeContents(); method public java.lang.String getFieldClassificationAlgorithm(); + method public java.lang.String getId(); method public static int getMaxFieldClassificationIdsSize(); method public static int getMaxUserDataSize(); method public static int getMaxValueLength(); @@ -38676,7 +38730,7 @@ package android.service.autofill { } public static final class UserData.Builder { - ctor public UserData.Builder(java.lang.String, java.lang.String); + ctor public UserData.Builder(java.lang.String, java.lang.String, java.lang.String); method public android.service.autofill.UserData.Builder add(java.lang.String, java.lang.String); method public android.service.autofill.UserData build(); method public android.service.autofill.UserData.Builder setFieldClassificationAlgorithm(java.lang.String, android.os.Bundle); @@ -44288,6 +44342,11 @@ package android.text.util { method public static final boolean addLinks(android.text.Spannable, java.util.regex.Pattern, java.lang.String); method public static final boolean addLinks(android.text.Spannable, java.util.regex.Pattern, java.lang.String, android.text.util.Linkify.MatchFilter, android.text.util.Linkify.TransformFilter); method public static final boolean addLinks(android.text.Spannable, java.util.regex.Pattern, java.lang.String, java.lang.String[], android.text.util.Linkify.MatchFilter, android.text.util.Linkify.TransformFilter); + method public static java.util.concurrent.Future<java.lang.Void> addLinksAsync(android.widget.TextView, android.view.textclassifier.TextLinks.Options); + method public static java.util.concurrent.Future<java.lang.Void> addLinksAsync(android.widget.TextView, android.view.textclassifier.TextLinks.Options, java.util.concurrent.Executor, java.util.function.Consumer<java.lang.Integer>); + method public static java.util.concurrent.Future<java.lang.Void> addLinksAsync(android.text.Spannable, android.view.textclassifier.TextClassifier, android.view.textclassifier.TextLinks.Options); + method public static java.util.concurrent.Future<java.lang.Void> addLinksAsync(android.text.Spannable, android.view.textclassifier.TextClassifier, int); + method public static java.util.concurrent.Future<java.lang.Void> addLinksAsync(android.text.Spannable, android.view.textclassifier.TextClassifier, android.view.textclassifier.TextLinks.Options, java.util.concurrent.Executor, java.util.function.Consumer<java.lang.Integer>); field public static final int ALL = 15; // 0xf field public static final int EMAIL_ADDRESSES = 2; // 0x2 field public static final int MAP_ADDRESSES = 8; // 0x8 @@ -49528,6 +49587,7 @@ package android.view.autofill { method public java.util.List<java.lang.String> getAvailableFieldClassificationAlgorithms(); method public java.lang.String getDefaultFieldClassificationAlgorithm(); method public android.service.autofill.UserData getUserData(); + method public java.lang.String getUserDataId(); method public boolean hasEnabledAutofillServices(); method public boolean isAutofillSupported(); method public boolean isEnabled(); @@ -49983,7 +50043,8 @@ package android.view.inputmethod { package android.view.textclassifier { - public final class TextClassification { + public final class TextClassification implements android.os.Parcelable { + method public int describeContents(); method public float getConfidenceScore(java.lang.String); method public java.lang.String getEntity(int); method public int getEntityCount(); @@ -49997,6 +50058,8 @@ package android.view.textclassifier { method public java.lang.CharSequence getSecondaryLabel(int); method public java.lang.String getSignature(); method public java.lang.String getText(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator<android.view.textclassifier.TextClassification> CREATOR; } public static final class TextClassification.Builder { @@ -50037,6 +50100,7 @@ package android.view.textclassifier { method public default android.view.textclassifier.TextLinks generateLinks(java.lang.CharSequence, android.view.textclassifier.TextLinks.Options); method public default android.view.textclassifier.TextLinks generateLinks(java.lang.CharSequence); method public default java.util.Collection<java.lang.String> getEntitiesForPreset(int); + method public default android.view.textclassifier.logging.Logger getLogger(android.view.textclassifier.logging.Logger.Config); method public default android.view.textclassifier.TextSelection suggestSelection(java.lang.CharSequence, int, int, android.view.textclassifier.TextSelection.Options); method public default android.view.textclassifier.TextSelection suggestSelection(java.lang.CharSequence, int, int); method public default android.view.textclassifier.TextSelection suggestSelection(java.lang.CharSequence, int, int, android.os.LocaleList); @@ -50066,32 +50130,42 @@ package android.view.textclassifier { } public final class TextLinks implements android.os.Parcelable { - method public boolean apply(android.text.SpannableString, java.util.function.Function<android.view.textclassifier.TextLinks.TextLink, android.text.style.ClickableSpan>); method public int describeContents(); method public java.util.Collection<android.view.textclassifier.TextLinks.TextLink> getLinks(); method public void writeToParcel(android.os.Parcel, int); + field public static final int APPLY_STRATEGY_IGNORE = 0; // 0x0 + field public static final int APPLY_STRATEGY_REPLACE = 1; // 0x1 field public static final android.os.Parcelable.Creator<android.view.textclassifier.TextLinks> CREATOR; + field public static final int STATUS_DIFFERENT_TEXT = 3; // 0x3 + field public static final int STATUS_LINKS_APPLIED = 0; // 0x0 + field public static final int STATUS_NO_LINKS_APPLIED = 2; // 0x2 + field public static final int STATUS_NO_LINKS_FOUND = 1; // 0x1 } public static final class TextLinks.Builder { ctor public TextLinks.Builder(java.lang.String); - method public android.view.textclassifier.TextLinks.Builder addLink(android.view.textclassifier.TextLinks.TextLink); + method public android.view.textclassifier.TextLinks.Builder addLink(int, int, java.util.Map<java.lang.String, java.lang.Float>); method public android.view.textclassifier.TextLinks build(); + method public android.view.textclassifier.TextLinks.Builder clearTextLinks(); } public static final class TextLinks.Options implements android.os.Parcelable { ctor public TextLinks.Options(); method public int describeContents(); + method public static android.view.textclassifier.TextLinks.Options fromLinkMask(int); + method public int getApplyStrategy(); method public android.os.LocaleList getDefaultLocales(); method public android.view.textclassifier.TextClassifier.EntityConfig getEntityConfig(); + method public java.util.function.Function<android.view.textclassifier.TextLinks.TextLink, android.view.textclassifier.TextLinks.TextLinkSpan> getSpanFactory(); + method public android.view.textclassifier.TextLinks.Options setApplyStrategy(int); method public android.view.textclassifier.TextLinks.Options setDefaultLocales(android.os.LocaleList); method public android.view.textclassifier.TextLinks.Options setEntityConfig(android.view.textclassifier.TextClassifier.EntityConfig); + method public android.view.textclassifier.TextLinks.Options setSpanFactory(java.util.function.Function<android.view.textclassifier.TextLinks.TextLink, android.view.textclassifier.TextLinks.TextLinkSpan>); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator<android.view.textclassifier.TextLinks.Options> CREATOR; } public static final class TextLinks.TextLink implements android.os.Parcelable { - ctor public TextLinks.TextLink(java.lang.String, int, int, java.util.Map<java.lang.String, java.lang.Float>); method public int describeContents(); method public float getConfidenceScore(java.lang.String); method public int getEnd(); @@ -50102,13 +50176,22 @@ package android.view.textclassifier { field public static final android.os.Parcelable.Creator<android.view.textclassifier.TextLinks.TextLink> CREATOR; } - public final class TextSelection { + public static class TextLinks.TextLinkSpan extends android.text.style.ClickableSpan { + ctor public TextLinks.TextLinkSpan(android.view.textclassifier.TextLinks.TextLink); + method public final android.view.textclassifier.TextLinks.TextLink getTextLink(); + method public void onClick(android.view.View); + } + + public final class TextSelection implements android.os.Parcelable { + method public int describeContents(); method public float getConfidenceScore(java.lang.String); method public java.lang.String getEntity(int); method public int getEntityCount(); method public int getSelectionEndIndex(); method public int getSelectionStartIndex(); method public java.lang.String getSignature(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator<android.view.textclassifier.TextSelection> CREATOR; } public static final class TextSelection.Builder { @@ -50129,6 +50212,75 @@ package android.view.textclassifier { } +package android.view.textclassifier.logging { + + public abstract class Logger { + ctor public Logger(android.view.textclassifier.logging.Logger.Config); + method public java.text.BreakIterator getTokenIterator(java.util.Locale); + method public boolean isSmartSelection(java.lang.String); + method public final void logSelectionActionEvent(int, int, int); + method public final void logSelectionActionEvent(int, int, int, android.view.textclassifier.TextClassification); + method public final void logSelectionModifiedEvent(int, int); + method public final void logSelectionModifiedEvent(int, int, android.view.textclassifier.TextClassification); + method public final void logSelectionModifiedEvent(int, int, android.view.textclassifier.TextSelection); + method public final void logSelectionStartedEvent(int); + method public abstract void writeEvent(android.view.textclassifier.logging.SelectionEvent); + field public static final int OUT_OF_BOUNDS = 2147483647; // 0x7fffffff + field public static final int OUT_OF_BOUNDS_NEGATIVE = -2147483648; // 0x80000000 + field public static final java.lang.String WIDGET_CUSTOM_EDITTEXT = "customedit"; + field public static final java.lang.String WIDGET_CUSTOM_TEXTVIEW = "customview"; + field public static final java.lang.String WIDGET_CUSTOM_UNSELECTABLE_TEXTVIEW = "nosel-customview"; + field public static final java.lang.String WIDGET_EDITTEXT = "edittext"; + field public static final java.lang.String WIDGET_EDIT_WEBVIEW = "edit-webview"; + field public static final java.lang.String WIDGET_TEXTVIEW = "textview"; + field public static final java.lang.String WIDGET_UNKNOWN = "unknown"; + field public static final java.lang.String WIDGET_UNSELECTABLE_TEXTVIEW = "nosel-textview"; + field public static final java.lang.String WIDGET_WEBVIEW = "webview"; + } + + public static final class Logger.Config { + ctor public Logger.Config(android.content.Context, java.lang.String, java.lang.String); + method public java.lang.String getPackageName(); + method public java.lang.String getWidgetType(); + method public java.lang.String getWidgetVersion(); + } + + public final class SelectionEvent { + method public long getDurationSincePreviousEvent(); + method public long getDurationSinceSessionStart(); + method public int getEnd(); + method public java.lang.String getEntityType(); + method public int getEventIndex(); + method public long getEventTime(); + method public int getEventType(); + method public java.lang.String getPackageName(); + method public java.lang.String getSessionId(); + method public java.lang.String getSignature(); + method public int getSmartEnd(); + method public int getSmartStart(); + method public int getStart(); + method public java.lang.String getWidgetType(); + method public java.lang.String getWidgetVersion(); + field public static final int ACTION_ABANDON = 107; // 0x6b + field public static final int ACTION_COPY = 101; // 0x65 + field public static final int ACTION_CUT = 103; // 0x67 + field public static final int ACTION_DRAG = 106; // 0x6a + field public static final int ACTION_OTHER = 108; // 0x6c + field public static final int ACTION_OVERTYPE = 100; // 0x64 + field public static final int ACTION_PASTE = 102; // 0x66 + field public static final int ACTION_RESET = 201; // 0xc9 + field public static final int ACTION_SELECT_ALL = 200; // 0xc8 + field public static final int ACTION_SHARE = 104; // 0x68 + field public static final int ACTION_SMART_SHARE = 105; // 0x69 + field public static final int EVENT_AUTO_SELECTION = 5; // 0x5 + field public static final int EVENT_SELECTION_MODIFIED = 2; // 0x2 + field public static final int EVENT_SELECTION_STARTED = 1; // 0x1 + field public static final int EVENT_SMART_SELECTION_MULTI = 4; // 0x4 + field public static final int EVENT_SMART_SELECTION_SINGLE = 3; // 0x3 + } + +} + package android.view.textservice { public final class SentenceSuggestionsInfo implements android.os.Parcelable { @@ -52253,6 +52405,7 @@ package android.widget { ctor public Magnifier(android.view.View); method public void dismiss(); method public void show(float, float); + method public void update(); } public class MediaController extends android.widget.FrameLayout { diff --git a/api/system-current.txt b/api/system-current.txt index 663ad112280a..034ee3090fc4 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -29,6 +29,7 @@ package android { field public static final java.lang.String BIND_RESOLVER_RANKER_SERVICE = "android.permission.BIND_RESOLVER_RANKER_SERVICE"; field public static final java.lang.String BIND_RUNTIME_PERMISSION_PRESENTER_SERVICE = "android.permission.BIND_RUNTIME_PERMISSION_PRESENTER_SERVICE"; field public static final java.lang.String BIND_SETTINGS_SUGGESTIONS_SERVICE = "android.permission.BIND_SETTINGS_SUGGESTIONS_SERVICE"; + field public static final java.lang.String BIND_TEXTCLASSIFIER_SERVICE = "android.permission.BIND_TEXTCLASSIFIER_SERVICE"; field public static final java.lang.String BIND_TRUST_AGENT = "android.permission.BIND_TRUST_AGENT"; field public static final java.lang.String BIND_TV_REMOTE_SERVICE = "android.permission.BIND_TV_REMOTE_SERVICE"; field public static final java.lang.String BLUETOOTH_PRIVILEGED = "android.permission.BLUETOOTH_PRIVILEGED"; @@ -2596,12 +2597,17 @@ package android.media.audiopolicy { method public void onStatusChange(); } + public static abstract class AudioPolicy.AudioPolicyVolumeCallback { + method public void onVolumeAdjustment(int); + } + public static class AudioPolicy.Builder { ctor public AudioPolicy.Builder(android.content.Context); method public android.media.audiopolicy.AudioPolicy.Builder addMix(android.media.audiopolicy.AudioMix) throws java.lang.IllegalArgumentException; method public android.media.audiopolicy.AudioPolicy build(); method public void setAudioPolicyFocusListener(android.media.audiopolicy.AudioPolicy.AudioPolicyFocusListener); method public void setAudioPolicyStatusListener(android.media.audiopolicy.AudioPolicy.AudioPolicyStatusListener); + method public android.media.audiopolicy.AudioPolicy.Builder setAudioPolicyVolumeCallback(android.media.audiopolicy.AudioPolicy.AudioPolicyVolumeCallback); method public android.media.audiopolicy.AudioPolicy.Builder setIsAudioFocusPolicy(boolean); method public android.media.audiopolicy.AudioPolicy.Builder setLooper(android.os.Looper) throws java.lang.IllegalArgumentException; } @@ -3834,6 +3840,20 @@ package android.os { public static abstract class UserManager.UserRestrictionSource implements java.lang.annotation.Annotation { } + public class WorkSource implements android.os.Parcelable { + method public android.os.WorkSource.WorkChain createWorkChain(); + } + + public static final class WorkSource.WorkChain implements android.os.Parcelable { + ctor public WorkSource.WorkChain(); + method public android.os.WorkSource.WorkChain addNode(int, java.lang.String); + method public int describeContents(); + method public java.lang.String getAttributionTag(); + method public int getAttributionUid(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator<android.os.WorkSource.WorkChain> CREATOR; + } + } package android.os.storage { @@ -4438,6 +4458,24 @@ package android.service.settings.suggestions { } +package android.service.textclassifier { + + public abstract class TextClassifierService extends android.app.Service { + ctor public TextClassifierService(); + method public final android.os.IBinder onBind(android.content.Intent); + method public abstract void onClassifyText(java.lang.CharSequence, int, int, android.view.textclassifier.TextClassification.Options, android.os.CancellationSignal, android.service.textclassifier.TextClassifierService.Callback<android.view.textclassifier.TextClassification>); + method public abstract void onGenerateLinks(java.lang.CharSequence, android.view.textclassifier.TextLinks.Options, android.os.CancellationSignal, android.service.textclassifier.TextClassifierService.Callback<android.view.textclassifier.TextLinks>); + method public abstract void onSuggestSelection(java.lang.CharSequence, int, int, android.view.textclassifier.TextSelection.Options, android.os.CancellationSignal, android.service.textclassifier.TextClassifierService.Callback<android.view.textclassifier.TextSelection>); + field public static final java.lang.String SERVICE_INTERFACE = "android.service.textclassifier.TextClassifierService"; + } + + public static abstract interface TextClassifierService.Callback<T> { + method public abstract void onFailure(java.lang.CharSequence); + method public abstract void onSuccess(T); + } + +} + package android.service.trust { public class TrustAgentService extends android.app.Service { @@ -5075,16 +5113,669 @@ package android.telephony.data { package android.telephony.ims { + public final class ImsCallForwardInfo implements android.os.Parcelable { + method public int describeContents(); + method public int getCondition(); + method public java.lang.String getNumber(); + method public int getServiceClass(); + method public int getStatus(); + method public int getTimeSeconds(); + method public int getToA(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator<android.telephony.ims.ImsCallForwardInfo> CREATOR; + } + + public final class ImsCallProfile implements android.os.Parcelable { + method public int describeContents(); + method public java.lang.String getCallExtra(java.lang.String); + method public java.lang.String getCallExtra(java.lang.String, java.lang.String); + method public boolean getCallExtraBoolean(java.lang.String); + method public boolean getCallExtraBoolean(java.lang.String, boolean); + method public int getCallExtraInt(java.lang.String); + method public int getCallExtraInt(java.lang.String, int); + method public android.os.Bundle getCallExtras(); + method public int getCallType(); + method public static int getCallTypeFromVideoState(int); + method public android.telephony.ims.ImsStreamMediaProfile getMediaProfile(); + method public int getRestrictCause(); + method public int getServiceType(); + method public static int getVideoStateFromCallType(int); + method public static int getVideoStateFromImsCallProfile(android.telephony.ims.ImsCallProfile); + method public boolean isVideoCall(); + method public boolean isVideoPaused(); + method public static int presentationToOir(int); + method public void setCallExtra(java.lang.String, java.lang.String); + method public void setCallExtraBoolean(java.lang.String, boolean); + method public void setCallExtraInt(java.lang.String, int); + method public void updateCallExtras(android.telephony.ims.ImsCallProfile); + method public void updateCallType(android.telephony.ims.ImsCallProfile); + method public void writeToParcel(android.os.Parcel, int); + field public static final int CALL_RESTRICT_CAUSE_DISABLED = 2; // 0x2 + field public static final int CALL_RESTRICT_CAUSE_HD = 3; // 0x3 + field public static final int CALL_RESTRICT_CAUSE_NONE = 0; // 0x0 + field public static final int CALL_RESTRICT_CAUSE_RAT = 1; // 0x1 + field public static final int CALL_TYPE_VIDEO_N_VOICE = 3; // 0x3 + field public static final int CALL_TYPE_VOICE = 2; // 0x2 + field public static final int CALL_TYPE_VOICE_N_VIDEO = 1; // 0x1 + field public static final int CALL_TYPE_VS = 8; // 0x8 + field public static final int CALL_TYPE_VS_RX = 10; // 0xa + field public static final int CALL_TYPE_VS_TX = 9; // 0x9 + field public static final int CALL_TYPE_VT = 4; // 0x4 + field public static final int CALL_TYPE_VT_NODIR = 7; // 0x7 + field public static final int CALL_TYPE_VT_RX = 6; // 0x6 + field public static final int CALL_TYPE_VT_TX = 5; // 0x5 + field public static final android.os.Parcelable.Creator<android.telephony.ims.ImsCallProfile> CREATOR; + field public static final int DIALSTRING_NORMAL = 0; // 0x0 + field public static final int DIALSTRING_SS_CONF = 1; // 0x1 + field public static final int DIALSTRING_USSD = 2; // 0x2 + field public static final java.lang.String EXTRA_ADDITIONAL_CALL_INFO = "AdditionalCallInfo"; + field public static final java.lang.String EXTRA_CALL_RAT_TYPE = "CallRadioTech"; + field public static final java.lang.String EXTRA_CHILD_NUMBER = "ChildNum"; + field public static final java.lang.String EXTRA_CNA = "cna"; + field public static final java.lang.String EXTRA_CNAP = "cnap"; + field public static final java.lang.String EXTRA_CODEC = "Codec"; + field public static final java.lang.String EXTRA_DIALSTRING = "dialstring"; + field public static final java.lang.String EXTRA_DISPLAY_TEXT = "DisplayText"; + field public static final java.lang.String EXTRA_IS_CALL_PULL = "CallPull"; + field public static final java.lang.String EXTRA_OI = "oi"; + field public static final java.lang.String EXTRA_OIR = "oir"; + field public static final java.lang.String EXTRA_REMOTE_URI = "remote_uri"; + field public static final java.lang.String EXTRA_USSD = "ussd"; + field public static final int OIR_DEFAULT = 0; // 0x0 + field public static final int OIR_PRESENTATION_NOT_RESTRICTED = 2; // 0x2 + field public static final int OIR_PRESENTATION_PAYPHONE = 4; // 0x4 + field public static final int OIR_PRESENTATION_RESTRICTED = 1; // 0x1 + field public static final int OIR_PRESENTATION_UNKNOWN = 3; // 0x3 + field public static final int SERVICE_TYPE_EMERGENCY = 2; // 0x2 + field public static final int SERVICE_TYPE_NONE = 0; // 0x0 + field public static final int SERVICE_TYPE_NORMAL = 1; // 0x1 + } + + public class ImsCallSessionListener { + method public void callSessionConferenceExtendFailed(android.telephony.ims.ImsReasonInfo); + method public void callSessionConferenceExtendReceived(android.telephony.ims.stub.ImsCallSessionImplBase, android.telephony.ims.ImsCallProfile); + method public void callSessionConferenceExtended(android.telephony.ims.stub.ImsCallSessionImplBase, android.telephony.ims.ImsCallProfile); + method public void callSessionConferenceStateUpdated(android.telephony.ims.ImsConferenceState); + method public void callSessionHandover(int, int, android.telephony.ims.ImsReasonInfo); + method public void callSessionHandoverFailed(int, int, android.telephony.ims.ImsReasonInfo); + method public void callSessionHeld(android.telephony.ims.ImsCallProfile); + method public void callSessionHoldFailed(android.telephony.ims.ImsReasonInfo); + method public void callSessionHoldReceived(android.telephony.ims.ImsCallProfile); + method public void callSessionInitiated(android.telephony.ims.ImsCallProfile); + method public void callSessionInitiatedFailed(android.telephony.ims.ImsReasonInfo); + method public void callSessionInviteParticipantsRequestDelivered(); + method public void callSessionInviteParticipantsRequestFailed(android.telephony.ims.ImsReasonInfo); + method public void callSessionMayHandover(int, int); + method public void callSessionMergeComplete(android.telephony.ims.stub.ImsCallSessionImplBase); + method public void callSessionMergeFailed(android.telephony.ims.ImsReasonInfo); + method public void callSessionMergeStarted(android.telephony.ims.stub.ImsCallSessionImplBase, android.telephony.ims.ImsCallProfile); + method public void callSessionMultipartyStateChanged(boolean); + method public void callSessionProgressing(android.telephony.ims.ImsStreamMediaProfile); + method public void callSessionRemoveParticipantsRequestDelivered(); + method public void callSessionRemoveParticipantsRequestFailed(android.telephony.ims.ImsReasonInfo); + method public void callSessionResumeFailed(android.telephony.ims.ImsReasonInfo); + method public void callSessionResumeReceived(android.telephony.ims.ImsCallProfile); + method public void callSessionResumed(android.telephony.ims.ImsCallProfile); + method public void callSessionRttMessageReceived(java.lang.String); + method public void callSessionRttModifyRequestReceived(android.telephony.ims.ImsCallProfile); + method public void callSessionRttModifyResponseReceived(int); + method public void callSessionSuppServiceReceived(android.telephony.ims.ImsSuppServiceNotification); + method public void callSessionTerminated(android.telephony.ims.ImsReasonInfo); + method public void callSessionTtyModeReceived(int); + method public void callSessionUpdateFailed(android.telephony.ims.ImsReasonInfo); + method public void callSessionUpdateReceived(android.telephony.ims.ImsCallProfile); + method public void callSessionUpdated(android.telephony.ims.ImsCallProfile); + method public void callSessionUssdMessageReceived(int, java.lang.String); + } + + public final class ImsConferenceState implements android.os.Parcelable { + method public int describeContents(); + method public static int getConnectionStateForStatus(java.lang.String); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator<android.telephony.ims.ImsConferenceState> CREATOR; + field public static final java.lang.String DISPLAY_TEXT = "display-text"; + field public static final java.lang.String ENDPOINT = "endpoint"; + field public static final java.lang.String SIP_STATUS_CODE = "sipstatuscode"; + field public static final java.lang.String STATUS = "status"; + field public static final java.lang.String STATUS_ALERTING = "alerting"; + field public static final java.lang.String STATUS_CONNECTED = "connected"; + field public static final java.lang.String STATUS_CONNECT_FAIL = "connect-fail"; + field public static final java.lang.String STATUS_DIALING_IN = "dialing-in"; + field public static final java.lang.String STATUS_DIALING_OUT = "dialing-out"; + field public static final java.lang.String STATUS_DISCONNECTED = "disconnected"; + field public static final java.lang.String STATUS_DISCONNECTING = "disconnecting"; + field public static final java.lang.String STATUS_MUTED_VIA_FOCUS = "muted-via-focus"; + field public static final java.lang.String STATUS_ON_HOLD = "on-hold"; + field public static final java.lang.String STATUS_PENDING = "pending"; + field public static final java.lang.String STATUS_SEND_ONLY = "sendonly"; + field public static final java.lang.String STATUS_SEND_RECV = "sendrecv"; + field public static final java.lang.String USER = "user"; + field public final java.util.HashMap<java.lang.String, android.os.Bundle> mParticipants; + } + + public final class ImsExternalCallState implements android.os.Parcelable { + method public int describeContents(); + method public android.net.Uri getAddress(); + method public int getCallId(); + method public int getCallState(); + method public int getCallType(); + method public boolean isCallHeld(); + method public boolean isCallPullable(); + method public void writeToParcel(android.os.Parcel, int); + field public static final int CALL_STATE_CONFIRMED = 1; // 0x1 + field public static final int CALL_STATE_TERMINATED = 2; // 0x2 + field public static final android.os.Parcelable.Creator<android.telephony.ims.ImsExternalCallState> CREATOR; + } + + public final class ImsReasonInfo implements android.os.Parcelable { + method public int describeContents(); + method public int getCode(); + method public int getExtraCode(); + method public java.lang.String getExtraMessage(); + method public void writeToParcel(android.os.Parcel, int); + field public static final int CODE_ACCESS_CLASS_BLOCKED = 1512; // 0x5e8 + field public static final int CODE_ANSWERED_ELSEWHERE = 1014; // 0x3f6 + field public static final int CODE_BLACKLISTED_CALL_ID = 506; // 0x1fa + field public static final int CODE_CALL_BARRED = 240; // 0xf0 + field public static final int CODE_CALL_DROP_IWLAN_TO_LTE_UNAVAILABLE = 1100; // 0x44c + field public static final int CODE_CALL_END_CAUSE_CALL_PULL = 1016; // 0x3f8 + field public static final int CODE_CALL_PULL_OUT_OF_SYNC = 1015; // 0x3f7 + field public static final int CODE_DATA_DISABLED = 1406; // 0x57e + field public static final int CODE_DATA_LIMIT_REACHED = 1405; // 0x57d + field public static final int CODE_DIAL_MODIFIED_TO_DIAL = 246; // 0xf6 + field public static final int CODE_DIAL_MODIFIED_TO_DIAL_VIDEO = 247; // 0xf7 + field public static final int CODE_DIAL_MODIFIED_TO_SS = 245; // 0xf5 + field public static final int CODE_DIAL_MODIFIED_TO_USSD = 244; // 0xf4 + field public static final int CODE_DIAL_VIDEO_MODIFIED_TO_DIAL = 248; // 0xf8 + field public static final int CODE_DIAL_VIDEO_MODIFIED_TO_DIAL_VIDEO = 249; // 0xf9 + field public static final int CODE_DIAL_VIDEO_MODIFIED_TO_SS = 250; // 0xfa + field public static final int CODE_DIAL_VIDEO_MODIFIED_TO_USSD = 251; // 0xfb + field public static final int CODE_ECBM_NOT_SUPPORTED = 901; // 0x385 + field public static final int CODE_EMERGENCY_PERM_FAILURE = 364; // 0x16c + field public static final int CODE_EMERGENCY_TEMP_FAILURE = 363; // 0x16b + field public static final int CODE_EPDG_TUNNEL_ESTABLISH_FAILURE = 1400; // 0x578 + field public static final int CODE_EPDG_TUNNEL_LOST_CONNECTION = 1402; // 0x57a + field public static final int CODE_EPDG_TUNNEL_REKEY_FAILURE = 1401; // 0x579 + field public static final int CODE_FDN_BLOCKED = 241; // 0xf1 + field public static final int CODE_IKEV2_AUTH_FAILURE = 1408; // 0x580 + field public static final int CODE_IMEI_NOT_ACCEPTED = 243; // 0xf3 + field public static final int CODE_IWLAN_DPD_FAILURE = 1300; // 0x514 + field public static final int CODE_LOCAL_CALL_BUSY = 142; // 0x8e + field public static final int CODE_LOCAL_CALL_CS_RETRY_REQUIRED = 146; // 0x92 + field public static final int CODE_LOCAL_CALL_DECLINE = 143; // 0x8f + field public static final int CODE_LOCAL_CALL_EXCEEDED = 141; // 0x8d + field public static final int CODE_LOCAL_CALL_RESOURCE_RESERVATION_FAILED = 145; // 0x91 + field public static final int CODE_LOCAL_CALL_TERMINATED = 148; // 0x94 + field public static final int CODE_LOCAL_CALL_VCC_ON_PROGRESSING = 144; // 0x90 + field public static final int CODE_LOCAL_CALL_VOLTE_RETRY_REQUIRED = 147; // 0x93 + field public static final int CODE_LOCAL_ENDED_BY_CONFERENCE_MERGE = 108; // 0x6c + field public static final int CODE_LOCAL_HO_NOT_FEASIBLE = 149; // 0x95 + field public static final int CODE_LOCAL_ILLEGAL_ARGUMENT = 101; // 0x65 + field public static final int CODE_LOCAL_ILLEGAL_STATE = 102; // 0x66 + field public static final int CODE_LOCAL_IMS_SERVICE_DOWN = 106; // 0x6a + field public static final int CODE_LOCAL_INTERNAL_ERROR = 103; // 0x67 + field public static final int CODE_LOCAL_LOW_BATTERY = 112; // 0x70 + field public static final int CODE_LOCAL_NETWORK_IP_CHANGED = 124; // 0x7c + field public static final int CODE_LOCAL_NETWORK_NO_LTE_COVERAGE = 122; // 0x7a + field public static final int CODE_LOCAL_NETWORK_NO_SERVICE = 121; // 0x79 + field public static final int CODE_LOCAL_NETWORK_ROAMING = 123; // 0x7b + field public static final int CODE_LOCAL_NOT_REGISTERED = 132; // 0x84 + field public static final int CODE_LOCAL_NO_PENDING_CALL = 107; // 0x6b + field public static final int CODE_LOCAL_POWER_OFF = 111; // 0x6f + field public static final int CODE_LOCAL_SERVICE_UNAVAILABLE = 131; // 0x83 + field public static final int CODE_LOW_BATTERY = 505; // 0x1f9 + field public static final int CODE_MAXIMUM_NUMBER_OF_CALLS_REACHED = 1403; // 0x57b + field public static final int CODE_MEDIA_INIT_FAILED = 401; // 0x191 + field public static final int CODE_MEDIA_NOT_ACCEPTABLE = 403; // 0x193 + field public static final int CODE_MEDIA_NO_DATA = 402; // 0x192 + field public static final int CODE_MEDIA_UNSPECIFIED = 404; // 0x194 + field public static final int CODE_MULTIENDPOINT_NOT_SUPPORTED = 902; // 0x386 + field public static final int CODE_NETWORK_DETACH = 1513; // 0x5e9 + field public static final int CODE_NETWORK_REJECT = 1504; // 0x5e0 + field public static final int CODE_NETWORK_RESP_TIMEOUT = 1503; // 0x5df + field public static final int CODE_NO_VALID_SIM = 1501; // 0x5dd + field public static final int CODE_OEM_CAUSE_1 = 61441; // 0xf001 + field public static final int CODE_OEM_CAUSE_10 = 61450; // 0xf00a + field public static final int CODE_OEM_CAUSE_11 = 61451; // 0xf00b + field public static final int CODE_OEM_CAUSE_12 = 61452; // 0xf00c + field public static final int CODE_OEM_CAUSE_13 = 61453; // 0xf00d + field public static final int CODE_OEM_CAUSE_14 = 61454; // 0xf00e + field public static final int CODE_OEM_CAUSE_15 = 61455; // 0xf00f + field public static final int CODE_OEM_CAUSE_2 = 61442; // 0xf002 + field public static final int CODE_OEM_CAUSE_3 = 61443; // 0xf003 + field public static final int CODE_OEM_CAUSE_4 = 61444; // 0xf004 + field public static final int CODE_OEM_CAUSE_5 = 61445; // 0xf005 + field public static final int CODE_OEM_CAUSE_6 = 61446; // 0xf006 + field public static final int CODE_OEM_CAUSE_7 = 61447; // 0xf007 + field public static final int CODE_OEM_CAUSE_8 = 61448; // 0xf008 + field public static final int CODE_OEM_CAUSE_9 = 61449; // 0xf009 + field public static final int CODE_RADIO_ACCESS_FAILURE = 1505; // 0x5e1 + field public static final int CODE_RADIO_INTERNAL_ERROR = 1502; // 0x5de + field public static final int CODE_RADIO_LINK_FAILURE = 1506; // 0x5e2 + field public static final int CODE_RADIO_LINK_LOST = 1507; // 0x5e3 + field public static final int CODE_RADIO_OFF = 1500; // 0x5dc + field public static final int CODE_RADIO_RELEASE_ABNORMAL = 1511; // 0x5e7 + field public static final int CODE_RADIO_RELEASE_NORMAL = 1510; // 0x5e6 + field public static final int CODE_RADIO_SETUP_FAILURE = 1509; // 0x5e5 + field public static final int CODE_RADIO_UPLINK_FAILURE = 1508; // 0x5e4 + field public static final int CODE_REGISTRATION_ERROR = 1000; // 0x3e8 + field public static final int CODE_REMOTE_CALL_DECLINE = 1404; // 0x57c + field public static final int CODE_SIP_ALTERNATE_EMERGENCY_CALL = 1514; // 0x5ea + field public static final int CODE_SIP_BAD_ADDRESS = 337; // 0x151 + field public static final int CODE_SIP_BAD_REQUEST = 331; // 0x14b + field public static final int CODE_SIP_BUSY = 338; // 0x152 + field public static final int CODE_SIP_CLIENT_ERROR = 342; // 0x156 + field public static final int CODE_SIP_FORBIDDEN = 332; // 0x14c + field public static final int CODE_SIP_GLOBAL_ERROR = 362; // 0x16a + field public static final int CODE_SIP_NOT_ACCEPTABLE = 340; // 0x154 + field public static final int CODE_SIP_NOT_FOUND = 333; // 0x14d + field public static final int CODE_SIP_NOT_REACHABLE = 341; // 0x155 + field public static final int CODE_SIP_NOT_SUPPORTED = 334; // 0x14e + field public static final int CODE_SIP_REDIRECTED = 321; // 0x141 + field public static final int CODE_SIP_REQUEST_CANCELLED = 339; // 0x153 + field public static final int CODE_SIP_REQUEST_TIMEOUT = 335; // 0x14f + field public static final int CODE_SIP_SERVER_ERROR = 354; // 0x162 + field public static final int CODE_SIP_SERVER_INTERNAL_ERROR = 351; // 0x15f + field public static final int CODE_SIP_SERVER_TIMEOUT = 353; // 0x161 + field public static final int CODE_SIP_SERVICE_UNAVAILABLE = 352; // 0x160 + field public static final int CODE_SIP_TEMPRARILY_UNAVAILABLE = 336; // 0x150 + field public static final int CODE_SIP_USER_REJECTED = 361; // 0x169 + field public static final int CODE_SUPP_SVC_CANCELLED = 1202; // 0x4b2 + field public static final int CODE_SUPP_SVC_FAILED = 1201; // 0x4b1 + field public static final int CODE_SUPP_SVC_REINVITE_COLLISION = 1203; // 0x4b3 + field public static final int CODE_TIMEOUT_1XX_WAITING = 201; // 0xc9 + field public static final int CODE_TIMEOUT_NO_ANSWER = 202; // 0xca + field public static final int CODE_TIMEOUT_NO_ANSWER_CALL_UPDATE = 203; // 0xcb + field public static final int CODE_UNSPECIFIED = 0; // 0x0 + field public static final int CODE_USER_DECLINE = 504; // 0x1f8 + field public static final int CODE_USER_IGNORE = 503; // 0x1f7 + field public static final int CODE_USER_NOANSWER = 502; // 0x1f6 + field public static final int CODE_USER_TERMINATED = 501; // 0x1f5 + field public static final int CODE_USER_TERMINATED_BY_REMOTE = 510; // 0x1fe + field public static final int CODE_UT_CB_PASSWORD_MISMATCH = 821; // 0x335 + field public static final int CODE_UT_NETWORK_ERROR = 804; // 0x324 + field public static final int CODE_UT_NOT_SUPPORTED = 801; // 0x321 + field public static final int CODE_UT_OPERATION_NOT_ALLOWED = 803; // 0x323 + field public static final int CODE_UT_SERVICE_UNAVAILABLE = 802; // 0x322 + field public static final int CODE_UT_SS_MODIFIED_TO_DIAL = 822; // 0x336 + field public static final int CODE_UT_SS_MODIFIED_TO_DIAL_VIDEO = 825; // 0x339 + field public static final int CODE_UT_SS_MODIFIED_TO_SS = 824; // 0x338 + field public static final int CODE_UT_SS_MODIFIED_TO_USSD = 823; // 0x337 + field public static final int CODE_WIFI_LOST = 1407; // 0x57f + field public static final android.os.Parcelable.Creator<android.telephony.ims.ImsReasonInfo> CREATOR; + field public static final int EXTRA_CODE_CALL_RETRY_BY_SETTINGS = 3; // 0x3 + field public static final int EXTRA_CODE_CALL_RETRY_NORMAL = 1; // 0x1 + field public static final int EXTRA_CODE_CALL_RETRY_SILENT_REDIAL = 2; // 0x2 + field public static final java.lang.String EXTRA_MSG_SERVICE_NOT_AUTHORIZED = "Forbidden. Not Authorized for Service"; + } + public class ImsService extends android.app.Service { ctor public ImsService(); + method public android.telephony.ims.feature.MmTelFeature createMmTelFeature(int); + method public android.telephony.ims.feature.RcsFeature createRcsFeature(int); + method public void disableIms(int); + method public void enableIms(int); + method public android.telephony.ims.stub.ImsConfigImplBase getConfig(int); + method public android.telephony.ims.stub.ImsRegistrationImplBase getRegistration(int); + method public final void onUpdateSupportedImsFeatures(android.telephony.ims.stub.ImsFeatureConfiguration) throws android.os.RemoteException; + method public android.telephony.ims.stub.ImsFeatureConfiguration querySupportedImsFeatures(); + method public void readyForFeatureCreation(); + } + + public final class ImsSsData implements android.os.Parcelable { + ctor public ImsSsData(); + method public int describeContents(); + method public boolean isTypeBarring(); + method public boolean isTypeCf(); + method public boolean isTypeClip(); + method public boolean isTypeClir(); + method public boolean isTypeColp(); + method public boolean isTypeColr(); + method public boolean isTypeCw(); + method public boolean isTypeIcb(); + method public boolean isTypeInterrogation(); + method public boolean isTypeUnConditional(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator<android.telephony.ims.ImsSsData> CREATOR; + field public static final int SS_ACTIVATION = 0; // 0x0 + field public static final int SS_ALL_BARRING = 18; // 0x12 + field public static final int SS_ALL_DATA_TELESERVICES = 3; // 0x3 + field public static final int SS_ALL_TELESERVICES_EXCEPT_SMS = 5; // 0x5 + field public static final int SS_ALL_TELESEVICES = 1; // 0x1 + field public static final int SS_ALL_TELE_AND_BEARER_SERVICES = 0; // 0x0 + field public static final int SS_BAIC = 16; // 0x10 + field public static final int SS_BAIC_ROAMING = 17; // 0x11 + field public static final int SS_BAOC = 13; // 0xd + field public static final int SS_BAOIC = 14; // 0xe + field public static final int SS_BAOIC_EXC_HOME = 15; // 0xf + field public static final int SS_CFU = 0; // 0x0 + field public static final int SS_CFUT = 6; // 0x6 + field public static final int SS_CF_ALL = 4; // 0x4 + field public static final int SS_CF_ALL_CONDITIONAL = 5; // 0x5 + field public static final int SS_CF_BUSY = 1; // 0x1 + field public static final int SS_CF_NOT_REACHABLE = 3; // 0x3 + field public static final int SS_CF_NO_REPLY = 2; // 0x2 + field public static final int SS_CLIP = 7; // 0x7 + field public static final int SS_CLIR = 8; // 0x8 + field public static final int SS_CNAP = 11; // 0xb + field public static final int SS_COLP = 9; // 0x9 + field public static final int SS_COLR = 10; // 0xa + field public static final int SS_DEACTIVATION = 1; // 0x1 + field public static final int SS_ERASURE = 4; // 0x4 + field public static final int SS_INCOMING_BARRING = 20; // 0x14 + field public static final int SS_INCOMING_BARRING_ANONYMOUS = 22; // 0x16 + field public static final int SS_INCOMING_BARRING_DN = 21; // 0x15 + field public static final int SS_INTERROGATION = 2; // 0x2 + field public static final int SS_OUTGOING_BARRING = 19; // 0x13 + field public static final int SS_REGISTRATION = 3; // 0x3 + field public static final int SS_SMS_SERVICES = 4; // 0x4 + field public static final int SS_TELEPHONY = 2; // 0x2 + field public static final int SS_WAIT = 12; // 0xc + } + + public final class ImsSsInfo implements android.os.Parcelable { + ctor public ImsSsInfo(); + method public int describeContents(); + method public java.lang.String getIcbNum(); + method public int getStatus(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator<android.telephony.ims.ImsSsInfo> CREATOR; + field public static final int DISABLED = 0; // 0x0 + field public static final int ENABLED = 1; // 0x1 + field public static final int NOT_REGISTERED = -1; // 0xffffffff + } + + public final class ImsStreamMediaProfile implements android.os.Parcelable { + method public void copyFrom(android.telephony.ims.ImsStreamMediaProfile); + method public int describeContents(); + method public int getAudioDirection(); + method public int getAudioQuality(); + method public int getRttMode(); + method public int getVideoDirection(); + method public int getVideoQuality(); + method public boolean isRttCall(); + method public void setRttMode(int); + method public void writeToParcel(android.os.Parcel, int); + field public static final int AUDIO_QUALITY_AMR = 1; // 0x1 + field public static final int AUDIO_QUALITY_AMR_WB = 2; // 0x2 + field public static final int AUDIO_QUALITY_EVRC = 4; // 0x4 + field public static final int AUDIO_QUALITY_EVRC_B = 5; // 0x5 + field public static final int AUDIO_QUALITY_EVRC_NW = 7; // 0x7 + field public static final int AUDIO_QUALITY_EVRC_WB = 6; // 0x6 + field public static final int AUDIO_QUALITY_EVS_FB = 20; // 0x14 + field public static final int AUDIO_QUALITY_EVS_NB = 17; // 0x11 + field public static final int AUDIO_QUALITY_EVS_SWB = 19; // 0x13 + field public static final int AUDIO_QUALITY_EVS_WB = 18; // 0x12 + field public static final int AUDIO_QUALITY_G711A = 13; // 0xd + field public static final int AUDIO_QUALITY_G711AB = 15; // 0xf + field public static final int AUDIO_QUALITY_G711U = 11; // 0xb + field public static final int AUDIO_QUALITY_G722 = 14; // 0xe + field public static final int AUDIO_QUALITY_G723 = 12; // 0xc + field public static final int AUDIO_QUALITY_G729 = 16; // 0x10 + field public static final int AUDIO_QUALITY_GSM_EFR = 8; // 0x8 + field public static final int AUDIO_QUALITY_GSM_FR = 9; // 0x9 + field public static final int AUDIO_QUALITY_GSM_HR = 10; // 0xa + field public static final int AUDIO_QUALITY_NONE = 0; // 0x0 + field public static final int AUDIO_QUALITY_QCELP13K = 3; // 0x3 + field public static final android.os.Parcelable.Creator<android.telephony.ims.ImsStreamMediaProfile> CREATOR; + field public static final int DIRECTION_INACTIVE = 0; // 0x0 + field public static final int DIRECTION_INVALID = -1; // 0xffffffff + field public static final int DIRECTION_RECEIVE = 1; // 0x1 + field public static final int DIRECTION_SEND = 2; // 0x2 + field public static final int DIRECTION_SEND_RECEIVE = 3; // 0x3 + field public static final int RTT_MODE_DISABLED = 0; // 0x0 + field public static final int RTT_MODE_FULL = 1; // 0x1 + field public static final int VIDEO_QUALITY_NONE = 0; // 0x0 + field public static final int VIDEO_QUALITY_QCIF = 1; // 0x1 + field public static final int VIDEO_QUALITY_QVGA_LANDSCAPE = 2; // 0x2 + field public static final int VIDEO_QUALITY_QVGA_PORTRAIT = 4; // 0x4 + field public static final int VIDEO_QUALITY_VGA_LANDSCAPE = 8; // 0x8 + field public static final int VIDEO_QUALITY_VGA_PORTRAIT = 16; // 0x10 + } + + public final class ImsSuppServiceNotification implements android.os.Parcelable { + ctor public ImsSuppServiceNotification(int, int, int, int, java.lang.String, java.lang.String[]); + method public int describeContents(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator<android.telephony.ims.ImsSuppServiceNotification> CREATOR; + field public final int code; + field public final java.lang.String[] history; + field public final int index; + field public final int notificationType; + field public final java.lang.String number; + field public final int type; + } + + public class ImsUtListener { + method public void onSupplementaryServiceIndication(android.telephony.ims.ImsSsData); + method public void onUtConfigurationCallBarringQueried(int, android.telephony.ims.ImsSsInfo[]); + method public void onUtConfigurationCallForwardQueried(int, android.telephony.ims.ImsCallForwardInfo[]); + method public void onUtConfigurationCallWaitingQueried(int, android.telephony.ims.ImsSsInfo[]); + method public void onUtConfigurationQueried(int, android.os.Bundle); + method public void onUtConfigurationQueryFailed(int, android.telephony.ims.ImsReasonInfo); + method public void onUtConfigurationUpdateFailed(int, android.telephony.ims.ImsReasonInfo); + method public void onUtConfigurationUpdated(int); + } + + public abstract class ImsVideoCallProvider { + ctor public ImsVideoCallProvider(); + method public void changeCallDataUsage(long); + method public void changeCameraCapabilities(android.telecom.VideoProfile.CameraCapabilities); + method public void changePeerDimensions(int, int); + method public void changeVideoQuality(int); + method public void handleCallSessionEvent(int); + method public abstract void onRequestCallDataUsage(); + method public abstract void onRequestCameraCapabilities(); + method public abstract void onSendSessionModifyRequest(android.telecom.VideoProfile, android.telecom.VideoProfile); + method public abstract void onSendSessionModifyResponse(android.telecom.VideoProfile); + method public abstract void onSetCamera(java.lang.String); + method public void onSetCamera(java.lang.String, int); + method public abstract void onSetDeviceOrientation(int); + method public abstract void onSetDisplaySurface(android.view.Surface); + method public abstract void onSetPauseImage(android.net.Uri); + method public abstract void onSetPreviewSurface(android.view.Surface); + method public abstract void onSetZoom(float); + method public void receiveSessionModifyRequest(android.telecom.VideoProfile); + method public void receiveSessionModifyResponse(int, android.telecom.VideoProfile, android.telecom.VideoProfile); + } + +} + +package android.telephony.ims.feature { + + public final class CapabilityChangeRequest implements android.os.Parcelable { + method public void addCapabilitiesToDisableForTech(int, int); + method public void addCapabilitiesToEnableForTech(int, int); + method public int describeContents(); + method public java.util.List<android.telephony.ims.feature.CapabilityChangeRequest.CapabilityPair> getCapabilitiesToDisable(); + method public java.util.List<android.telephony.ims.feature.CapabilityChangeRequest.CapabilityPair> getCapabilitiesToEnable(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator<android.telephony.ims.feature.CapabilityChangeRequest> CREATOR; + } + + public static class CapabilityChangeRequest.CapabilityPair { + ctor public CapabilityChangeRequest.CapabilityPair(int, int); + method public int getCapability(); + method public int getRadioTech(); + } + + public abstract class ImsFeature { + ctor public ImsFeature(); + method public abstract void changeEnabledCapabilities(android.telephony.ims.feature.CapabilityChangeRequest, android.telephony.ims.feature.ImsFeature.CapabilityCallbackProxy); + method public abstract void onFeatureReady(); + method public abstract void onFeatureRemoved(); + method public final void setFeatureState(int); + field public static final int CAPABILITY_ERROR_GENERIC = -1; // 0xffffffff + field public static final int CAPABILITY_SUCCESS = 0; // 0x0 + field public static final int FEATURE_EMERGENCY_MMTEL = 0; // 0x0 + field public static final int FEATURE_MMTEL = 1; // 0x1 + field public static final int FEATURE_RCS = 2; // 0x2 + field public static final int STATE_INITIALIZING = 1; // 0x1 + field public static final int STATE_READY = 2; // 0x2 + field public static final int STATE_UNAVAILABLE = 0; // 0x0 + } + + protected static class ImsFeature.CapabilityCallbackProxy { + method public void onChangeCapabilityConfigurationError(int, int, int); + } + + public class MmTelFeature extends android.telephony.ims.feature.ImsFeature { + ctor public MmTelFeature(); + method public void changeEnabledCapabilities(android.telephony.ims.feature.CapabilityChangeRequest, android.telephony.ims.feature.ImsFeature.CapabilityCallbackProxy); + method public android.telephony.ims.ImsCallProfile createCallProfile(int, int); + method public android.telephony.ims.stub.ImsCallSessionImplBase createCallSession(android.telephony.ims.ImsCallProfile); + method public android.telephony.ims.stub.ImsEcbmImplBase getEcbm(); + method public android.telephony.ims.stub.ImsMultiEndpointImplBase getMultiEndpoint(); + method public android.telephony.ims.stub.ImsSmsImplBase getSmsImplementation(); + method public android.telephony.ims.stub.ImsUtImplBase getUt(); + method public final void notifyCapabilitiesStatusChanged(android.telephony.ims.feature.MmTelFeature.MmTelCapabilities); + method public final void notifyIncomingCall(android.telephony.ims.stub.ImsCallSessionImplBase, android.os.Bundle); + method public final void notifyVoiceMessageCountUpdate(int); + method public void onFeatureReady(); + method public void onFeatureRemoved(); + method public boolean queryCapabilityConfiguration(int, int); + method public final android.telephony.ims.feature.MmTelFeature.MmTelCapabilities queryCapabilityStatus(); + method public void setUiTtyMode(int, android.os.Message); + method public int shouldProcessCall(java.lang.String[]); + field public static final int PROCESS_CALL_CSFB = 1; // 0x1 + field public static final int PROCESS_CALL_EMERGENCY_CSFB = 2; // 0x2 + field public static final int PROCESS_CALL_IMS = 0; // 0x0 + } + + public static class MmTelFeature.MmTelCapabilities { + ctor public MmTelFeature.MmTelCapabilities(android.telephony.ims.feature.ImsFeature.Capabilities); + ctor public MmTelFeature.MmTelCapabilities(int); + method public final void addCapabilities(int); + method public final boolean isCapable(int); + method public final void removeCapabilities(int); + field public static final int CAPABILITY_TYPE_SMS = 8; // 0x8 + field public static final int CAPABILITY_TYPE_UT = 4; // 0x4 + field public static final int CAPABILITY_TYPE_VIDEO = 2; // 0x2 + field public static final int CAPABILITY_TYPE_VOICE = 1; // 0x1 + } + + public static abstract class MmTelFeature.MmTelCapabilities.MmTelCapability implements java.lang.annotation.Annotation { + } + + public static abstract class MmTelFeature.ProcessCallResult implements java.lang.annotation.Annotation { + } + + public class RcsFeature extends android.telephony.ims.feature.ImsFeature { + ctor public RcsFeature(); + method public void changeEnabledCapabilities(android.telephony.ims.feature.CapabilityChangeRequest, android.telephony.ims.feature.ImsFeature.CapabilityCallbackProxy); + method public void onFeatureReady(); + method public void onFeatureRemoved(); } } -package android.telephony.ims.internal.stub { +package android.telephony.ims.stub { + + public class ImsCallSessionImplBase implements java.lang.AutoCloseable { + ctor public ImsCallSessionImplBase(); + method public void accept(int, android.telephony.ims.ImsStreamMediaProfile); + method public void close(); + method public void extendToConference(java.lang.String[]); + method public java.lang.String getCallId(); + method public android.telephony.ims.ImsCallProfile getCallProfile(); + method public android.telephony.ims.ImsVideoCallProvider getImsVideoCallProvider(); + method public android.telephony.ims.ImsCallProfile getLocalCallProfile(); + method public java.lang.String getProperty(java.lang.String); + method public android.telephony.ims.ImsCallProfile getRemoteCallProfile(); + method public int getState(); + method public void hold(android.telephony.ims.ImsStreamMediaProfile); + method public void inviteParticipants(java.lang.String[]); + method public boolean isInCall(); + method public boolean isMultiparty(); + method public void merge(); + method public void reject(int); + method public void removeParticipants(java.lang.String[]); + method public void resume(android.telephony.ims.ImsStreamMediaProfile); + method public void sendDtmf(char, android.os.Message); + method public void sendRttMessage(java.lang.String); + method public void sendRttModifyRequest(android.telephony.ims.ImsCallProfile); + method public void sendRttModifyResponse(boolean); + method public void sendUssd(java.lang.String); + method public void setListener(android.telephony.ims.ImsCallSessionListener); + method public void setMute(boolean); + method public void start(java.lang.String, android.telephony.ims.ImsCallProfile); + method public void startConference(java.lang.String[], android.telephony.ims.ImsCallProfile); + method public void startDtmf(char); + method public void stopDtmf(); + method public void terminate(int); + method public void update(int, android.telephony.ims.ImsStreamMediaProfile); + field public static final int USSD_MODE_NOTIFY = 0; // 0x0 + field public static final int USSD_MODE_REQUEST = 1; // 0x1 + } + + public static class ImsCallSessionImplBase.State { + method public static java.lang.String toString(int); + field public static final int ESTABLISHED = 4; // 0x4 + field public static final int ESTABLISHING = 3; // 0x3 + field public static final int IDLE = 0; // 0x0 + field public static final int INITIATED = 1; // 0x1 + field public static final int INVALID = -1; // 0xffffffff + field public static final int NEGOTIATING = 2; // 0x2 + field public static final int REESTABLISHING = 6; // 0x6 + field public static final int RENEGOTIATING = 5; // 0x5 + field public static final int TERMINATED = 8; // 0x8 + field public static final int TERMINATING = 7; // 0x7 + } + + public class ImsConfigImplBase { + ctor public ImsConfigImplBase(); + method public int getConfigInt(int); + method public java.lang.String getConfigString(int); + method public final void notifyProvisionedValueChanged(int, int); + method public final void notifyProvisionedValueChanged(int, java.lang.String); + method public int setConfig(int, int); + method public int setConfig(int, java.lang.String); + field public static final int CONFIG_RESULT_FAILED = 1; // 0x1 + field public static final int CONFIG_RESULT_SUCCESS = 0; // 0x0 + field public static final int CONFIG_RESULT_UNKNOWN = -1; // 0xffffffff + } + + public class ImsEcbmImplBase { + ctor public ImsEcbmImplBase(); + method public final void enteredEcbm(); + method public void exitEmergencyCallbackMode(); + method public final void exitedEcbm(); + } + + public final class ImsFeatureConfiguration implements android.os.Parcelable { + ctor public ImsFeatureConfiguration(); + method public int describeContents(); + method public int[] getServiceFeatures(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator<android.telephony.ims.stub.ImsFeatureConfiguration> CREATOR; + } + + public static class ImsFeatureConfiguration.Builder { + ctor public ImsFeatureConfiguration.Builder(); + method public android.telephony.ims.stub.ImsFeatureConfiguration.Builder addFeature(int); + method public android.telephony.ims.stub.ImsFeatureConfiguration build(); + } - public class SmsImplBase { - ctor public SmsImplBase(); + public class ImsMultiEndpointImplBase { + ctor public ImsMultiEndpointImplBase(); + method public final void onImsExternalCallStateUpdate(java.util.List<android.telephony.ims.ImsExternalCallState>); + method public void requestImsExternalCallStateInfo(); + } + + public class ImsRegistrationImplBase { + ctor public ImsRegistrationImplBase(); + method public final void onDeregistered(android.telephony.ims.ImsReasonInfo); + method public final void onRegistered(int); + method public final void onRegistering(int); + method public final void onSubscriberAssociatedUriChanged(android.net.Uri[]); + method public final void onTechnologyChangeFailed(int, android.telephony.ims.ImsReasonInfo); + field public static final int REGISTRATION_TECH_IWLAN = 1; // 0x1 + field public static final int REGISTRATION_TECH_LTE = 0; // 0x0 + field public static final int REGISTRATION_TECH_NONE = -1; // 0xffffffff + } + + public class ImsSmsImplBase { + ctor public ImsSmsImplBase(); method public void acknowledgeSms(int, int, int); method public void acknowledgeSmsReport(int, int, int); method public java.lang.String getSmsFormat(); @@ -5103,6 +5794,29 @@ package android.telephony.ims.internal.stub { field public static final int STATUS_REPORT_STATUS_OK = 1; // 0x1 } + public class ImsUtImplBase { + ctor public ImsUtImplBase(); + method public void close(); + method public int queryCallBarring(int); + method public int queryCallBarringForServiceClass(int, int); + method public int queryCallForward(int, java.lang.String); + method public int queryCallWaiting(); + method public int queryClip(); + method public int queryClir(); + method public int queryColp(); + method public int queryColr(); + method public void setListener(android.telephony.ims.ImsUtListener); + method public int transact(android.os.Bundle); + method public int updateCallBarring(int, int, java.lang.String[]); + method public int updateCallBarringForServiceClass(int, int, java.lang.String[], int); + method public int updateCallForward(int, int, java.lang.String, int, int); + method public int updateCallWaiting(boolean, int); + method public int updateClip(boolean); + method public int updateClir(int); + method public int updateColp(boolean); + method public int updateColr(int); + } + } package android.telephony.mbms { diff --git a/api/test-current.txt b/api/test-current.txt index 4e8f904b96b7..d834cf7d62ab 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -565,6 +565,14 @@ package android.service.autofill { method public void apply(android.service.autofill.ValueFinder, android.widget.RemoteViews, int) throws java.lang.Exception; } + public final class DateTransformation extends android.service.autofill.InternalTransformation implements android.os.Parcelable android.service.autofill.Transformation { + method public void apply(android.service.autofill.ValueFinder, android.widget.RemoteViews, int) throws java.lang.Exception; + } + + public final class DateValueSanitizer extends android.service.autofill.InternalSanitizer implements android.os.Parcelable android.service.autofill.Sanitizer { + method public android.view.autofill.AutofillValue sanitize(android.view.autofill.AutofillValue); + } + public final class FillResponse implements android.os.Parcelable { method public int getFlags(); } @@ -599,7 +607,8 @@ package android.service.autofill { } public abstract interface ValueFinder { - method public abstract java.lang.String findByAutofillId(android.view.autofill.AutofillId); + method public default java.lang.String findByAutofillId(android.view.autofill.AutofillId); + method public abstract android.view.autofill.AutofillValue findRawValueByAutofillId(android.view.autofill.AutofillId); } } diff --git a/cmds/bootanimation/BootAnimation.cpp b/cmds/bootanimation/BootAnimation.cpp index d1af71d8886e..54785ca13185 100644 --- a/cmds/bootanimation/BootAnimation.cpp +++ b/cmds/bootanimation/BootAnimation.cpp @@ -66,9 +66,12 @@ namespace android { static const char OEM_BOOTANIMATION_FILE[] = "/oem/media/bootanimation.zip"; +static const char PRODUCT_BOOTANIMATION_FILE[] = "/product/media/bootanimation.zip"; static const char SYSTEM_BOOTANIMATION_FILE[] = "/system/media/bootanimation.zip"; +static const char PRODUCT_ENCRYPTED_BOOTANIMATION_FILE[] = "/product/media/bootanimation-encrypted.zip"; static const char SYSTEM_ENCRYPTED_BOOTANIMATION_FILE[] = "/system/media/bootanimation-encrypted.zip"; static const char OEM_SHUTDOWNANIMATION_FILE[] = "/oem/media/shutdownanimation.zip"; +static const char PRODUCT_SHUTDOWNANIMATION_FILE[] = "/product/media/shutdownanimation.zip"; static const char SYSTEM_SHUTDOWNANIMATION_FILE[] = "/system/media/shutdownanimation.zip"; static const char SYSTEM_DATA_DIR_PATH[] = "/data/system"; @@ -308,14 +311,20 @@ status_t BootAnimation::readyToRun() { bool encryptedAnimation = atoi(decrypt) != 0 || !strcmp("trigger_restart_min_framework", decrypt); - if (!mShuttingDown && encryptedAnimation && - (access(SYSTEM_ENCRYPTED_BOOTANIMATION_FILE, R_OK) == 0)) { - mZipFileName = SYSTEM_ENCRYPTED_BOOTANIMATION_FILE; - return NO_ERROR; + if (!mShuttingDown && encryptedAnimation) { + static const char* encryptedBootFiles[] = + {PRODUCT_ENCRYPTED_BOOTANIMATION_FILE, SYSTEM_ENCRYPTED_BOOTANIMATION_FILE}; + for (const char* f : encryptedBootFiles) { + if (access(f, R_OK) == 0) { + mZipFileName = f; + return NO_ERROR; + } + } } - static const char* bootFiles[] = {OEM_BOOTANIMATION_FILE, SYSTEM_BOOTANIMATION_FILE}; + static const char* bootFiles[] = + {PRODUCT_BOOTANIMATION_FILE, OEM_BOOTANIMATION_FILE, SYSTEM_BOOTANIMATION_FILE}; static const char* shutdownFiles[] = - {OEM_SHUTDOWNANIMATION_FILE, SYSTEM_SHUTDOWNANIMATION_FILE}; + {PRODUCT_SHUTDOWNANIMATION_FILE, OEM_SHUTDOWNANIMATION_FILE, SYSTEM_SHUTDOWNANIMATION_FILE}; for (const char* f : (!mShuttingDown ? bootFiles : shutdownFiles)) { if (access(f, R_OK) == 0) { diff --git a/cmds/incidentd/src/Privacy.cpp b/cmds/incidentd/src/Privacy.cpp index 44adaecfe97f..3f0e331c8b55 100644 --- a/cmds/incidentd/src/Privacy.cpp +++ b/cmds/incidentd/src/Privacy.cpp @@ -65,7 +65,7 @@ PrivacySpec::CheckPremission(const Privacy* privacy, const uint8_t defaultDest) bool PrivacySpec::RequireAll() const { return dest == android::os::DEST_LOCAL; } -PrivacySpec new_spec_from_args(int dest) +PrivacySpec PrivacySpec::new_spec(int dest) { switch (dest) { case android::os::DEST_AUTOMATIC: @@ -77,4 +77,7 @@ PrivacySpec new_spec_from_args(int dest) } } -PrivacySpec get_default_dropbox_spec() { return PrivacySpec(android::os::DEST_AUTOMATIC); }
\ No newline at end of file +PrivacySpec PrivacySpec::get_default_dropbox_spec() +{ + return PrivacySpec(android::os::DEST_AUTOMATIC); +} diff --git a/cmds/incidentd/src/Privacy.h b/cmds/incidentd/src/Privacy.h index 9e15ff43be06..4f3db678f765 100644 --- a/cmds/incidentd/src/Privacy.h +++ b/cmds/incidentd/src/Privacy.h @@ -65,8 +65,6 @@ public: const uint8_t dest; PrivacySpec() : dest(DEST_DEFAULT_VALUE) {} - PrivacySpec(uint8_t dest) : dest(dest) {} - bool operator<(const PrivacySpec& other) const; // check permission of a policy, if returns true, don't strip the data. @@ -74,9 +72,12 @@ public: // if returns true, no data need to be stripped. bool RequireAll() const; -}; -PrivacySpec new_spec_from_args(int dest); -PrivacySpec get_default_dropbox_spec(); + // Constructs spec using static methods below. + static PrivacySpec new_spec(int dest); + static PrivacySpec get_default_dropbox_spec(); +private: + PrivacySpec(uint8_t dest) : dest(dest) {} +}; #endif // PRIVACY_H diff --git a/cmds/incidentd/src/Reporter.cpp b/cmds/incidentd/src/Reporter.cpp index bd559d6980f1..b9f479bd683f 100644 --- a/cmds/incidentd/src/Reporter.cpp +++ b/cmds/incidentd/src/Reporter.cpp @@ -64,7 +64,8 @@ ReportRequest::ok() ReportRequestSet::ReportRequestSet() :mRequests(), mSections(), - mMainFd(-1) + mMainFd(-1), + mMainDest(-1) { } @@ -86,6 +87,12 @@ ReportRequestSet::setMainFd(int fd) mMainFd = fd; } +void +ReportRequestSet::setMainDest(int dest) +{ + mMainDest = dest; +} + bool ReportRequestSet::containsSection(int id) { return mSections.containsSection(id); @@ -125,12 +132,14 @@ Reporter::runReport() status_t err = NO_ERROR; bool needMainFd = false; int mainFd = -1; + int mainDest = -1; HeaderSection headers; // See if we need the main file for (ReportRequestSet::iterator it=batch.begin(); it!=batch.end(); it++) { if ((*it)->fd < 0 && mainFd < 0) { needMainFd = true; + mainDest = (*it)->args.dest(); break; } } @@ -154,6 +163,7 @@ Reporter::runReport() // Add to the set batch.setMainFd(mainFd); + batch.setMainDest(mainDest); } // Tell everyone that we're starting. diff --git a/cmds/incidentd/src/Reporter.h b/cmds/incidentd/src/Reporter.h index 2615c6202d3d..f30ecf0dd648 100644 --- a/cmds/incidentd/src/Reporter.h +++ b/cmds/incidentd/src/Reporter.h @@ -53,6 +53,7 @@ public: void add(const sp<ReportRequest>& request); void setMainFd(int fd); + void setMainDest(int dest); typedef vector<sp<ReportRequest>>::iterator iterator; @@ -61,10 +62,12 @@ public: int mainFd() { return mMainFd; } bool containsSection(int id); + int mainDest() { return mMainDest; } private: vector<sp<ReportRequest>> mRequests; IncidentReportArgs mSections; int mMainFd; + int mMainDest; }; // ================================================================================ diff --git a/cmds/incidentd/src/Section.cpp b/cmds/incidentd/src/Section.cpp index 0827785811b6..faeab87f8178 100644 --- a/cmds/incidentd/src/Section.cpp +++ b/cmds/incidentd/src/Section.cpp @@ -152,36 +152,40 @@ write_report_requests(const int id, const FdBuffer& buffer, ReportRequestSet* re // The streaming ones, group requests by spec in order to save unnecessary strip operations map<PrivacySpec, vector<sp<ReportRequest>>> requestsBySpec; - for (ReportRequestSet::iterator it = requests->begin(); it != requests->end(); it++) { + for (auto it = requests->begin(); it != requests->end(); it++) { sp<ReportRequest> request = *it; if (!request->ok() || !request->args.containsSection(id)) { continue; // skip invalid request } - PrivacySpec spec = new_spec_from_args(request->args.dest()); + PrivacySpec spec = PrivacySpec::new_spec(request->args.dest()); requestsBySpec[spec].push_back(request); } - for (map<PrivacySpec, vector<sp<ReportRequest>>>::iterator mit = requestsBySpec.begin(); mit != requestsBySpec.end(); mit++) { + for (auto mit = requestsBySpec.begin(); mit != requestsBySpec.end(); mit++) { PrivacySpec spec = mit->first; err = privacyBuffer.strip(spec); if (err != NO_ERROR) return err; // it means the privacyBuffer data is corrupted. if (privacyBuffer.size() == 0) continue; - for (vector<sp<ReportRequest>>::iterator it = mit->second.begin(); it != mit->second.end(); it++) { + for (auto it = mit->second.begin(); it != mit->second.end(); it++) { sp<ReportRequest> request = *it; err = write_section_header(request->fd, id, privacyBuffer.size()); if (err != NO_ERROR) { request->err = err; continue; } err = privacyBuffer.flush(request->fd); if (err != NO_ERROR) { request->err = err; continue; } writeable++; - ALOGD("Section %d flushed %zu bytes to fd %d with spec %d", id, privacyBuffer.size(), request->fd, spec.dest); + ALOGD("Section %d flushed %zu bytes to fd %d with spec %d", id, + privacyBuffer.size(), request->fd, spec.dest); } privacyBuffer.clear(); } // The dropbox file if (requests->mainFd() >= 0) { - err = privacyBuffer.strip(get_default_dropbox_spec()); + PrivacySpec spec = requests->mainDest() < 0 ? + PrivacySpec::get_default_dropbox_spec() : + PrivacySpec::new_spec(requests->mainDest()); + err = privacyBuffer.strip(spec); if (err != NO_ERROR) return err; // the buffer data is corrupted. if (privacyBuffer.size() == 0) goto DONE; @@ -190,7 +194,8 @@ write_report_requests(const int id, const FdBuffer& buffer, ReportRequestSet* re err = privacyBuffer.flush(requests->mainFd()); if (err != NO_ERROR) { requests->setMainFd(-1); goto DONE; } writeable++; - ALOGD("Section %d flushed %zu bytes to dropbox %d", id, privacyBuffer.size(), requests->mainFd()); + ALOGD("Section %d flushed %zu bytes to dropbox %d with spec %d", id, + privacyBuffer.size(), requests->mainFd(), spec.dest); } DONE: diff --git a/cmds/incidentd/tests/PrivacyBuffer_test.cpp b/cmds/incidentd/tests/PrivacyBuffer_test.cpp index 32b9e42d270c..c7bfe5555743 100644 --- a/cmds/incidentd/tests/PrivacyBuffer_test.cpp +++ b/cmds/incidentd/tests/PrivacyBuffer_test.cpp @@ -73,7 +73,7 @@ public: } void assertStrip(uint8_t dest, string expected, Privacy* policy) { - PrivacySpec spec(dest); + PrivacySpec spec = PrivacySpec::new_spec(dest); EncodedBuffer::iterator bufData = buffer.data(); PrivacyBuffer privacyBuf(policy, bufData); ASSERT_EQ(privacyBuf.strip(spec), NO_ERROR); @@ -224,7 +224,8 @@ TEST_F(PrivacyBufferTest, ClearAndStrip) { Privacy* list[] = { create_privacy(1, OTHER_TYPE, DEST_LOCAL), NULL }; EncodedBuffer::iterator bufData = buffer.data(); PrivacyBuffer privacyBuf(create_message_privacy(300, list), bufData); - PrivacySpec spec1(DEST_EXPLICIT), spec2(DEST_LOCAL); + PrivacySpec spec1 = PrivacySpec::new_spec(DEST_EXPLICIT); + PrivacySpec spec2 = PrivacySpec::new_spec(DEST_LOCAL); ASSERT_EQ(privacyBuf.strip(spec1), NO_ERROR); assertBuffer(privacyBuf, STRING_FIELD_0); diff --git a/cmds/statsd/Android.mk b/cmds/statsd/Android.mk index 565b092998af..eabbb96a392e 100644 --- a/cmds/statsd/Android.mk +++ b/cmds/statsd/Android.mk @@ -137,7 +137,7 @@ LOCAL_SHARED_LIBRARIES := $(statsd_common_shared_libraries) \ LOCAL_MODULE_CLASS := EXECUTABLES -#LOCAL_INIT_RC := statsd.rc +LOCAL_INIT_RC := statsd.rc include $(BUILD_EXECUTABLE) diff --git a/cmds/statsd/src/HashableDimensionKey.cpp b/cmds/statsd/src/HashableDimensionKey.cpp index 288ebe9dbbe9..857a6ddad0be 100644 --- a/cmds/statsd/src/HashableDimensionKey.cpp +++ b/cmds/statsd/src/HashableDimensionKey.cpp @@ -69,18 +69,17 @@ android::hash_t hashDimensionsValue(const DimensionsValue& value) { using std::string; - string HashableDimensionKey::toString() const { string flattened; DimensionsValueToString(getDimensionsValue(), &flattened); return flattened; } -bool compareDimensionsValue(const DimensionsValue& s1, const DimensionsValue& s2) { +bool EqualsTo(const DimensionsValue& s1, const DimensionsValue& s2) { if (s1.field() != s2.field()) { return false; } - if (s1.value_case() != s1.value_case()) { + if (s1.value_case() != s2.value_case()) { return false; } switch (s1.value_case()) { @@ -102,8 +101,8 @@ bool compareDimensionsValue(const DimensionsValue& s1, const DimensionsValue& s2 } bool allMatched = true; for (int i = 0; allMatched && i < s1.value_tuple().dimensions_value_size(); ++i) { - allMatched &= compareDimensionsValue(s1.value_tuple().dimensions_value(i), - s2.value_tuple().dimensions_value(i)); + allMatched &= EqualsTo(s1.value_tuple().dimensions_value(i), + s2.value_tuple().dimensions_value(i)); } return allMatched; } @@ -113,12 +112,54 @@ bool compareDimensionsValue(const DimensionsValue& s1, const DimensionsValue& s2 } } +bool LessThan(const DimensionsValue& s1, const DimensionsValue& s2) { + if (s1.field() != s2.field()) { + return s1.field() < s2.field(); + } + if (s1.value_case() != s2.value_case()) { + return s1.value_case() < s2.value_case(); + } + switch (s1.value_case()) { + case DimensionsValue::ValueCase::kValueStr: + return s1.value_str() < s2.value_str(); + case DimensionsValue::ValueCase::kValueInt: + return s1.value_int() < s2.value_int(); + case DimensionsValue::ValueCase::kValueLong: + return s1.value_long() < s2.value_long(); + case DimensionsValue::ValueCase::kValueBool: + return (int)s1.value_bool() < (int)s2.value_bool(); + case DimensionsValue::ValueCase::kValueFloat: + return s1.value_float() < s2.value_float(); + case DimensionsValue::ValueCase::kValueTuple: + { + if (s1.value_tuple().dimensions_value_size() != + s2.value_tuple().dimensions_value_size()) { + return s1.value_tuple().dimensions_value_size() < + s2.value_tuple().dimensions_value_size(); + } + for (int i = 0; i < s1.value_tuple().dimensions_value_size(); ++i) { + if (EqualsTo(s1.value_tuple().dimensions_value(i), + s2.value_tuple().dimensions_value(i))) { + continue; + } else { + return LessThan(s1.value_tuple().dimensions_value(i), + s2.value_tuple().dimensions_value(i)); + } + } + return false; + } + case DimensionsValue::ValueCase::VALUE_NOT_SET: + default: + return false; + } +} + bool HashableDimensionKey::operator==(const HashableDimensionKey& that) const { - return compareDimensionsValue(getDimensionsValue(), that.getDimensionsValue()); + return EqualsTo(getDimensionsValue(), that.getDimensionsValue()); }; bool HashableDimensionKey::operator<(const HashableDimensionKey& that) const { - return toString().compare(that.toString()) < 0; + return LessThan(getDimensionsValue(), that.getDimensionsValue()); }; } // namespace statsd diff --git a/cmds/statsd/src/StatsLogProcessor.cpp b/cmds/statsd/src/StatsLogProcessor.cpp index edc9f2ce6b3c..a4066aa7bca5 100644 --- a/cmds/statsd/src/StatsLogProcessor.cpp +++ b/cmds/statsd/src/StatsLogProcessor.cpp @@ -91,23 +91,22 @@ void StatsLogProcessor::onAnomalyAlarmFired( } void StatsLogProcessor::mapIsolatedUidToHostUidIfNecessaryLocked(LogEvent* event) const { - std::vector<Field> uidFields; + std::set<Field, FieldCmp> uidFields; if (android::util::kAtomsWithAttributionChain.find(event->GetTagId()) != android::util::kAtomsWithAttributionChain.end()) { - findFields( - event->getFieldValueMap(), - buildAttributionUidFieldMatcher(event->GetTagId(), Position::ANY), - &uidFields); + FieldMatcher matcher; + buildAttributionUidFieldMatcher(event->GetTagId(), Position::ANY, &matcher); + findFields(event->getFieldValueMap(), matcher, &uidFields); } else if (android::util::kAtomsWithUidField.find(event->GetTagId()) != android::util::kAtomsWithUidField.end()) { - findFields( - event->getFieldValueMap(), - buildSimpleAtomFieldMatcher(event->GetTagId(), 1 /* uid is always the 1st field. */), - &uidFields); + FieldMatcher matcher; + buildSimpleAtomFieldMatcher( + event->GetTagId(), 1 /* uid is always the 1st field. */, &matcher); + findFields(event->getFieldValueMap(), matcher, &uidFields); } - for (size_t i = 0; i < uidFields.size(); ++i) { - DimensionsValue* value = event->findFieldValueOrNull(uidFields[i]); + for (const auto& uidField : uidFields) { + DimensionsValue* value = event->findFieldValueOrNull(uidField); if (value != nullptr && value->value_case() == DimensionsValue::ValueCase::kValueInt) { const int uid = mUidMap->getHostUidOrSelf(value->value_int()); value->set_value_int(uid); @@ -196,6 +195,14 @@ size_t StatsLogProcessor::GetMetricsSize(const ConfigKey& key) const { return it->second->byteSize(); } +void StatsLogProcessor::dumpStates(FILE* out, bool verbose) { + std::lock_guard<std::mutex> lock(mMetricsMutex); + fprintf(out, "MetricsManager count: %lu\n", (unsigned long)mMetricsManagers.size()); + for (auto metricsManager : mMetricsManagers) { + metricsManager.second->dumpStates(out, verbose); + } +} + void StatsLogProcessor::onDumpReport(const ConfigKey& key, const uint64_t& dumpTimeStampNs, ConfigMetricsReportList* report) { std::lock_guard<std::mutex> lock(mMetricsMutex); diff --git a/cmds/statsd/src/StatsLogProcessor.h b/cmds/statsd/src/StatsLogProcessor.h index fb85aa837242..c19ff63e2858 100644 --- a/cmds/statsd/src/StatsLogProcessor.h +++ b/cmds/statsd/src/StatsLogProcessor.h @@ -61,6 +61,8 @@ public: return mUidMap; } + void dumpStates(FILE* out, bool verbose); + private: mutable mutex mMetricsMutex; diff --git a/cmds/statsd/src/StatsService.cpp b/cmds/statsd/src/StatsService.cpp index 31994e1a92d1..f545bb0738e9 100644 --- a/cmds/statsd/src/StatsService.cpp +++ b/cmds/statsd/src/StatsService.cpp @@ -175,8 +175,13 @@ status_t StatsService::dump(int fd, const Vector<String16>& args) { return NO_MEMORY; // the fd is already open } + bool verbose = false; + if (args.size() > 0 && !args[0].compare(String16("-v"))) { + verbose = true; + } + // TODO: Proto format for incident reports - dump_impl(out); + dump_impl(out, verbose); fclose(out); return NO_ERROR; @@ -185,9 +190,9 @@ status_t StatsService::dump(int fd, const Vector<String16>& args) { /** * Write debugging data about statsd in text format. */ -void StatsService::dump_impl(FILE* out) { - mConfigManager->Dump(out); +void StatsService::dump_impl(FILE* out, bool verbose) { StatsdStats::getInstance().dumpStats(out); + mProcessor->dumpStates(out, verbose); } /** diff --git a/cmds/statsd/src/StatsService.h b/cmds/statsd/src/StatsService.h index ba6bd2499820..be20893994f1 100644 --- a/cmds/statsd/src/StatsService.h +++ b/cmds/statsd/src/StatsService.h @@ -140,7 +140,7 @@ private: /** * Text output of dumpsys. */ - void dump_impl(FILE* out); + void dump_impl(FILE* out, bool verbose); /** * Print usage information for the commands diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto index 27fa67212af8..4e570a684a06 100644 --- a/cmds/statsd/src/atoms.proto +++ b/cmds/statsd/src/atoms.proto @@ -93,6 +93,8 @@ message Atom { WifiMulticastLockStateChanged wifi_multicast_lock_state_changed = 53; LmkStateChanged lmk_state_changed = 54; AppStartMemoryStateCaptured app_start_memory_state_captured = 55; + ShutdownSequenceReported shutdown_sequence_reported = 56; + BootSequenceReported boot_sequence_reported = 57; // TODO: Reorder the numbering so that the most frequent occur events occur in the first 15. } @@ -669,6 +671,56 @@ message WifiMulticastLockStateChanged { } /** + * Logs shutdown reason and duration on next boot. + * + * Logged from: + * frameworks/base/core/java/com/android/server/BootReceiver.java + */ +message ShutdownSequenceReported { + // True if shutdown is for a reboot. Default: false if we do not know. + optional bool reboot = 1; + + // Reason for shutdown. Eg: userrequested. Default: "<EMPTY>". + optional string reason = 2; + + // Beginning of shutdown time in ms using wall clock time since unix epoch. + // Default: 0 if no start time received. + optional int64 start_time_ms = 3; + + // Duration of shutdown in ms. Default: 0 if no duration received. + optional int64 duration_ms = 4; +} + + +/** + * Logs boot reason and duration. + * + * Logged from: + * system/core/bootstat/bootstat.cpp + */ +message BootSequenceReported { + // Reason for bootloader boot. Eg. reboot. See bootstat.cpp for larger list + // Default: "<EMPTY>" if not available. + optional string bootloader_reason = 1; + + // Reason for system boot. Eg. bootloader, reboot,userrequested + // Default: "<EMPTY>" if not available. + optional string system_reason = 2; + + // End of boot time in ms from unix epoch using system wall clock. + optional int64 end_time_ms = 3; + + // Total boot duration in ms. + optional int64 total_duration_ms = 4; + + // Bootloader duration in ms. + optional int64 bootloader_duration_ms = 5; + + // Time since last boot in ms. Default: 0 if not available. + optional int64 time_since_last_boot = 6; +} + +/** * Logs phone signal strength changes. * * Logged from: @@ -1214,8 +1266,14 @@ message AppStartMemoryStateCaptured { // # of major page-faults optional int64 pgmajfault = 5; - // RSS+CACHE(+SWAP) - optional int64 usage_in_bytes = 6; + // RSS + optional int64 rss_in_bytes = 6; + + // CACHE + optional int64 cache_in_bytes = 7; + + // SWAP + optional int64 swap_in_bytes = 8; } /* @@ -1237,8 +1295,14 @@ message ProcessMemoryState { // # of major page-faults optional int64 pgmajfault = 5; - // RSS+CACHE(+SWAP) - optional int64 usage_in_bytes = 6; + // RSS + optional int64 rss_in_bytes = 6; + + // CACHE + optional int64 cache_in_bytes = 7; + + // SWAP + optional int64 swap_in_bytes = 8; } /* @@ -1277,8 +1341,14 @@ message LmkKillOccurred { // # of major page-faults optional int64 pgmajfault = 5; - // RSS+CACHE(+SWAP) - optional int64 usage_in_bytes = 6; + // RSS + optional int64 rss_in_bytes = 6; + + // CACHE + optional int64 cache_in_bytes = 7; + + // SWAP + optional int64 swap_in_bytes = 8; } /* diff --git a/cmds/statsd/src/condition/SimpleConditionTracker.cpp b/cmds/statsd/src/condition/SimpleConditionTracker.cpp index 7a1bb0c71373..5cfc349ea46a 100644 --- a/cmds/statsd/src/condition/SimpleConditionTracker.cpp +++ b/cmds/statsd/src/condition/SimpleConditionTracker.cpp @@ -289,7 +289,8 @@ void SimpleConditionTracker::evaluateCondition(const LogEvent& event, } // outputKey is the output values. e.g, uid:1234 - const std::vector<DimensionsValue> outputValues = getDimensionKeys(event, mOutputDimensions); + std::vector<DimensionsValue> outputValues; + getDimensionKeys(event, mOutputDimensions, &outputValues); if (outputValues.size() == 0) { // The original implementation would generate an empty string dimension hash when condition // is not sliced. diff --git a/cmds/statsd/src/condition/condition_util.cpp b/cmds/statsd/src/condition/condition_util.cpp index ddfb8d12a335..3b2d480b3ebf 100644 --- a/cmds/statsd/src/condition/condition_util.cpp +++ b/cmds/statsd/src/condition/condition_util.cpp @@ -116,28 +116,30 @@ void OrBooleanVector(const std::vector<bool>& ref, vector<bool> * ored) { } } -void getFieldsFromFieldMatcher(const FieldMatcher& matcher, const Field& parentField, - std::vector<Field> *allFields) { - Field newParent = parentField; - Field* leaf = getSingleLeaf(&newParent); - leaf->set_field(matcher.field()); +void getFieldsFromFieldMatcher(const FieldMatcher& matcher, Field* rootField, Field* leafField, + std::vector<Field> *allFields) { if (matcher.child_size() == 0) { - allFields->push_back(newParent); + allFields->push_back(*rootField); return; } for (int i = 0; i < matcher.child_size(); ++i) { - leaf->add_child(); - getFieldsFromFieldMatcher(matcher.child(i), newParent, allFields); + Field* newLeafField = leafField->add_child(); + newLeafField->set_field(matcher.child(i).field()); + getFieldsFromFieldMatcher(matcher.child(i), rootField, newLeafField, allFields); } } void getFieldsFromFieldMatcher(const FieldMatcher& matcher, std::vector<Field> *allFields) { - Field parentField; - getFieldsFromFieldMatcher(matcher, parentField, allFields); + if (!matcher.has_field()) { + return; + } + Field rootField; + rootField.set_field(matcher.field()); + getFieldsFromFieldMatcher(matcher, &rootField, &rootField, allFields); } void flattenValueLeaves(const DimensionsValue& value, - std::vector<DimensionsValue> *allLaves) { + std::vector<const DimensionsValue*> *allLaves) { switch (value.value_case()) { case DimensionsValue::ValueCase::kValueStr: case DimensionsValue::ValueCase::kValueInt: @@ -145,7 +147,7 @@ void flattenValueLeaves(const DimensionsValue& value, case DimensionsValue::ValueCase::kValueBool: case DimensionsValue::ValueCase::kValueFloat: case DimensionsValue::ValueCase::VALUE_NOT_SET: - allLaves->push_back(value); + allLaves->push_back(&value); break; case DimensionsValue::ValueCase::kValueTuple: for (int i = 0; i < value.value_tuple().dimensions_value_size(); ++i) { @@ -155,45 +157,44 @@ void flattenValueLeaves(const DimensionsValue& value, } } -std::vector<HashableDimensionKey> getDimensionKeysForCondition( - const LogEvent& event, const MetricConditionLink& link) { +void getDimensionKeysForCondition( + const LogEvent& event, const MetricConditionLink& link, + std::vector<HashableDimensionKey> *hashableDimensionKeys) { std::vector<Field> whatFields; getFieldsFromFieldMatcher(link.fields_in_what(), &whatFields); std::vector<Field> conditionFields; getFieldsFromFieldMatcher(link.fields_in_condition(), &conditionFields); - std::vector<HashableDimensionKey> hashableDimensionKeys; - // TODO(yanglu): here we could simplify the logic to get the leaf value node in what and // directly construct the full condition value tree. - std::vector<DimensionsValue> whatValues = getDimensionKeys(event, link.fields_in_what()); + std::vector<DimensionsValue> whatValues; + getDimensionKeys(event, link.fields_in_what(), &whatValues); for (size_t i = 0; i < whatValues.size(); ++i) { - std::vector<DimensionsValue> whatLeaves; + std::vector<const DimensionsValue*> whatLeaves; flattenValueLeaves(whatValues[i], &whatLeaves); if (whatLeaves.size() != whatFields.size() || whatLeaves.size() != conditionFields.size()) { ALOGE("Dimensions between what and condition not equal."); - return hashableDimensionKeys; + return; } FieldValueMap conditionValueMap; for (size_t j = 0; j < whatLeaves.size(); ++j) { - if (!setFieldInLeafValueProto(conditionFields[j], &whatLeaves[j])) { + DimensionsValue* conditionValue = &conditionValueMap[conditionFields[j]]; + *conditionValue = *whatLeaves[i]; + if (!setFieldInLeafValueProto(conditionFields[j], conditionValue)) { ALOGE("Not able to reset the field for condition leaf value."); - return hashableDimensionKeys; + return; } - conditionValueMap.insert(std::make_pair(conditionFields[j], whatLeaves[j])); } - std::vector<DimensionsValue> conditionValues; - findDimensionsValues(conditionValueMap, link.fields_in_condition(), &conditionValues); - if (conditionValues.size() != 1) { + std::vector<DimensionsValue> conditionValueTrees; + findDimensionsValues(conditionValueMap, link.fields_in_condition(), &conditionValueTrees); + if (conditionValueTrees.size() != 1) { ALOGE("Not able to find unambiguous field value in condition atom."); continue; } - hashableDimensionKeys.push_back(HashableDimensionKey(conditionValues[0])); + hashableDimensionKeys->push_back(HashableDimensionKey(conditionValueTrees[0])); } - - return hashableDimensionKeys; } } // namespace statsd diff --git a/cmds/statsd/src/condition/condition_util.h b/cmds/statsd/src/condition/condition_util.h index 598027b7e366..a7288beb69ca 100644 --- a/cmds/statsd/src/condition/condition_util.h +++ b/cmds/statsd/src/condition/condition_util.h @@ -40,8 +40,9 @@ ConditionState evaluateCombinationCondition(const std::vector<int>& children, const LogicalOperation& operation, const std::vector<ConditionState>& conditionCache); -std::vector<HashableDimensionKey> getDimensionKeysForCondition( - const LogEvent& event, const MetricConditionLink& link); +void getDimensionKeysForCondition( + const LogEvent& event, const MetricConditionLink& link, + std::vector<HashableDimensionKey> *dimensionKeys); } // namespace statsd } // namespace os } // namespace android diff --git a/cmds/statsd/src/dimension.cpp b/cmds/statsd/src/dimension.cpp index bb7a044fa8ce..04445ca0e230 100644 --- a/cmds/statsd/src/dimension.cpp +++ b/cmds/statsd/src/dimension.cpp @@ -38,7 +38,7 @@ DimensionsValue getSingleLeafValue(const DimensionsValue& value) { return *leafValue; } -void appendLeafNodeToParent(const Field& field, +void appendLeafNodeToTree(const Field& field, const DimensionsValue& value, DimensionsValue* parentValue) { if (field.child_size() <= 0) { @@ -58,24 +58,24 @@ void appendLeafNodeToParent(const Field& field, parentValue->mutable_value_tuple()->add_dimensions_value(); idx = parentValue->mutable_value_tuple()->dimensions_value_size() - 1; } - appendLeafNodeToParent( + appendLeafNodeToTree( field.child(0), value, parentValue->mutable_value_tuple()->mutable_dimensions_value(idx)); } -void addNodeToRootDimensionsValues(const Field& field, - const DimensionsValue& node, - std::vector<DimensionsValue>* rootValues) { - if (rootValues == nullptr) { +void appendLeafNodeToTrees(const Field& field, + const DimensionsValue& node, + std::vector<DimensionsValue>* rootTrees) { + if (rootTrees == nullptr) { return; } - if (rootValues->empty()) { - DimensionsValue rootValue; - appendLeafNodeToParent(field, node, &rootValue); - rootValues->push_back(rootValue); + if (rootTrees->empty()) { + DimensionsValue tree; + appendLeafNodeToTree(field, node, &tree); + rootTrees->push_back(tree); } else { - for (size_t i = 0; i < rootValues->size(); ++i) { - appendLeafNodeToParent(field, node, &rootValues->at(i)); + for (size_t i = 0; i < rootTrees->size(); ++i) { + appendLeafNodeToTree(field, node, &rootTrees->at(i)); } } } @@ -85,22 +85,25 @@ namespace { void findDimensionsValues( const FieldValueMap& fieldValueMap, const FieldMatcher& matcher, - const Field& field, + Field* rootField, + Field* leafField, std::vector<DimensionsValue>* rootDimensionsValues); void findNonRepeatedDimensionsValues( const FieldValueMap& fieldValueMap, const FieldMatcher& matcher, - const Field& field, + Field* rootField, + Field* leafField, std::vector<DimensionsValue>* rootValues) { if (matcher.child_size() > 0) { + Field* newLeafField = leafField->add_child(); for (const auto& childMatcher : matcher.child()) { - Field childField = field; - appendLeaf(&childField, childMatcher.field()); - findDimensionsValues(fieldValueMap, childMatcher, childField, rootValues); + newLeafField->set_field(childMatcher.field()); + findDimensionsValues(fieldValueMap, childMatcher, rootField, newLeafField, rootValues); } + leafField->clear_child(); } else { - auto ret = fieldValueMap.equal_range(field); + auto ret = fieldValueMap.equal_range(*rootField); int found = 0; for (auto it = ret.first; it != ret.second; ++it) { found++; @@ -113,40 +116,43 @@ void findNonRepeatedDimensionsValues( ALOGE("Found multiple values for optional field."); return; } - addNodeToRootDimensionsValues(field, ret.first->second, rootValues); + appendLeafNodeToTrees(*rootField, ret.first->second, rootValues); } } void findRepeatedDimensionsValues(const FieldValueMap& fieldValueMap, const FieldMatcher& matcher, - const Field& field, + Field* rootField, + Field* leafField, std::vector<DimensionsValue>* rootValues) { if (matcher.position() == Position::FIRST) { - Field first_field = field; - setPositionForLeaf(&first_field, 0); - findNonRepeatedDimensionsValues(fieldValueMap, matcher, first_field, rootValues); + leafField->set_position_index(0); + findNonRepeatedDimensionsValues(fieldValueMap, matcher, rootField, leafField, rootValues); + leafField->clear_position_index(); } else { - auto itLower = fieldValueMap.lower_bound(field); + auto itLower = fieldValueMap.lower_bound(*rootField); if (itLower == fieldValueMap.end()) { return; } - Field next_field = field; - getNextField(&next_field); - auto itUpper = fieldValueMap.lower_bound(next_field); + const int leafFieldNum = leafField->field(); + leafField->set_field(leafFieldNum + 1); + auto itUpper = fieldValueMap.lower_bound(*rootField); + // Resets the field number. + leafField->set_field(leafFieldNum); switch (matcher.position()) { case Position::LAST: { itUpper--; if (itUpper != fieldValueMap.end()) { - Field last_field = field; - int last_index = getPositionByReferenceField(field, itUpper->first); + int last_index = getPositionByReferenceField(*rootField, itUpper->first); if (last_index < 0) { return; } - setPositionForLeaf(&last_field, last_index); + leafField->set_position_index(last_index); findNonRepeatedDimensionsValues( - fieldValueMap, matcher, last_field, rootValues); + fieldValueMap, matcher, rootField, leafField, rootValues); + leafField->clear_position_index(); } } break; @@ -154,20 +160,20 @@ void findRepeatedDimensionsValues(const FieldValueMap& fieldValueMap, { std::set<int> indexes; for (auto it = itLower; it != itUpper; ++it) { - int index = getPositionByReferenceField(field, it->first); + int index = getPositionByReferenceField(*rootField, it->first); if (index >= 0) { indexes.insert(index); } } if (!indexes.empty()) { - Field any_field = field; std::vector<DimensionsValue> allValues; for (const int index : indexes) { - setPositionForLeaf(&any_field, index); + leafField->set_position_index(index); std::vector<DimensionsValue> newValues = *rootValues; findNonRepeatedDimensionsValues( - fieldValueMap, matcher, any_field, &newValues); + fieldValueMap, matcher, rootField, leafField, &newValues); allValues.insert(allValues.end(), newValues.begin(), newValues.end()); + leafField->clear_position_index(); } rootValues->clear(); rootValues->insert(rootValues->end(), allValues.begin(), allValues.end()); @@ -183,12 +189,15 @@ void findRepeatedDimensionsValues(const FieldValueMap& fieldValueMap, void findDimensionsValues( const FieldValueMap& fieldValueMap, const FieldMatcher& matcher, - const Field& field, + Field* rootField, + Field* leafField, std::vector<DimensionsValue>* rootDimensionsValues) { if (!matcher.has_position()) { - findNonRepeatedDimensionsValues(fieldValueMap, matcher, field, rootDimensionsValues); + findNonRepeatedDimensionsValues(fieldValueMap, matcher, rootField, leafField, + rootDimensionsValues); } else { - findRepeatedDimensionsValues(fieldValueMap, matcher, field, rootDimensionsValues); + findRepeatedDimensionsValues(fieldValueMap, matcher, rootField, leafField, + rootDimensionsValues); } } @@ -198,56 +207,49 @@ void findDimensionsValues( const FieldValueMap& fieldValueMap, const FieldMatcher& matcher, std::vector<DimensionsValue>* rootDimensionsValues) { - findDimensionsValues(fieldValueMap, matcher, - buildSimpleAtomField(matcher.field()), rootDimensionsValues); + Field rootField; + buildSimpleAtomField(matcher.field(), &rootField); + findDimensionsValues(fieldValueMap, matcher, &rootField, &rootField, rootDimensionsValues); } -FieldMatcher buildSimpleAtomFieldMatcher(const int tagId) { - FieldMatcher matcher; - matcher.set_field(tagId); - return matcher; +void buildSimpleAtomFieldMatcher(const int tagId, FieldMatcher* matcher) { + matcher->set_field(tagId); } -FieldMatcher buildSimpleAtomFieldMatcher(const int tagId, const int atomFieldNum) { - FieldMatcher matcher; - matcher.set_field(tagId); - matcher.add_child()->set_field(atomFieldNum); - return matcher; +void buildSimpleAtomFieldMatcher(const int tagId, const int fieldNum, FieldMatcher* matcher) { + matcher->set_field(tagId); + matcher->add_child()->set_field(fieldNum); } constexpr int ATTRIBUTION_FIELD_NUM_IN_ATOM_PROTO = 1; constexpr int UID_FIELD_NUM_IN_ATTRIBUTION_NODE_PROTO = 1; constexpr int TAG_FIELD_NUM_IN_ATTRIBUTION_NODE_PROTO = 2; -FieldMatcher buildAttributionUidFieldMatcher(const int tagId, const Position position) { - FieldMatcher matcher; - matcher.set_field(tagId); - auto child = matcher.add_child(); +void buildAttributionUidFieldMatcher(const int tagId, const Position position, + FieldMatcher* matcher) { + matcher->set_field(tagId); + auto child = matcher->add_child(); child->set_field(ATTRIBUTION_FIELD_NUM_IN_ATOM_PROTO); child->set_position(position); child->add_child()->set_field(UID_FIELD_NUM_IN_ATTRIBUTION_NODE_PROTO); - return matcher; } -FieldMatcher buildAttributionTagFieldMatcher(const int tagId, const Position position) { - FieldMatcher matcher; - matcher.set_field(tagId); - FieldMatcher* child = matcher.add_child(); +void buildAttributionTagFieldMatcher(const int tagId, const Position position, + FieldMatcher* matcher) { + matcher->set_field(tagId); + FieldMatcher* child = matcher->add_child(); child->set_field(ATTRIBUTION_FIELD_NUM_IN_ATOM_PROTO); child->set_position(position); child->add_child()->set_field(TAG_FIELD_NUM_IN_ATTRIBUTION_NODE_PROTO); - return matcher; } -FieldMatcher buildAttributionFieldMatcher(const int tagId, const Position position) { - FieldMatcher matcher; - matcher.set_field(tagId); - FieldMatcher* child = matcher.add_child(); +void buildAttributionFieldMatcher(const int tagId, const Position position, FieldMatcher* matcher) { + matcher->set_field(tagId); + FieldMatcher* child = matcher->add_child(); child->set_field(ATTRIBUTION_FIELD_NUM_IN_ATOM_PROTO); child->set_position(position); child->add_child()->set_field(UID_FIELD_NUM_IN_ATTRIBUTION_NODE_PROTO); child->add_child()->set_field(TAG_FIELD_NUM_IN_ATTRIBUTION_NODE_PROTO); - return matcher; } void DimensionsValueToString(const DimensionsValue& value, std::string *flattened) { @@ -284,28 +286,6 @@ void DimensionsValueToString(const DimensionsValue& value, std::string *flattene } } -void getDimensionsValueLeafNodes( - const DimensionsValue& value, std::vector<DimensionsValue> *leafNodes) { - switch (value.value_case()) { - case DimensionsValue::ValueCase::kValueStr: - case DimensionsValue::ValueCase::kValueInt: - case DimensionsValue::ValueCase::kValueLong: - case DimensionsValue::ValueCase::kValueBool: - case DimensionsValue::ValueCase::kValueFloat: - leafNodes->push_back(value); - break; - case DimensionsValue::ValueCase::kValueTuple: - for (int i = 0; i < value.value_tuple().dimensions_value_size(); ++i) { - getDimensionsValueLeafNodes(value.value_tuple().dimensions_value(i), leafNodes); - } - break; - case DimensionsValue::ValueCase::VALUE_NOT_SET: - break; - default: - break; - } -} - std::string DimensionsValueToString(const DimensionsValue& value) { std::string flatten; DimensionsValueToString(value, &flatten); diff --git a/cmds/statsd/src/dimension.h b/cmds/statsd/src/dimension.h index d0f96a2abe6b..e900c5e87227 100644 --- a/cmds/statsd/src/dimension.h +++ b/cmds/statsd/src/dimension.h @@ -33,8 +33,7 @@ const DimensionsValue* getSingleLeafValue(const DimensionsValue* value); DimensionsValue getSingleLeafValue(const DimensionsValue& value); // Appends the leaf node to the parent tree. -void appendLeafNodeToParent(const Field& field, const DimensionsValue& value, - DimensionsValue* parentValue); +void appendLeafNodeToTree(const Field& field, const DimensionsValue& value, DimensionsValue* tree); // Constructs the DimensionsValue protos from the FieldMatcher. Each DimensionsValue proto // represents a tree. When the input proto has repeated fields and the input "dimensions" wants @@ -45,13 +44,16 @@ void findDimensionsValues( std::vector<DimensionsValue>* rootDimensionsValues); // Utils to build FieldMatcher proto for simple one-depth atoms. -FieldMatcher buildSimpleAtomFieldMatcher(const int tagId, const int atomFieldNum); -FieldMatcher buildSimpleAtomFieldMatcher(const int tagId); +void buildSimpleAtomFieldMatcher(const int tagId, const int atomFieldNum, FieldMatcher* matcher); +void buildSimpleAtomFieldMatcher(const int tagId, FieldMatcher* matcher); // Utils to build FieldMatcher proto for attribution nodes. -FieldMatcher buildAttributionUidFieldMatcher(const int tagId, const Position position); -FieldMatcher buildAttributionTagFieldMatcher(const int tagId, const Position position); -FieldMatcher buildAttributionFieldMatcher(const int tagId, const Position position); +void buildAttributionUidFieldMatcher(const int tagId, const Position position, + FieldMatcher* matcher); +void buildAttributionTagFieldMatcher(const int tagId, const Position position, + FieldMatcher* matcher); +void buildAttributionFieldMatcher(const int tagId, const Position position, + FieldMatcher* matcher); // Utils to print pretty string for DimensionsValue proto. std::string DimensionsValueToString(const DimensionsValue& value); diff --git a/cmds/statsd/src/field_util.cpp b/cmds/statsd/src/field_util.cpp index 4ff4f74f7979..acf64fe12e6d 100644 --- a/cmds/statsd/src/field_util.cpp +++ b/cmds/statsd/src/field_util.cpp @@ -102,24 +102,13 @@ bool setFieldInLeafValueProto(const Field &field, DimensionsValue* leafValue) { } } -Field buildAtomField(const int tagId, const Field &atomField) { - Field field; - *field.add_child() = atomField; - field.set_field(tagId); - return field; +void buildSimpleAtomField(const int tagId, const int atomFieldNum, Field *field) { + field->set_field(tagId); + field->add_child()->set_field(atomFieldNum); } -Field buildSimpleAtomField(const int tagId, const int atomFieldNum) { - Field field; - field.set_field(tagId); - field.add_child()->set_field(atomFieldNum); - return field; -} - -Field buildSimpleAtomField(const int tagId) { - Field field; - field.set_field(tagId); - return field; +void buildSimpleAtomField(const int tagId, Field *field) { + field->set_field(tagId); } void appendLeaf(Field *parent, int node_field_num) { @@ -145,18 +134,6 @@ void appendLeaf(Field *parent, int node_field_num, int position) { } } - -void getNextField(Field* field) { - if (field->child_size() <= 0) { - field->set_field(field->field() + 1); - return; - } - if (field->child_size() != 1) { - return; - } - getNextField(field->mutable_child(0)); -} - void increasePosition(Field *field) { if (!field->has_position_index()) { field->set_position_index(0); @@ -176,34 +153,30 @@ int getPositionByReferenceField(const Field& ref, const Field& field_with_index) return getPositionByReferenceField(ref.child(0), field_with_index.child(0)); } -void setPositionForLeaf(Field *field, int index) { - if (field->child_size() <= 0) { - field->set_position_index(index); - } else { - setPositionForLeaf(field->mutable_child(0), index); - } -} - namespace { + void findFields( const FieldValueMap& fieldValueMap, const FieldMatcher& matcher, - const Field& field, - std::vector<Field>* rootFields); + Field* rootField, + Field* leafField, + std::set<Field, FieldCmp>* rootFields); void findNonRepeatedFields( const FieldValueMap& fieldValueMap, const FieldMatcher& matcher, - const Field& field, - std::vector<Field>* rootFields) { + Field* rootField, + Field* leafField, + std::set<Field, FieldCmp>* rootFields) { if (matcher.child_size() > 0) { + Field* newLeafField = leafField->add_child(); for (const auto& childMatcher : matcher.child()) { - Field childField = field; - appendLeaf(&childField, childMatcher.field()); - findFields(fieldValueMap, childMatcher, childField, rootFields); + newLeafField->set_field(childMatcher.field()); + findFields(fieldValueMap, childMatcher, rootField, newLeafField, rootFields); } + leafField->clear_child(); } else { - auto ret = fieldValueMap.equal_range(field); + auto ret = fieldValueMap.equal_range(*rootField); int found = 0; for (auto it = ret.first; it != ret.second; ++it) { found++; @@ -216,38 +189,42 @@ void findNonRepeatedFields( ALOGE("Found multiple values for optional field."); return; } - rootFields->push_back(ret.first->first); + rootFields->insert(ret.first->first); } } void findRepeatedFields(const FieldValueMap& fieldValueMap, const FieldMatcher& matcher, - const Field& field, std::vector<Field>* rootFields) { + Field* rootField, Field* leafField, + std::set<Field, FieldCmp>* rootFields) { if (matcher.position() == Position::FIRST) { - Field first_field = field; - setPositionForLeaf(&first_field, 0); - findNonRepeatedFields(fieldValueMap, matcher, first_field, rootFields); + leafField->set_position_index(0); + findNonRepeatedFields(fieldValueMap, matcher, rootField, leafField, rootFields); + leafField->clear_position_index(); } else { - auto itLower = fieldValueMap.lower_bound(field); + auto itLower = fieldValueMap.lower_bound(*rootField); if (itLower == fieldValueMap.end()) { return; } - Field next_field = field; - getNextField(&next_field); - auto itUpper = fieldValueMap.lower_bound(next_field); + + const int leafFieldNum = leafField->field(); + leafField->set_field(leafFieldNum + 1); + auto itUpper = fieldValueMap.lower_bound(*rootField); + // Resets the field number. + leafField->set_field(leafFieldNum); switch (matcher.position()) { case Position::LAST: { itUpper--; if (itUpper != fieldValueMap.end()) { - Field last_field = field; - int last_index = getPositionByReferenceField(field, itUpper->first); + int last_index = getPositionByReferenceField(*rootField, itUpper->first); if (last_index < 0) { return; } - setPositionForLeaf(&last_field, last_index); + leafField->set_position_index(last_index); findNonRepeatedFields( - fieldValueMap, matcher, last_field, rootFields); + fieldValueMap, matcher, rootField, leafField, rootFields); + leafField->clear_position_index(); } } break; @@ -255,17 +232,17 @@ void findRepeatedFields(const FieldValueMap& fieldValueMap, const FieldMatcher& { std::set<int> indexes; for (auto it = itLower; it != itUpper; ++it) { - int index = getPositionByReferenceField(field, it->first); + int index = getPositionByReferenceField(*rootField, it->first); if (index >= 0) { indexes.insert(index); } } if (!indexes.empty()) { - Field any_field = field; for (const int index : indexes) { - setPositionForLeaf(&any_field, index); + leafField->set_position_index(index); findNonRepeatedFields( - fieldValueMap, matcher, any_field, rootFields); + fieldValueMap, matcher, rootField, leafField, rootFields); + leafField->clear_position_index(); } } } @@ -279,12 +256,13 @@ void findRepeatedFields(const FieldValueMap& fieldValueMap, const FieldMatcher& void findFields( const FieldValueMap& fieldValueMap, const FieldMatcher& matcher, - const Field& field, - std::vector<Field>* rootFields) { + Field* rootField, + Field* leafField, + std::set<Field, FieldCmp>* rootFields) { if (!matcher.has_position()) { - findNonRepeatedFields(fieldValueMap, matcher, field, rootFields); + findNonRepeatedFields(fieldValueMap, matcher, rootField, leafField, rootFields); } else { - findRepeatedFields(fieldValueMap, matcher, field, rootFields); + findRepeatedFields(fieldValueMap, matcher, rootField, leafField, rootFields); } } @@ -293,17 +271,24 @@ void findFields( void findFields( const FieldValueMap& fieldValueMap, const FieldMatcher& matcher, - std::vector<Field>* rootFields) { - return findFields(fieldValueMap, matcher, buildSimpleAtomField(matcher.field()), rootFields); + std::set<Field, FieldCmp>* rootFields) { + if (!matcher.has_field() || fieldValueMap.empty()) { + return; + } + Field rootField; + buildSimpleAtomField(matcher.field(), &rootField); + return findFields(fieldValueMap, matcher, &rootField, &rootField, rootFields); } void filterFields(const FieldMatcher& matcher, FieldValueMap* fieldValueMap) { - std::vector<Field> rootFields; + if (!matcher.has_field()) { + return; + } + std::set<Field, FieldCmp> rootFields; findFields(*fieldValueMap, matcher, &rootFields); - std::set<Field, FieldCmp> rootFieldSet(rootFields.begin(), rootFields.end()); auto it = fieldValueMap->begin(); while (it != fieldValueMap->end()) { - if (rootFieldSet.find(it->first) == rootFieldSet.end()) { + if (rootFields.find(it->first) == rootFields.end()) { it = fieldValueMap->erase(it); } else { it++; diff --git a/cmds/statsd/src/field_util.h b/cmds/statsd/src/field_util.h index a4dfdddf210b..b04465dc9862 100644 --- a/cmds/statsd/src/field_util.h +++ b/cmds/statsd/src/field_util.h @@ -20,7 +20,8 @@ #include "frameworks/base/cmds/statsd/src/statsd_internal.pb.h" #include "frameworks/base/cmds/statsd/src/stats_log.pb.h" -#include <unordered_map> +#include <map> +#include <set> namespace android { namespace os { @@ -54,15 +55,9 @@ Field* getSingleLeaf(Field* field); void appendLeaf(Field *parent, int node_field_num); void appendLeaf(Field *parent, int node_field_num, int position); -// Given the field sorting logic, this function is to increase the "field" at the leaf node. -void getNextField(Field* field); - // Increase the position index for the node. If the "position_index" is not set, set it as 0. void increasePosition(Field *field); -// Finds the leaf node and set the index there. -void setPositionForLeaf(Field *field, int index); - // Returns true if the matcher has specified at least one leaf node. bool hasLeafNode(const FieldMatcher& matcher); @@ -72,15 +67,13 @@ bool hasLeafNode(const FieldMatcher& matcher); int getPositionByReferenceField(const Field& reference, const Field& field_with_index); // Utils to build the Field proto for simple atom fields. -Field buildAtomField(const int tagId, const Field &atomField); -Field buildSimpleAtomField(const int tagId, const int atomFieldNum); -Field buildSimpleAtomField(const int tagId); +void buildSimpleAtomField(const int tagId, const int atomFieldNum, Field* field); +void buildSimpleAtomField(const int tagId, Field* field); // Find out all the fields specified by the matcher. void findFields( - const FieldValueMap& fieldValueMap, - const FieldMatcher& matcher, - std::vector<Field>* rootFields); + const FieldValueMap& fieldValueMap, const FieldMatcher& matcher, + std::set<Field, FieldCmp>* rootFields); // Filter out the fields not in the field matcher. void filterFields(const FieldMatcher& matcher, FieldValueMap* fieldValueMap); diff --git a/cmds/statsd/src/guardrail/StatsdStats.cpp b/cmds/statsd/src/guardrail/StatsdStats.cpp index 63bde7d82372..77f54569cb47 100644 --- a/cmds/statsd/src/guardrail/StatsdStats.cpp +++ b/cmds/statsd/src/guardrail/StatsdStats.cpp @@ -45,6 +45,8 @@ const int FIELD_ID_CONFIG_STATS = 3; const int FIELD_ID_ATOM_STATS = 7; const int FIELD_ID_UIDMAP_STATS = 8; const int FIELD_ID_ANOMALY_ALARM_STATS = 9; +const int FIELD_ID_PULLED_ATOM_STATS = 10; +const int FIELD_ID_LOGGER_ERROR_STATS = 11; const int FIELD_ID_MATCHER_STATS_NAME = 1; const int FIELD_ID_MATCHER_STATS_COUNT = 2; @@ -60,6 +62,9 @@ const int FIELD_ID_ATOM_STATS_COUNT = 2; const int FIELD_ID_ANOMALY_ALARMS_REGISTERED = 1; +const int FIELD_ID_LOGGER_STATS_TIME = 1; +const int FIELD_ID_LOGGER_STATS_ERROR_CODE = 2; + std::map<int, long> StatsdStats::kPullerCooldownMap = { {android::util::KERNEL_WAKELOCK, 1}, {android::util::WIFI_BYTES_TRANSFER, 1}, @@ -282,6 +287,15 @@ void StatsdStats::noteAtomLogged(int atomId, int32_t timeSec) { mPushedAtomStats[atomId]++; } +void StatsdStats::noteLoggerError(int error) { + lock_guard<std::mutex> lock(mLock); + // grows strictly one at a time. so it won't > kMaxLoggerErrors + if (mLoggerErrors.size() == kMaxLoggerErrors) { + mLoggerErrors.pop_front(); + } + mLoggerErrors.push_back(std::make_pair(time(nullptr), error)); +} + void StatsdStats::reset() { lock_guard<std::mutex> lock(mLock); resetInternalLocked(); @@ -297,6 +311,7 @@ void StatsdStats::resetInternalLocked() { mAlertStats.clear(); mAnomalyAlarmRegisteredStats = 0; mMatcherStats.clear(); + mLoggerErrors.clear(); for (auto& config : mConfigStats) { config.second.clear_broadcast_sent_time_sec(); config.second.clear_data_drop_time_sec(); @@ -465,6 +480,14 @@ void StatsdStats::dumpStats(FILE* out) const { "lost=%d\n", mUidMapStats.bytes_used(), mUidMapStats.snapshots(), mUidMapStats.changes(), mUidMapStats.dropped_snapshots(), mUidMapStats.dropped_changes()); + + for (const auto& error : mLoggerErrors) { + time_t error_time = error.first; + struct tm* error_tm = localtime(&error_time); + char buffer[80]; + strftime(buffer, sizeof(buffer), "%Y-%m-%d %I:%M%p\n", error_tm); + fprintf(out, "Logger error %d at %s\n", error.second, buffer); + } } void StatsdStats::dumpStats(std::vector<uint8_t>* output, bool reset) { @@ -526,6 +549,14 @@ void StatsdStats::dumpStats(std::vector<uint8_t>* output, bool reset) { mUidMapStats.SerializeToArray(&buffer[0], numBytes); proto.write(FIELD_TYPE_MESSAGE | FIELD_ID_UIDMAP_STATS, &buffer[0], buffer.size()); + for (const auto& error : mLoggerErrors) { + long long token = proto.start(FIELD_TYPE_MESSAGE | FIELD_ID_LOGGER_ERROR_STATS | + FIELD_COUNT_REPEATED); + proto.write(FIELD_TYPE_INT32 | FIELD_ID_LOGGER_STATS_TIME, error.first); + proto.write(FIELD_TYPE_INT32 | FIELD_ID_LOGGER_STATS_ERROR_CODE, error.second); + proto.end(token); + } + output->clear(); size_t bufferSize = proto.size(); output->resize(bufferSize); diff --git a/cmds/statsd/src/guardrail/StatsdStats.h b/cmds/statsd/src/guardrail/StatsdStats.h index 7cb48ead55d8..1f4bfa62c453 100644 --- a/cmds/statsd/src/guardrail/StatsdStats.h +++ b/cmds/statsd/src/guardrail/StatsdStats.h @@ -49,6 +49,8 @@ public: // The max number of old config stats we keep. const static int kMaxIceBoxSize = 20; + const static int kMaxLoggerErrors = 10; + const static int kMaxTimestampCount = 20; const static int kMaxLogSourceCount = 50; @@ -185,6 +187,11 @@ public: void notePullFromCache(int pullAtomId); /** + * Records statsd met an error while reading from logd. + */ + void noteLoggerError(int error); + + /** * Reset the historical stats. Including all stats in icebox, and the tracked stats about * metrics, matchers, and atoms. The active configs will be kept and StatsdStats will continue * to collect stats after reset() has been called. @@ -246,6 +253,9 @@ private: // Maps PullAtomId to its stats. The size is capped by the puller atom counts. std::map<int, PulledAtomStats> mPulledAtomStats; + // Logd errors. Size capped by kMaxLoggerErrors. + std::list<const std::pair<int, int>> mLoggerErrors; + // Stores the number of times statsd modified the anomaly alarm registered with // StatsCompanionService. int mAnomalyAlarmRegisteredStats = 0; diff --git a/cmds/statsd/src/logd/LogEvent.cpp b/cmds/statsd/src/logd/LogEvent.cpp index 1ca793c81878..9e72f5bd4b72 100644 --- a/cmds/statsd/src/logd/LogEvent.cpp +++ b/cmds/statsd/src/logd/LogEvent.cpp @@ -198,7 +198,8 @@ void LogEvent::init(android_log_context context) { int seenListStart = 0; - Field field; + Field fieldTree; + Field* atomField = fieldTree.add_child(); do { elem = android_log_read_next(context); switch ((int)elem.type) { @@ -206,51 +207,37 @@ void LogEvent::init(android_log_context context) { // elem at [0] is EVENT_TYPE_LIST, [1] is the tag id. if (i == 1) { mTagId = elem.data.int32; + fieldTree.set_field(mTagId); } else { - increaseField(&field, seenListStart > 0/* is_child */); - DimensionsValue dimensionsValue; - dimensionsValue.set_value_int(elem.data.int32); - setFieldInLeafValueProto(field, &dimensionsValue); - mFieldValueMap.insert( - std::make_pair(buildAtomField(mTagId, field), dimensionsValue)); + increaseField(atomField, seenListStart > 0/* is_child */); + mFieldValueMap[fieldTree].set_value_int(elem.data.int32); } break; case EVENT_TYPE_FLOAT: { - increaseField(&field, seenListStart > 0/* is_child */); - DimensionsValue dimensionsValue; - dimensionsValue.set_value_float(elem.data.float32); - setFieldInLeafValueProto(field, &dimensionsValue); - mFieldValueMap.insert( - std::make_pair(buildAtomField(mTagId, field), dimensionsValue)); + increaseField(atomField, seenListStart > 0/* is_child */); + mFieldValueMap[fieldTree].set_value_float(elem.data.float32); } break; case EVENT_TYPE_STRING: { - increaseField(&field, seenListStart > 0/* is_child */); - DimensionsValue dimensionsValue; - dimensionsValue.set_value_str(string(elem.data.string, elem.len).c_str()); - setFieldInLeafValueProto(field, &dimensionsValue); - mFieldValueMap.insert( - std::make_pair(buildAtomField(mTagId, field), dimensionsValue)); + increaseField(atomField, seenListStart > 0/* is_child */); + mFieldValueMap[fieldTree].set_value_str( + string(elem.data.string, elem.len).c_str()); } break; case EVENT_TYPE_LONG: { - increaseField(&field, seenListStart > 0 /* is_child */); - DimensionsValue dimensionsValue; - dimensionsValue.set_value_long(elem.data.int64); - setFieldInLeafValueProto(field, &dimensionsValue); - mFieldValueMap.insert( - std::make_pair(buildAtomField(mTagId, field), dimensionsValue)); + increaseField(atomField, seenListStart > 0 /* is_child */); + mFieldValueMap[fieldTree].set_value_long(elem.data.int64); } break; case EVENT_TYPE_LIST: if (i >= 1) { if (seenListStart > 0) { - increasePosition(&field); + increasePosition(atomField); } else { - increaseField(&field, false /* is_child */); + increaseField(atomField, false /* is_child */); } seenListStart++; if (seenListStart >= 3) { @@ -262,10 +249,10 @@ void LogEvent::init(android_log_context context) { case EVENT_TYPE_LIST_STOP: seenListStart--; if (seenListStart == 0) { - field.clear_position_index(); + atomField->clear_position_index(); } else { - if (field.child_size() > 0) { - field.mutable_child(0)->clear_field(); + if (atomField->child_size() > 0) { + atomField->mutable_child(0)->clear_field(); } } break; @@ -393,14 +380,9 @@ bool LogEvent::GetAtomDimensionsValueProto(const FieldMatcher& matcher, bool LogEvent::GetSimpleAtomDimensionsValueProto(size_t atomField, DimensionsValue* dimensionsValue) const { - return GetAtomDimensionsValueProto( - buildSimpleAtomFieldMatcher(mTagId, atomField), dimensionsValue); -} - -DimensionsValue LogEvent::GetSimpleAtomDimensionsValueProto(size_t atomField) const { - DimensionsValue dimensionsValue; - GetSimpleAtomDimensionsValueProto(atomField, &dimensionsValue); - return dimensionsValue; + FieldMatcher matcher; + buildSimpleAtomFieldMatcher(mTagId, atomField, &matcher); + return GetAtomDimensionsValueProto(matcher, dimensionsValue); } DimensionsValue* LogEvent::findFieldValueOrNull(const Field& field) { diff --git a/cmds/statsd/src/logd/LogEvent.h b/cmds/statsd/src/logd/LogEvent.h index 5a4efd4b54a4..eb2c00845d00 100644 --- a/cmds/statsd/src/logd/LogEvent.h +++ b/cmds/statsd/src/logd/LogEvent.h @@ -92,7 +92,6 @@ public: * Get a DimensionsValue proto objects from Field. */ bool GetSimpleAtomDimensionsValueProto(size_t field, DimensionsValue* dimensionsValue) const; - DimensionsValue GetSimpleAtomDimensionsValueProto(size_t atomField) const; /** * Write test data to the LogEvent. This can only be used when the LogEvent is constructed diff --git a/cmds/statsd/src/logd/LogReader.cpp b/cmds/statsd/src/logd/LogReader.cpp index 5d43ef3f88bd..0fe896bb10c3 100644 --- a/cmds/statsd/src/logd/LogReader.cpp +++ b/cmds/statsd/src/logd/LogReader.cpp @@ -16,10 +16,11 @@ #include "logd/LogReader.h" -#include <utils/Errors.h> +#include "guardrail/StatsdStats.h" #include <time.h> #include <unistd.h> +#include <utils/Errors.h> using namespace android; using namespace std; @@ -92,16 +93,15 @@ int LogReader::connect_and_read() { // Read forever if (eventLogger) { - + log_msg msg; while (true) { - log_msg msg; - // Read a message err = android_logger_list_read(loggers, &msg); // err = 0 - no content, unexpected connection drop or EOF. // err = +ive number - size of retrieved data from logger // err = -ive number, OS supplied error _except_ for -EAGAIN if (err <= 0) { + StatsdStats::getInstance().noteLoggerError(err); fprintf(stderr, "logcat read failure: %s\n", strerror(err)); break; } diff --git a/cmds/statsd/src/matchers/matcher_util.cpp b/cmds/statsd/src/matchers/matcher_util.cpp index 48f62e702560..b6f440f2e348 100644 --- a/cmds/statsd/src/matchers/matcher_util.cpp +++ b/cmds/statsd/src/matchers/matcher_util.cpp @@ -93,25 +93,28 @@ bool combinationMatch(const vector<int>& children, const LogicalOperation& opera return matched; } -bool matchesNonRepeatedField( - const UidMap& uidMap, - const FieldValueMap& fieldMap, - const FieldValueMatcher&matcher, - const Field& field) { +namespace { + +bool matchFieldSimple(const UidMap& uidMap, const FieldValueMap& fieldMap, + const FieldValueMatcher&matcher, Field* rootField, Field* leafField); + +bool matchesNonRepeatedField(const UidMap& uidMap, const FieldValueMap& fieldMap, + const FieldValueMatcher&matcher, Field* rootField, Field* leafField) { if (matcher.value_matcher_case() == FieldValueMatcher::ValueMatcherCase::VALUE_MATCHER_NOT_SET) { return !fieldMap.empty() && fieldMap.begin()->first.field() == matcher.field(); } else if (matcher.value_matcher_case() == FieldValueMatcher::ValueMatcherCase::kMatchesTuple) { bool allMatched = true; + Field* newLeafField = leafField->add_child(); for (int i = 0; allMatched && i < matcher.matches_tuple().field_value_matcher_size(); ++i) { const auto& childMatcher = matcher.matches_tuple().field_value_matcher(i); - Field childField = field; - appendLeaf(&childField, childMatcher.field()); - allMatched &= matchFieldSimple(uidMap, fieldMap, childMatcher, childField); + newLeafField->set_field(childMatcher.field()); + allMatched &= matchFieldSimple(uidMap, fieldMap, childMatcher, rootField, newLeafField); } + leafField->clear_child(); return allMatched; } else { - auto ret = fieldMap.equal_range(field); + auto ret = fieldMap.equal_range(*rootField); int found = 0; for (auto it = ret.first; it != ret.second; ++it) { found++; @@ -132,7 +135,7 @@ bool matchesNonRepeatedField( break; case FieldValueMatcher::ValueMatcherCase::kEqString: { - if (IsAttributionUidField(field)) { + if (IsAttributionUidField(*rootField)) { const int uid = ret.first->second.value_int(); std::set<string> packageNames = uidMap.getAppNamesFromUid(uid, true /* normalize*/); @@ -171,19 +174,25 @@ bool matchesNonRepeatedField( } bool matchesRepeatedField(const UidMap& uidMap, const FieldValueMap& fieldMap, - const FieldValueMatcher&matcher, const Field& field) { + const FieldValueMatcher&matcher, + Field* rootField, Field* leafField) { if (matcher.position() == Position::FIRST) { - Field first_field = field; - setPositionForLeaf(&first_field, 0); - return matchesNonRepeatedField(uidMap, fieldMap, matcher, first_field); + leafField->set_position_index(0); + bool res = matchesNonRepeatedField(uidMap, fieldMap, matcher, rootField, leafField); + leafField->clear_position_index(); + return res; } else { - auto itLower = fieldMap.lower_bound(field); + auto itLower = fieldMap.lower_bound(*rootField); if (itLower == fieldMap.end()) { return false; } - Field next_field = field; - getNextField(&next_field); - auto itUpper = fieldMap.lower_bound(next_field); + + const int leafFieldNum = leafField->field(); + leafField->set_field(leafFieldNum + 1); + auto itUpper = fieldMap.lower_bound(*rootField); + // Resets the field number. + leafField->set_field(leafFieldNum); + switch (matcher.position()) { case Position::LAST: { @@ -191,31 +200,31 @@ bool matchesRepeatedField(const UidMap& uidMap, const FieldValueMap& fieldMap, if (itUpper == fieldMap.end()) { return false; } else { - Field last_field = field; - int last_index = getPositionByReferenceField(field, itUpper->first); + int last_index = getPositionByReferenceField(*rootField, itUpper->first); if (last_index < 0) { return false; } - setPositionForLeaf(&last_field, last_index); - return matchesNonRepeatedField(uidMap, fieldMap, matcher, last_field); + leafField->set_position_index(last_index); + bool res = matchesNonRepeatedField(uidMap, fieldMap, matcher, rootField, leafField); + leafField->clear_position_index(); + return res; } } break; case Position::ANY: { - std::set<int> indexes; + bool matched = false; for (auto it = itLower; it != itUpper; ++it) { - int index = getPositionByReferenceField(field, it->first); + int index = getPositionByReferenceField(*rootField, it->first); if (index >= 0) { - indexes.insert(index); + leafField->set_position_index(index); + matched |= matchesNonRepeatedField(uidMap, fieldMap, matcher, rootField, leafField); + leafField->clear_position_index(); + if (matched) { + break; + } } } - bool matched = false; - for (const int index : indexes) { - Field any_field = field; - setPositionForLeaf(&any_field, index); - matched |= matchesNonRepeatedField(uidMap, fieldMap, matcher, any_field); - } return matched; } default: @@ -226,14 +235,16 @@ bool matchesRepeatedField(const UidMap& uidMap, const FieldValueMap& fieldMap, } bool matchFieldSimple(const UidMap& uidMap, const FieldValueMap& fieldMap, - const FieldValueMatcher&matcher, const Field& field) { + const FieldValueMatcher&matcher, Field* rootField, Field* leafField) { if (!matcher.has_position()) { - return matchesNonRepeatedField(uidMap, fieldMap, matcher, field); + return matchesNonRepeatedField(uidMap, fieldMap, matcher, rootField, leafField); } else { - return matchesRepeatedField(uidMap, fieldMap, matcher, field); + return matchesRepeatedField(uidMap, fieldMap, matcher, rootField, leafField); } } +} // namespace + bool matchesSimple(const UidMap& uidMap, const SimpleAtomMatcher& simpleMatcher, const LogEvent& event) { if (simpleMatcher.field_value_matcher_size() <= 0) { @@ -247,13 +258,15 @@ bool matchesSimple(const UidMap& uidMap, const SimpleAtomMatcher& simpleMatcher, *root_field_matcher.mutable_matches_tuple()->add_field_value_matcher() = simpleMatcher.field_value_matcher(i); } - return matchFieldSimple(uidMap, event.getFieldValueMap(), root_field_matcher, root_field); + return matchFieldSimple( + uidMap, event.getFieldValueMap(), root_field_matcher, &root_field, &root_field); } -vector<DimensionsValue> getDimensionKeys(const LogEvent& event, const FieldMatcher& matcher) { - vector<DimensionsValue> values; - findDimensionsValues(event.getFieldValueMap(), matcher, &values); - return values; +void getDimensionKeys(const LogEvent& event, const FieldMatcher& matcher, + std::vector<DimensionsValue> *dimensionKeys) { + if (matcher.has_field()) { + findDimensionsValues(event.getFieldValueMap(), matcher, dimensionKeys); + } } } // namespace statsd } // namespace os diff --git a/cmds/statsd/src/matchers/matcher_util.h b/cmds/statsd/src/matchers/matcher_util.h index 704cb4c22e00..a45a9fb26a13 100644 --- a/cmds/statsd/src/matchers/matcher_util.h +++ b/cmds/statsd/src/matchers/matcher_util.h @@ -42,13 +42,11 @@ enum MatchingState { bool combinationMatch(const std::vector<int>& children, const LogicalOperation& operation, const std::vector<MatchingState>& matcherResults); -bool matchFieldSimple(const UidMap& uidMap, const FieldValueMap& dimensionsMap, - const FieldValueMatcher& matcher, const Field& field); - bool matchesSimple(const UidMap& uidMap, const SimpleAtomMatcher& simpleMatcher, const LogEvent& wrapper); -std::vector<DimensionsValue> getDimensionKeys(const LogEvent& event, const FieldMatcher& matcher); +void getDimensionKeys(const LogEvent& event, const FieldMatcher& matcher, + std::vector<DimensionsValue> *dimensionKeys); } // namespace statsd } // namespace os diff --git a/cmds/statsd/src/metrics/CountMetricProducer.h b/cmds/statsd/src/metrics/CountMetricProducer.h index 16fc7ee3c6e9..061b7a36817c 100644 --- a/cmds/statsd/src/metrics/CountMetricProducer.h +++ b/cmds/statsd/src/metrics/CountMetricProducer.h @@ -68,6 +68,8 @@ private: // Internal function to calculate the current used bytes. size_t byteSizeLocked() const override; + void dumpStatesLocked(FILE* out, bool verbose) const override{}; + // Util function to flush the old packet. void flushIfNeededLocked(const uint64_t& newEventTime); diff --git a/cmds/statsd/src/metrics/DurationMetricProducer.cpp b/cmds/statsd/src/metrics/DurationMetricProducer.cpp index e26fe5649090..000874cf8f44 100644 --- a/cmds/statsd/src/metrics/DurationMetricProducer.cpp +++ b/cmds/statsd/src/metrics/DurationMetricProducer.cpp @@ -233,6 +233,21 @@ void DurationMetricProducer::flushIfNeededLocked(const uint64_t& eventTime) { mCurrentBucketNum += numBucketsForward; } +void DurationMetricProducer::dumpStatesLocked(FILE* out, bool verbose) const { + if (mCurrentSlicedDuration.size() == 0) { + return; + } + + fprintf(out, "DurationMetric %lld dimension size %lu\n", (long long)mMetricId, + (unsigned long)mCurrentSlicedDuration.size()); + if (verbose) { + for (const auto& slice : mCurrentSlicedDuration) { + fprintf(out, "\t%s\n", slice.first.c_str()); + slice.second->dumpStates(out, verbose); + } + } +} + bool DurationMetricProducer::hitGuardRailLocked(const HashableDimensionKey& newKey) { // the key is not new, we are good. if (mCurrentSlicedDuration.find(newKey) != mCurrentSlicedDuration.end()) { @@ -275,7 +290,8 @@ void DurationMetricProducer::onMatchedLogEventInternalLocked( auto it = mCurrentSlicedDuration.find(eventKey); - std::vector<DimensionsValue> values = getDimensionKeys(event, mInternalDimensions); + std::vector<DimensionsValue> values; + getDimensionKeys(event, mInternalDimensions, &values); if (values.empty()) { if (matcherIndex == mStartIndex) { it->second->noteStart(DEFAULT_DIMENSION_KEY, condition, diff --git a/cmds/statsd/src/metrics/DurationMetricProducer.h b/cmds/statsd/src/metrics/DurationMetricProducer.h index e06b9a14563d..d8cab92a2b84 100644 --- a/cmds/statsd/src/metrics/DurationMetricProducer.h +++ b/cmds/statsd/src/metrics/DurationMetricProducer.h @@ -68,6 +68,8 @@ private: // Internal function to calculate the current used bytes. size_t byteSizeLocked() const override; + void dumpStatesLocked(FILE* out, bool verbose) const override; + // Util function to flush the old packet. void flushIfNeededLocked(const uint64_t& eventTime); diff --git a/cmds/statsd/src/metrics/EventMetricProducer.h b/cmds/statsd/src/metrics/EventMetricProducer.h index a57b07d6648e..9da0dd0569d6 100644 --- a/cmds/statsd/src/metrics/EventMetricProducer.h +++ b/cmds/statsd/src/metrics/EventMetricProducer.h @@ -62,6 +62,8 @@ private: // Internal function to calculate the current used bytes. size_t byteSizeLocked() const override; + void dumpStatesLocked(FILE* out, bool verbose) const override{}; + // Maps to a EventMetricDataWrapper. Storing atom events in ProtoOutputStream // is more space efficient than storing LogEvent. std::unique_ptr<android::util::ProtoOutputStream> mProto; diff --git a/cmds/statsd/src/metrics/GaugeMetricProducer.cpp b/cmds/statsd/src/metrics/GaugeMetricProducer.cpp index 24dc5b0fba53..1072c5aae6e4 100644 --- a/cmds/statsd/src/metrics/GaugeMetricProducer.cpp +++ b/cmds/statsd/src/metrics/GaugeMetricProducer.cpp @@ -58,6 +58,7 @@ const int FIELD_ID_BUCKET_INFO = 3; const int FIELD_ID_START_BUCKET_NANOS = 1; const int FIELD_ID_END_BUCKET_NANOS = 2; const int FIELD_ID_ATOM = 3; +const int FIELD_ID_TIMESTAMP = 4; GaugeMetricProducer::GaugeMetricProducer(const ConfigKey& key, const GaugeMetric& metric, const int conditionIndex, @@ -67,7 +68,7 @@ GaugeMetricProducer::GaugeMetricProducer(const ConfigKey& key, const GaugeMetric : MetricProducer(metric.id(), key, startTimeNs, conditionIndex, wizard), mStatsPullerManager(statsPullerManager), mPullTagId(pullTagId) { - mCurrentSlicedBucket = std::make_shared<DimToGaugeFieldsMap>(); + mCurrentSlicedBucket = std::make_shared<DimToGaugeAtomsMap>(); mCurrentSlicedBucketForAnomaly = std::make_shared<DimToValMap>(); int64_t bucketSizeMills = 0; if (metric.has_bucket()) { @@ -77,6 +78,7 @@ GaugeMetricProducer::GaugeMetricProducer(const ConfigKey& key, const GaugeMetric } mBucketSizeNs = bucketSizeMills * 1000000; + mSamplingType = metric.sampling_type(); mFieldFilter = metric.gauge_fields_filter(); // TODO: use UidMap if uid->pkg_name is required @@ -89,7 +91,7 @@ GaugeMetricProducer::GaugeMetricProducer(const ConfigKey& key, const GaugeMetric } // Kicks off the puller immediately. - if (mPullTagId != -1) { + if (mPullTagId != -1 && mSamplingType == GaugeMetric::RANDOM_ONE_SAMPLE) { mStatsPullerManager->RegisterReceiver(mPullTagId, this, bucketSizeMills); } @@ -154,12 +156,23 @@ void GaugeMetricProducer::onDumpReportLocked(const uint64_t dumpTimeNs, (long long)bucket.mBucketStartNs); protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_END_BUCKET_NANOS, (long long)bucket.mBucketEndNs); - long long atomToken = protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_ID_ATOM); - writeFieldValueTreeToStream(*bucket.mGaugeFields, protoOutput); - protoOutput->end(atomToken); + + if (!bucket.mGaugeAtoms.empty()) { + long long atomsToken = + protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_ATOM); + for (const auto& atom : bucket.mGaugeAtoms) { + writeFieldValueTreeToStream(*atom.mFields, protoOutput); + } + protoOutput->end(atomsToken); + + for (const auto& atom : bucket.mGaugeAtoms) { + protoOutput->write(FIELD_TYPE_INT64 | FIELD_COUNT_REPEATED | FIELD_ID_TIMESTAMP, + (long long)atom.mTimestamps); + } + } protoOutput->end(bucketInfoToken); - VLOG("\t bucket [%lld - %lld] includes %d gauge fields.", (long long)bucket.mBucketStartNs, - (long long)bucket.mBucketEndNs, (int)bucket.mGaugeFields->size()); + VLOG("\t bucket [%lld - %lld] includes %d atoms.", (long long)bucket.mBucketStartNs, + (long long)bucket.mBucketEndNs, (int)bucket.mGaugeAtoms.size()); } protoOutput->end(wrapperToken); } @@ -181,14 +194,26 @@ void GaugeMetricProducer::onConditionChangedLocked(const bool conditionMet, if (mPullTagId == -1) { return; } - // No need to pull again. Either scheduled pull or condition on true happened - if (!mCondition) { - return; + + bool triggerPuller = false; + switch(mSamplingType) { + // When the metric wants to do random sampling and there is already one gauge atom for the + // current bucket, do not do it again. + case GaugeMetric::RANDOM_ONE_SAMPLE: { + triggerPuller = mCondition && mCurrentSlicedBucket->empty(); + break; + } + case GaugeMetric::ALL_CONDITION_CHANGES: { + triggerPuller = true; + break; + } + default: + break; } - // Already have gauge metric for the current bucket, do not do it again. - if (mCurrentSlicedBucket->size() > 0) { + if (!triggerPuller) { return; } + vector<std::shared_ptr<LogEvent>> allData; if (!mStatsPullerManager->Pull(mPullTagId, &allData)) { ALOGE("Stats puller failed for tag: %d", mPullTagId); @@ -257,20 +282,24 @@ void GaugeMetricProducer::onMatchedLogEventInternalLocked( } flushIfNeededLocked(eventTimeNs); - // For gauge metric, we just simply use the first gauge in the given bucket. - if (mCurrentSlicedBucket->find(eventKey) != mCurrentSlicedBucket->end()) { + // When gauge metric wants to randomly sample the output atom, we just simply use the first + // gauge in the given bucket. + if (mCurrentSlicedBucket->find(eventKey) != mCurrentSlicedBucket->end() && + mSamplingType == GaugeMetric::RANDOM_ONE_SAMPLE) { return; } - std::shared_ptr<FieldValueMap> gaugeFields = getGaugeFields(event); if (hitGuardRailLocked(eventKey)) { return; } - (*mCurrentSlicedBucket)[eventKey] = gaugeFields; + GaugeAtom gaugeAtom; + gaugeAtom.mFields = getGaugeFields(event); + gaugeAtom.mTimestamps = eventTimeNs; + (*mCurrentSlicedBucket)[eventKey].push_back(gaugeAtom); // Anomaly detection on gauge metric only works when there is one numeric // field specified. if (mAnomalyTrackers.size() > 0) { - if (gaugeFields->size() == 1) { - const DimensionsValue& dimensionsValue = gaugeFields->begin()->second; + if (gaugeAtom.mFields->size() == 1) { + const DimensionsValue& dimensionsValue = gaugeAtom.mFields->begin()->second; long gaugeVal = 0; if (dimensionsValue.has_value_int()) { gaugeVal = (long)dimensionsValue.value_int(); @@ -289,7 +318,10 @@ void GaugeMetricProducer::updateCurrentSlicedBucketForAnomaly() { mCurrentSlicedBucketForAnomaly->clear(); status_t err = NO_ERROR; for (const auto& slice : *mCurrentSlicedBucket) { - const DimensionsValue& dimensionsValue = slice.second->begin()->second; + if (slice.second.empty() || slice.second.front().mFields->empty()) { + continue; + } + const DimensionsValue& dimensionsValue = slice.second.front().mFields->begin()->second; long gaugeVal = 0; if (dimensionsValue.has_value_int()) { gaugeVal = (long)dimensionsValue.value_int(); @@ -318,7 +350,7 @@ void GaugeMetricProducer::flushIfNeededLocked(const uint64_t& eventTimeNs) { info.mBucketNum = mCurrentBucketNum; for (const auto& slice : *mCurrentSlicedBucket) { - info.mGaugeFields = slice.second; + info.mGaugeAtoms = slice.second; auto& bucketList = mPastBuckets[slice.first]; bucketList.push_back(info); VLOG("gauge metric %lld, dump key value: %s", @@ -334,7 +366,7 @@ void GaugeMetricProducer::flushIfNeededLocked(const uint64_t& eventTimeNs) { } mCurrentSlicedBucketForAnomaly = std::make_shared<DimToValMap>(); - mCurrentSlicedBucket = std::make_shared<DimToGaugeFieldsMap>(); + mCurrentSlicedBucket = std::make_shared<DimToGaugeAtomsMap>(); // Adjusts the bucket start time int64_t numBucketsForward = (eventTimeNs - mCurrentBucketStartTimeNs) / mBucketSizeNs; diff --git a/cmds/statsd/src/metrics/GaugeMetricProducer.h b/cmds/statsd/src/metrics/GaugeMetricProducer.h index f267e987f464..6c013477af37 100644 --- a/cmds/statsd/src/metrics/GaugeMetricProducer.h +++ b/cmds/statsd/src/metrics/GaugeMetricProducer.h @@ -32,15 +32,20 @@ namespace android { namespace os { namespace statsd { +struct GaugeAtom { + std::shared_ptr<FieldValueMap> mFields; + int64_t mTimestamps; +}; + struct GaugeBucket { int64_t mBucketStartNs; int64_t mBucketEndNs; - std::shared_ptr<FieldValueMap> mGaugeFields; + std::vector<GaugeAtom> mGaugeAtoms; uint64_t mBucketNum; }; -typedef std::unordered_map<HashableDimensionKey, std::shared_ptr<FieldValueMap>> - DimToGaugeFieldsMap; +typedef std::unordered_map<HashableDimensionKey, std::vector<GaugeAtom>> + DimToGaugeAtomsMap; // This gauge metric producer first register the puller to automatically pull the gauge at the // beginning of each bucket. If the condition is met, insert it to the bucket info. Otherwise @@ -48,7 +53,7 @@ typedef std::unordered_map<HashableDimensionKey, std::shared_ptr<FieldValueMap>> // producer always reports the guage at the earliest time of the bucket when the condition is met. class GaugeMetricProducer : public virtual MetricProducer, public virtual PullDataReceiver { public: - GaugeMetricProducer(const ConfigKey& key, const GaugeMetric& countMetric, + GaugeMetricProducer(const ConfigKey& key, const GaugeMetric& gaugeMetric, const int conditionIndex, const sp<ConditionWizard>& wizard, const int pullTagId, const int64_t startTimeNs); @@ -83,6 +88,8 @@ private: // Internal function to calculate the current used bytes. size_t byteSizeLocked() const override; + void dumpStatesLocked(FILE* out, bool verbose) const override{}; + // Util function to flush the old packet. void flushIfNeededLocked(const uint64_t& eventTime); @@ -95,7 +102,7 @@ private: std::unordered_map<HashableDimensionKey, std::vector<GaugeBucket>> mPastBuckets; // The current bucket. - std::shared_ptr<DimToGaugeFieldsMap> mCurrentSlicedBucket; + std::shared_ptr<DimToGaugeAtomsMap> mCurrentSlicedBucket; // The current bucket for anomaly detection. std::shared_ptr<DimToValMap> mCurrentSlicedBucketForAnomaly; @@ -106,6 +113,8 @@ private: // Whitelist of fields to report. Empty means all are reported. FieldFilter mFieldFilter; + GaugeMetric::SamplingType mSamplingType; + // apply a whitelist on the original input std::shared_ptr<FieldValueMap> getGaugeFields(const LogEvent& event); diff --git a/cmds/statsd/src/metrics/MetricProducer.cpp b/cmds/statsd/src/metrics/MetricProducer.cpp index d620a7ebee00..e74924a81fbf 100644 --- a/cmds/statsd/src/metrics/MetricProducer.cpp +++ b/cmds/statsd/src/metrics/MetricProducer.cpp @@ -32,8 +32,7 @@ void MetricProducer::onMatchedLogEventLocked(const size_t matcherIndex, const Lo ConditionKey conditionKey; if (mConditionSliced) { for (const auto& link : mConditionLinks) { - conditionKey.insert(std::make_pair(link.condition(), - getDimensionKeysForCondition(event, link))); + getDimensionKeysForCondition(event, link, &conditionKey[link.condition()]); } if (mWizard->query(mConditionTrackerIndex, conditionKey) != ConditionState::kTrue) { condition = false; @@ -44,8 +43,9 @@ void MetricProducer::onMatchedLogEventLocked(const size_t matcherIndex, const Lo condition = mCondition; } - if (mDimensions.child_size() > 0) { - vector<DimensionsValue> dimensionValues = getDimensionKeys(event, mDimensions); + if (mDimensions.has_field() && mDimensions.child_size() > 0) { + vector<DimensionsValue> dimensionValues; + getDimensionKeys(event, mDimensions, &dimensionValues); for (const DimensionsValue& dimensionValue : dimensionValues) { onMatchedLogEventInternalLocked( matcherIndex, HashableDimensionKey(dimensionValue), conditionKey, condition, event); diff --git a/cmds/statsd/src/metrics/MetricProducer.h b/cmds/statsd/src/metrics/MetricProducer.h index 3779c4487d23..6f33073c633c 100644 --- a/cmds/statsd/src/metrics/MetricProducer.h +++ b/cmds/statsd/src/metrics/MetricProducer.h @@ -96,6 +96,11 @@ public: return onDumpReportLocked(dumpTimeNs, report); } + void dumpStates(FILE* out, bool verbose) const { + std::lock_guard<std::mutex> lock(mMutex); + dumpStatesLocked(out, verbose); + } + // Returns the memory in bytes currently used to store this metric's data. Does not change // state. size_t byteSize() const { @@ -128,6 +133,7 @@ protected: android::util::ProtoOutputStream* protoOutput) = 0; virtual void onDumpReportLocked(const uint64_t dumpTimeNs, StatsLogReport* report) = 0; virtual size_t byteSizeLocked() const = 0; + virtual void dumpStatesLocked(FILE* out, bool verbose) const = 0; const int64_t mMetricId; diff --git a/cmds/statsd/src/metrics/MetricsManager.cpp b/cmds/statsd/src/metrics/MetricsManager.cpp index f92951703030..d0737de8acf3 100644 --- a/cmds/statsd/src/metrics/MetricsManager.cpp +++ b/cmds/statsd/src/metrics/MetricsManager.cpp @@ -154,6 +154,20 @@ void MetricsManager::onDumpReport(const uint64_t& dumpTimeStampNs, ConfigMetrics } } +void MetricsManager::dumpStates(FILE* out, bool verbose) { + fprintf(out, "ConfigKey %s, allowed source:", mConfigKey.ToString().c_str()); + { + std::lock_guard<std::mutex> lock(mAllowedLogSourcesMutex); + for (const auto& source : mAllowedLogSources) { + fprintf(out, "%d ", source); + } + } + fprintf(out, "\n"); + for (const auto& producer : mAllMetricProducers) { + producer->dumpStates(out, verbose); + } +} + void MetricsManager::onDumpReport(ProtoOutputStream* protoOutput) { VLOG("=========================Metric Reports Start=========================="); uint64_t dumpTimeStampNs = time(nullptr) * NS_PER_SEC; diff --git a/cmds/statsd/src/metrics/MetricsManager.h b/cmds/statsd/src/metrics/MetricsManager.h index a0239fcd1127..9cdbafc75fb1 100644 --- a/cmds/statsd/src/metrics/MetricsManager.h +++ b/cmds/statsd/src/metrics/MetricsManager.h @@ -61,6 +61,8 @@ public: return !mAllowedPkg.empty(); } + void dumpStates(FILE* out, bool verbose); + // Config source owner can call onDumpReport() to get all the metrics collected. virtual void onDumpReport(android::util::ProtoOutputStream* protoOutput); virtual void onDumpReport(const uint64_t& dumpTimeStampNs, ConfigMetricsReport* report); @@ -68,7 +70,6 @@ public: // Computes the total byte size of all metrics managed by a single config source. // Does not change the state. virtual size_t byteSize(); - private: const ConfigKey mConfigKey; diff --git a/cmds/statsd/src/metrics/ValueMetricProducer.h b/cmds/statsd/src/metrics/ValueMetricProducer.h index 3e7032d8cf2d..9f750cf419b5 100644 --- a/cmds/statsd/src/metrics/ValueMetricProducer.h +++ b/cmds/statsd/src/metrics/ValueMetricProducer.h @@ -67,6 +67,8 @@ private: // Internal function to calculate the current used bytes. size_t byteSizeLocked() const override; + void dumpStatesLocked(FILE* out, bool verbose) const override{}; + // Util function to flush the old packet. void flushIfNeededLocked(const uint64_t& eventTime); diff --git a/cmds/statsd/src/metrics/duration_helper/DurationTracker.h b/cmds/statsd/src/metrics/duration_helper/DurationTracker.h index 371460e804bd..c2d2cea2a1ff 100644 --- a/cmds/statsd/src/metrics/duration_helper/DurationTracker.h +++ b/cmds/statsd/src/metrics/duration_helper/DurationTracker.h @@ -97,6 +97,8 @@ public: // Predict the anomaly timestamp given the current status. virtual int64_t predictAnomalyTimestampNs(const DurationAnomalyTracker& anomalyTracker, const uint64_t currentTimestamp) const = 0; + // Dump internal states for debugging + virtual void dumpStates(FILE* out, bool verbose) const = 0; protected: // Starts the anomaly alarm. diff --git a/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.cpp b/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.cpp index 0c99391d491f..412a0c935766 100644 --- a/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.cpp +++ b/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.cpp @@ -291,6 +291,11 @@ int64_t MaxDurationTracker::predictAnomalyTimestampNs(const DurationAnomalyTrack return currentTimestamp; } +void MaxDurationTracker::dumpStates(FILE* out, bool verbose) const { + fprintf(out, "\t\t sub-durations %lu\n", (unsigned long)mInfos.size()); + fprintf(out, "\t\t current duration %lld\n", (long long)mDuration); +} + } // namespace statsd } // namespace os } // namespace android diff --git a/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.h b/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.h index 5d3c15804638..661d1311293a 100644 --- a/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.h +++ b/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.h @@ -48,9 +48,10 @@ public: int64_t predictAnomalyTimestampNs(const DurationAnomalyTracker& anomalyTracker, const uint64_t currentTimestamp) const override; + void dumpStates(FILE* out, bool verbose) const override; private: - std::map<HashableDimensionKey, DurationInfo> mInfos; + std::unordered_map<HashableDimensionKey, DurationInfo> mInfos; void noteConditionChanged(const HashableDimensionKey& key, bool conditionMet, const uint64_t timestamp); diff --git a/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.cpp b/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.cpp index 6bf42287e6dd..75d7c0898d78 100644 --- a/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.cpp +++ b/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.cpp @@ -314,6 +314,12 @@ int64_t OringDurationTracker::predictAnomalyTimestampNs( return eventTimestampNs + thresholdNs; } +void OringDurationTracker::dumpStates(FILE* out, bool verbose) const { + fprintf(out, "\t\t started count %lu\n", (unsigned long)mStarted.size()); + fprintf(out, "\t\t paused count %lu\n", (unsigned long)mPaused.size()); + fprintf(out, "\t\t current duration %lld\n", (long long)mDuration); +} + } // namespace statsd } // namespace os } // namespace android diff --git a/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.h b/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.h index 638b7ad7af26..43469ca9a551 100644 --- a/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.h +++ b/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.h @@ -48,6 +48,7 @@ public: int64_t predictAnomalyTimestampNs(const DurationAnomalyTracker& anomalyTracker, const uint64_t currentTimestamp) const override; + void dumpStates(FILE* out, bool verbose) const override; private: // We don't need to keep track of individual durations. The information that's needed is: @@ -55,10 +56,10 @@ private: // 2) which keys are paused (started but condition was false) // 3) whenever a key stops, we remove it from the started set. And if the set becomes empty, // it means everything has stopped, we then record the end time. - std::map<HashableDimensionKey, int> mStarted; - std::map<HashableDimensionKey, int> mPaused; + std::unordered_map<HashableDimensionKey, int> mStarted; + std::unordered_map<HashableDimensionKey, int> mPaused; int64_t mLastStartTime; - std::map<HashableDimensionKey, ConditionKey> mConditionKeyMap; + std::unordered_map<HashableDimensionKey, ConditionKey> mConditionKeyMap; // return true if we should not allow newKey to be tracked because we are above the threshold bool hitGuardRail(const HashableDimensionKey& newKey); diff --git a/cmds/statsd/src/stats_log.proto b/cmds/statsd/src/stats_log.proto index f73c4a5303cf..af21ca4d82e8 100644 --- a/cmds/statsd/src/stats_log.proto +++ b/cmds/statsd/src/stats_log.proto @@ -100,7 +100,9 @@ message GaugeBucketInfo { optional int64 end_bucket_nanos = 2; - optional Atom atom = 3; + repeated Atom atom = 3; + + repeated int64 timestamp_nanos = 4; } message GaugeMetricData { @@ -262,4 +264,10 @@ message StatsdStatsReport { optional int64 min_pull_interval_sec = 4; } repeated PulledAtomStats pulled_atom_stats = 10; + + message LoggerErrorStats { + optional int32 logger_disconnection_sec = 1; + optional int32 error_code = 2; + } + repeated LoggerErrorStats logger_error_stats = 11; }
\ No newline at end of file diff --git a/cmds/statsd/src/statsd_config.proto b/cmds/statsd/src/statsd_config.proto index 07bbcb2190e8..2ea79a64a5ea 100644 --- a/cmds/statsd/src/statsd_config.proto +++ b/cmds/statsd/src/statsd_config.proto @@ -222,6 +222,12 @@ message GaugeMetric { optional TimeUnit bucket = 6; repeated MetricConditionLink links = 7; + + enum SamplingType { + RANDOM_ONE_SAMPLE = 1; + ALL_CONDITION_CHANGES = 2; + } + optional SamplingType sampling_type = 9 [default = RANDOM_ONE_SAMPLE] ; } message ValueMetric { diff --git a/cmds/statsd/tests/e2e/GaugeMetric_e2e_test.cpp b/cmds/statsd/tests/e2e/GaugeMetric_e2e_test.cpp index e56a6c57848f..a80fdc5606b7 100644 --- a/cmds/statsd/tests/e2e/GaugeMetric_e2e_test.cpp +++ b/cmds/statsd/tests/e2e/GaugeMetric_e2e_test.cpp @@ -153,23 +153,26 @@ TEST(GaugeMetricE2eTest, TestMultipleFieldsForPushedEvent) { EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).field(), 1 /* uid field */); EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).value_int(), appUid1); EXPECT_EQ(data.bucket_info_size(), 3); + EXPECT_EQ(data.bucket_info(0).atom_size(), 1); EXPECT_EQ(data.bucket_info(0).start_bucket_nanos(), bucketStartTimeNs); EXPECT_EQ(data.bucket_info(0).end_bucket_nanos(), bucketStartTimeNs + bucketSizeNs); - EXPECT_EQ(data.bucket_info(0).atom().app_start_changed().type(), AppStartChanged::HOT); - EXPECT_EQ(data.bucket_info(0).atom().app_start_changed().activity_name(), "activity_name2"); - EXPECT_EQ(data.bucket_info(0).atom().app_start_changed().activity_start_msec(), 102L); + EXPECT_EQ(data.bucket_info(0).atom(0).app_start_changed().type(), AppStartChanged::HOT); + EXPECT_EQ(data.bucket_info(0).atom(0).app_start_changed().activity_name(), "activity_name2"); + EXPECT_EQ(data.bucket_info(0).atom(0).app_start_changed().activity_start_msec(), 102L); + EXPECT_EQ(data.bucket_info(1).atom_size(), 1); EXPECT_EQ(data.bucket_info(1).start_bucket_nanos(), bucketStartTimeNs + bucketSizeNs); EXPECT_EQ(data.bucket_info(1).end_bucket_nanos(), bucketStartTimeNs + 2 * bucketSizeNs); - EXPECT_EQ(data.bucket_info(1).atom().app_start_changed().type(), AppStartChanged::WARM); - EXPECT_EQ(data.bucket_info(1).atom().app_start_changed().activity_name(), "activity_name4"); - EXPECT_EQ(data.bucket_info(1).atom().app_start_changed().activity_start_msec(), 104L); + EXPECT_EQ(data.bucket_info(1).atom(0).app_start_changed().type(), AppStartChanged::WARM); + EXPECT_EQ(data.bucket_info(1).atom(0).app_start_changed().activity_name(), "activity_name4"); + EXPECT_EQ(data.bucket_info(1).atom(0).app_start_changed().activity_start_msec(), 104L); + EXPECT_EQ(data.bucket_info(2).atom_size(), 1); EXPECT_EQ(data.bucket_info(2).start_bucket_nanos(), bucketStartTimeNs + 2 * bucketSizeNs); EXPECT_EQ(data.bucket_info(2).end_bucket_nanos(), bucketStartTimeNs + 3 * bucketSizeNs); - EXPECT_EQ(data.bucket_info(2).atom().app_start_changed().type(), AppStartChanged::COLD); - EXPECT_EQ(data.bucket_info(2).atom().app_start_changed().activity_name(), "activity_name5"); - EXPECT_EQ(data.bucket_info(2).atom().app_start_changed().activity_start_msec(), 105L); + EXPECT_EQ(data.bucket_info(2).atom(0).app_start_changed().type(), AppStartChanged::COLD); + EXPECT_EQ(data.bucket_info(2).atom(0).app_start_changed().activity_name(), "activity_name5"); + EXPECT_EQ(data.bucket_info(2).atom(0).app_start_changed().activity_start_msec(), 105L); data = gaugeMetrics.data(1); @@ -178,11 +181,12 @@ TEST(GaugeMetricE2eTest, TestMultipleFieldsForPushedEvent) { EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).field(), 1 /* uid field */); EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).value_int(), appUid2); EXPECT_EQ(data.bucket_info_size(), 1); + EXPECT_EQ(data.bucket_info(0).atom_size(), 1); EXPECT_EQ(data.bucket_info(0).start_bucket_nanos(), bucketStartTimeNs + 2 * bucketSizeNs); EXPECT_EQ(data.bucket_info(0).end_bucket_nanos(), bucketStartTimeNs + 3 * bucketSizeNs); - EXPECT_EQ(data.bucket_info(0).atom().app_start_changed().type(), AppStartChanged::COLD); - EXPECT_EQ(data.bucket_info(0).atom().app_start_changed().activity_name(), "activity_name7"); - EXPECT_EQ(data.bucket_info(0).atom().app_start_changed().activity_start_msec(), 201L); + EXPECT_EQ(data.bucket_info(0).atom(0).app_start_changed().type(), AppStartChanged::COLD); + EXPECT_EQ(data.bucket_info(0).atom(0).app_start_changed().activity_name(), "activity_name7"); + EXPECT_EQ(data.bucket_info(0).atom(0).app_start_changed().activity_start_msec(), 201L); } #else diff --git a/cmds/statsd/tests/metrics/CountMetricProducer_test.cpp b/cmds/statsd/tests/metrics/CountMetricProducer_test.cpp index 897328d4635e..4ad209712905 100644 --- a/cmds/statsd/tests/metrics/CountMetricProducer_test.cpp +++ b/cmds/statsd/tests/metrics/CountMetricProducer_test.cpp @@ -149,8 +149,8 @@ TEST(CountMetricProducerTest, TestEventsWithSlicedCondition) { metric.set_condition(StringToId("APP_IN_BACKGROUND_PER_UID_AND_SCREEN_ON")); MetricConditionLink* link = metric.add_links(); link->set_condition(StringToId("APP_IN_BACKGROUND_PER_UID")); - *link->mutable_fields_in_what() = buildSimpleAtomFieldMatcher(tagId, 1); - *link->mutable_fields_in_condition() = buildSimpleAtomFieldMatcher(conditionTagId, 2); + buildSimpleAtomFieldMatcher(tagId, 1, link->mutable_fields_in_what()); + buildSimpleAtomFieldMatcher(conditionTagId, 2, link->mutable_fields_in_condition()); LogEvent event1(tagId, bucketStartTimeNs + 1); event1.write("111"); // uid diff --git a/cmds/statsd/tests/metrics/EventMetricProducer_test.cpp b/cmds/statsd/tests/metrics/EventMetricProducer_test.cpp index 34cde607988e..da00cae125c7 100644 --- a/cmds/statsd/tests/metrics/EventMetricProducer_test.cpp +++ b/cmds/statsd/tests/metrics/EventMetricProducer_test.cpp @@ -98,8 +98,8 @@ TEST(EventMetricProducerTest, TestEventsWithSlicedCondition) { metric.set_condition(StringToId("APP_IN_BACKGROUND_PER_UID_AND_SCREEN_ON")); MetricConditionLink* link = metric.add_links(); link->set_condition(StringToId("APP_IN_BACKGROUND_PER_UID")); - *link->mutable_fields_in_what() = buildSimpleAtomFieldMatcher(tagId, 1); - *link->mutable_fields_in_condition() = buildSimpleAtomFieldMatcher(conditionTagId, 2); + buildSimpleAtomFieldMatcher(tagId, 1, link->mutable_fields_in_what()); + buildSimpleAtomFieldMatcher(conditionTagId, 2, link->mutable_fields_in_condition()); LogEvent event1(tagId, bucketStartTimeNs + 1); EXPECT_TRUE(event1.write("111")); diff --git a/cmds/statsd/tests/metrics/GaugeMetricProducer_test.cpp b/cmds/statsd/tests/metrics/GaugeMetricProducer_test.cpp index 82772d854db2..4533ac610057 100644 --- a/cmds/statsd/tests/metrics/GaugeMetricProducer_test.cpp +++ b/cmds/statsd/tests/metrics/GaugeMetricProducer_test.cpp @@ -78,7 +78,7 @@ TEST(GaugeMetricProducerTest, TestNoCondition) { gaugeProducer.onDataPulled(allData); EXPECT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->size()); - auto it = gaugeProducer.mCurrentSlicedBucket->begin()->second->begin(); + auto it = gaugeProducer.mCurrentSlicedBucket->begin()->second.front().mFields->begin(); EXPECT_EQ(10, it->second.value_int()); it++; EXPECT_EQ(11, it->second.value_int()); @@ -94,14 +94,14 @@ TEST(GaugeMetricProducerTest, TestNoCondition) { allData.push_back(event2); gaugeProducer.onDataPulled(allData); EXPECT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->size()); - it = gaugeProducer.mCurrentSlicedBucket->begin()->second->begin(); + it = gaugeProducer.mCurrentSlicedBucket->begin()->second.front().mFields->begin(); EXPECT_EQ(24, it->second.value_int()); it++; EXPECT_EQ(25, it->second.value_int()); // One dimension. EXPECT_EQ(1UL, gaugeProducer.mPastBuckets.size()); EXPECT_EQ(1UL, gaugeProducer.mPastBuckets.begin()->second.size()); - it = gaugeProducer.mPastBuckets.begin()->second.back().mGaugeFields->begin(); + it = gaugeProducer.mPastBuckets.begin()->second.back().mGaugeAtoms.front().mFields->begin(); EXPECT_EQ(10L, it->second.value_int()); it++; EXPECT_EQ(11L, it->second.value_int()); @@ -112,7 +112,7 @@ TEST(GaugeMetricProducerTest, TestNoCondition) { // One dimension. EXPECT_EQ(1UL, gaugeProducer.mPastBuckets.size()); EXPECT_EQ(2UL, gaugeProducer.mPastBuckets.begin()->second.size()); - it = gaugeProducer.mPastBuckets.begin()->second.back().mGaugeFields->begin(); + it = gaugeProducer.mPastBuckets.begin()->second.back().mGaugeAtoms.front().mFields->begin(); EXPECT_EQ(24L, it->second.value_int()); it++; EXPECT_EQ(25L, it->second.value_int()); @@ -151,7 +151,8 @@ TEST(GaugeMetricProducerTest, TestWithCondition) { gaugeProducer.onConditionChanged(true, bucketStartTimeNs + 8); EXPECT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->size()); EXPECT_EQ(100, - gaugeProducer.mCurrentSlicedBucket->begin()->second->begin()->second.value_int()); + gaugeProducer.mCurrentSlicedBucket->begin()-> + second.front().mFields->begin()->second.value_int()); EXPECT_EQ(0UL, gaugeProducer.mPastBuckets.size()); vector<shared_ptr<LogEvent>> allData; @@ -165,17 +166,18 @@ TEST(GaugeMetricProducerTest, TestWithCondition) { EXPECT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->size()); EXPECT_EQ(110, - gaugeProducer.mCurrentSlicedBucket->begin()->second->begin()->second.value_int()); + gaugeProducer.mCurrentSlicedBucket->begin()-> + second.front().mFields->begin()->second.value_int()); EXPECT_EQ(1UL, gaugeProducer.mPastBuckets.size()); EXPECT_EQ(100, gaugeProducer.mPastBuckets.begin()->second.back() - .mGaugeFields->begin()->second.value_int()); + .mGaugeAtoms.front().mFields->begin()->second.value_int()); gaugeProducer.onConditionChanged(false, bucket2StartTimeNs + 10); gaugeProducer.flushIfNeededLocked(bucket3StartTimeNs + 10); EXPECT_EQ(1UL, gaugeProducer.mPastBuckets.size()); EXPECT_EQ(2UL, gaugeProducer.mPastBuckets.begin()->second.size()); EXPECT_EQ(110L, gaugeProducer.mPastBuckets.begin()->second.back() - .mGaugeFields->begin()->second.value_int()); + .mGaugeAtoms.front().mFields->begin()->second.value_int()); EXPECT_EQ(1UL, gaugeProducer.mPastBuckets.begin()->second.back().mBucketNum); } @@ -214,7 +216,8 @@ TEST(GaugeMetricProducerTest, TestAnomalyDetection) { gaugeProducer.onDataPulled({event1}); EXPECT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->size()); EXPECT_EQ(13L, - gaugeProducer.mCurrentSlicedBucket->begin()->second->begin()->second.value_int()); + gaugeProducer.mCurrentSlicedBucket->begin()-> + second.front().mFields->begin()->second.value_int()); EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(DEFAULT_DIMENSION_KEY), 0U); std::shared_ptr<LogEvent> event2 = @@ -226,7 +229,8 @@ TEST(GaugeMetricProducerTest, TestAnomalyDetection) { gaugeProducer.onDataPulled({event2}); EXPECT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->size()); EXPECT_EQ(15L, - gaugeProducer.mCurrentSlicedBucket->begin()->second->begin()->second.value_int()); + gaugeProducer.mCurrentSlicedBucket->begin()-> + second.front().mFields->begin()->second.value_int()); EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(DEFAULT_DIMENSION_KEY), event2->GetTimestampNs() / NS_PER_SEC + refPeriodSec); @@ -239,7 +243,8 @@ TEST(GaugeMetricProducerTest, TestAnomalyDetection) { gaugeProducer.onDataPulled({event3}); EXPECT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->size()); EXPECT_EQ(26L, - gaugeProducer.mCurrentSlicedBucket->begin()->second->begin()->second.value_int()); + gaugeProducer.mCurrentSlicedBucket->begin()-> + second.front().mFields->begin()->second.value_int()); EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(DEFAULT_DIMENSION_KEY), event2->GetTimestampNs() / NS_PER_SEC + refPeriodSec); @@ -250,7 +255,7 @@ TEST(GaugeMetricProducerTest, TestAnomalyDetection) { event4->init(); gaugeProducer.onDataPulled({event4}); EXPECT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->size()); - EXPECT_TRUE(gaugeProducer.mCurrentSlicedBucket->begin()->second->empty()); + EXPECT_TRUE(gaugeProducer.mCurrentSlicedBucket->begin()->second.front().mFields->empty()); } } // namespace statsd diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java index b58c523f7927..7bfb20f8cc35 100644 --- a/core/java/android/app/Activity.java +++ b/core/java/android/app/Activity.java @@ -17,7 +17,6 @@ package android.app; import static android.Manifest.permission.CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS; - import static java.lang.Character.MIN_VALUE; import android.annotation.CallSuper; @@ -4713,7 +4712,6 @@ public class Activity extends ContextThemeWrapper * their launch had come from the original activity. * @param intent The Intent to start. * @param options ActivityOptions or null. - * @param permissionToken Token received from the system that permits this call to be made. * @param ignoreTargetSecurity If true, the activity manager will not check whether the * caller it is doing the start is, is actually allowed to start the target activity. * If you set this to true, you must set an explicit component in the Intent and do any @@ -4722,7 +4720,7 @@ public class Activity extends ContextThemeWrapper * @hide */ public void startActivityAsCaller(Intent intent, @Nullable Bundle options, - IBinder permissionToken, boolean ignoreTargetSecurity, int userId) { + boolean ignoreTargetSecurity, int userId) { if (mParent != null) { throw new RuntimeException("Can't be called from a child"); } @@ -4730,7 +4728,7 @@ public class Activity extends ContextThemeWrapper Instrumentation.ActivityResult ar = mInstrumentation.execStartActivityAsCaller( this, mMainThread.getApplicationThread(), mToken, this, - intent, -1, options, permissionToken, ignoreTargetSecurity, userId); + intent, -1, options, ignoreTargetSecurity, userId); if (ar != null) { mMainThread.sendActivityResult( mToken, mEmbeddedID, -1, ar.getResultCode(), @@ -6340,7 +6338,7 @@ public class Activity extends ContextThemeWrapper mHandler.getLooper().dump(new PrintWriterPrinter(writer), prefix); - final AutofillManager afm = mAutofillManager; + final AutofillManager afm = getAutofillManager(); if (afm != null) { afm.dump(prefix, writer); } else { @@ -7118,7 +7116,7 @@ public class Activity extends ContextThemeWrapper boolean isAppDebuggable = (mApplication.getApplicationInfo().flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0; - // This property is set for all builds except final release + // This property is set for all non-user builds except final release boolean isDlwarningEnabled = SystemProperties.getInt("ro.bionic.ld.warning", 0) == 1; if (isAppDebuggable || isDlwarningEnabled) { @@ -7141,8 +7139,8 @@ public class Activity extends ContextThemeWrapper } } - // We might disable this for final builds. - boolean isApiWarningEnabled = true; + // This property is set for all non-user builds except final release + boolean isApiWarningEnabled = SystemProperties.getInt("ro.art.hiddenapi.warning", 0) == 1; if (isAppDebuggable || isApiWarningEnabled) { if (VMRuntime.getRuntime().hasUsedHiddenApi()) { diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java index 4d5ac6f45703..e2ce8b177d2e 100644 --- a/core/java/android/app/ActivityManager.java +++ b/core/java/android/app/ActivityManager.java @@ -449,31 +449,6 @@ public class ActivityManager { */ public static final int INTENT_SENDER_FOREGROUND_SERVICE = 5; - /** - * Extra included on intents that are delegating the call to - * ActivityManager#startActivityAsCaller to another app. This token is necessary for that call - * to succeed. Type is IBinder. - * @hide - */ - public static final String EXTRA_PERMISSION_TOKEN = "android.app.extra.PERMISSION_TOKEN"; - - /** - * Extra included on intents that contain an EXTRA_INTENT, with options that the contained - * intent may want to be started with. Type is Bundle. - * TODO: remove once the ChooserActivity moves to systemui - * @hide - */ - public static final String EXTRA_OPTIONS = "android.app.extra.OPTIONS"; - - /** - * Extra included on intents that contain an EXTRA_INTENT, use this boolean value for the - * parameter of the same name when starting the contained intent. - * TODO: remove once the ChooserActivity moves to systemui - * @hide - */ - public static final String EXTRA_IGNORE_TARGET_SECURITY = - "android.app.extra.EXTRA_IGNORE_TARGET_SECURITY"; - /** @hide User operation call: success! */ public static final int USER_OP_SUCCESS = 0; diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index 3dedeea1a1b0..e8535cd1bca2 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -389,7 +389,7 @@ public final class ActivityThread extends ClientTransactionHandler { ActivityInfo activityInfo; CompatibilityInfo compatInfo; - public LoadedApk loadedApk; + public LoadedApk packageInfo; List<ResultInfo> pendingResults; List<ReferrerIntent> pendingIntents; @@ -432,7 +432,7 @@ public final class ActivityThread extends ClientTransactionHandler { this.isForward = isForward; this.profilerInfo = profilerInfo; this.overrideConfig = overrideConfig; - this.loadedApk = client.getLoadedApkNoCheck(activityInfo.applicationInfo, + this.packageInfo = client.getPackageInfoNoCheck(activityInfo.applicationInfo, compatInfo); init(); } @@ -614,7 +614,7 @@ public final class ActivityThread extends ClientTransactionHandler { } static final class AppBindData { - LoadedApk loadedApk; + LoadedApk info; String processName; ApplicationInfo appInfo; List<ProviderInfo> providers; @@ -1913,13 +1913,13 @@ public final class ActivityThread extends ClientTransactionHandler { return mH; } - public final LoadedApk getLoadedApkForPackageName(String packageName, - CompatibilityInfo compatInfo, int flags) { - return getLoadedApkForPackageName(packageName, compatInfo, flags, UserHandle.myUserId()); + public final LoadedApk getPackageInfo(String packageName, CompatibilityInfo compatInfo, + int flags) { + return getPackageInfo(packageName, compatInfo, flags, UserHandle.myUserId()); } - public final LoadedApk getLoadedApkForPackageName(String packageName, - CompatibilityInfo compatInfo, int flags, int userId) { + public final LoadedApk getPackageInfo(String packageName, CompatibilityInfo compatInfo, + int flags, int userId) { final boolean differentUser = (UserHandle.myUserId() != userId); synchronized (mResourcesManager) { WeakReference<LoadedApk> ref; @@ -1932,13 +1932,13 @@ public final class ActivityThread extends ClientTransactionHandler { ref = mResourcePackages.get(packageName); } - LoadedApk loadedApk = ref != null ? ref.get() : null; - //Slog.i(TAG, "getLoadedApkForPackageName " + packageName + ": " + loadedApk); - //if (loadedApk != null) Slog.i(TAG, "isUptoDate " + loadedApk.mResDir - // + ": " + loadedApk.mResources.getAssets().isUpToDate()); - if (loadedApk != null && (loadedApk.mResources == null - || loadedApk.mResources.getAssets().isUpToDate())) { - if (loadedApk.isSecurityViolation() + LoadedApk packageInfo = ref != null ? ref.get() : null; + //Slog.i(TAG, "getPackageInfo " + packageName + ": " + packageInfo); + //if (packageInfo != null) Slog.i(TAG, "isUptoDate " + packageInfo.mResDir + // + ": " + packageInfo.mResources.getAssets().isUpToDate()); + if (packageInfo != null && (packageInfo.mResources == null + || packageInfo.mResources.getAssets().isUpToDate())) { + if (packageInfo.isSecurityViolation() && (flags&Context.CONTEXT_IGNORE_SECURITY) == 0) { throw new SecurityException( "Requesting code from " + packageName @@ -1946,7 +1946,7 @@ public final class ActivityThread extends ClientTransactionHandler { + mBoundApplication.processName + "/" + mBoundApplication.appInfo.uid); } - return loadedApk; + return packageInfo; } } @@ -1961,13 +1961,13 @@ public final class ActivityThread extends ClientTransactionHandler { } if (ai != null) { - return getLoadedApk(ai, compatInfo, flags); + return getPackageInfo(ai, compatInfo, flags); } return null; } - public final LoadedApk getLoadedApk(ApplicationInfo ai, CompatibilityInfo compatInfo, + public final LoadedApk getPackageInfo(ApplicationInfo ai, CompatibilityInfo compatInfo, int flags) { boolean includeCode = (flags&Context.CONTEXT_INCLUDE_CODE) != 0; boolean securityViolation = includeCode && ai.uid != 0 @@ -1989,17 +1989,17 @@ public final class ActivityThread extends ClientTransactionHandler { throw new SecurityException(msg); } } - return getLoadedApk(ai, compatInfo, null, securityViolation, includeCode, + return getPackageInfo(ai, compatInfo, null, securityViolation, includeCode, registerPackage); } @Override - public final LoadedApk getLoadedApkNoCheck(ApplicationInfo ai, + public final LoadedApk getPackageInfoNoCheck(ApplicationInfo ai, CompatibilityInfo compatInfo) { - return getLoadedApk(ai, compatInfo, null, false, true, false); + return getPackageInfo(ai, compatInfo, null, false, true, false); } - public final LoadedApk peekLoadedApk(String packageName, boolean includeCode) { + public final LoadedApk peekPackageInfo(String packageName, boolean includeCode) { synchronized (mResourcesManager) { WeakReference<LoadedApk> ref; if (includeCode) { @@ -2011,7 +2011,7 @@ public final class ActivityThread extends ClientTransactionHandler { } } - private LoadedApk getLoadedApk(ApplicationInfo aInfo, CompatibilityInfo compatInfo, + private LoadedApk getPackageInfo(ApplicationInfo aInfo, CompatibilityInfo compatInfo, ClassLoader baseLoader, boolean securityViolation, boolean includeCode, boolean registerPackage) { final boolean differentUser = (UserHandle.myUserId() != UserHandle.getUserId(aInfo.uid)); @@ -2026,35 +2026,35 @@ public final class ActivityThread extends ClientTransactionHandler { ref = mResourcePackages.get(aInfo.packageName); } - LoadedApk loadedApk = ref != null ? ref.get() : null; - if (loadedApk == null || (loadedApk.mResources != null - && !loadedApk.mResources.getAssets().isUpToDate())) { + LoadedApk packageInfo = ref != null ? ref.get() : null; + if (packageInfo == null || (packageInfo.mResources != null + && !packageInfo.mResources.getAssets().isUpToDate())) { if (localLOGV) Slog.v(TAG, (includeCode ? "Loading code package " : "Loading resource-only package ") + aInfo.packageName + " (in " + (mBoundApplication != null ? mBoundApplication.processName : null) + ")"); - loadedApk = + packageInfo = new LoadedApk(this, aInfo, compatInfo, baseLoader, securityViolation, includeCode && (aInfo.flags&ApplicationInfo.FLAG_HAS_CODE) != 0, registerPackage); if (mSystemThread && "android".equals(aInfo.packageName)) { - loadedApk.installSystemApplicationInfo(aInfo, - getSystemContext().mLoadedApk.getClassLoader()); + packageInfo.installSystemApplicationInfo(aInfo, + getSystemContext().mPackageInfo.getClassLoader()); } if (differentUser) { // Caching not supported across users } else if (includeCode) { mPackages.put(aInfo.packageName, - new WeakReference<LoadedApk>(loadedApk)); + new WeakReference<LoadedApk>(packageInfo)); } else { mResourcePackages.put(aInfo.packageName, - new WeakReference<LoadedApk>(loadedApk)); + new WeakReference<LoadedApk>(packageInfo)); } } - return loadedApk; + return packageInfo; } } @@ -2778,8 +2778,8 @@ public final class ActivityThread extends ClientTransactionHandler { /** Core implementation of activity launch. */ private Activity performLaunchActivity(ActivityClientRecord r) { ActivityInfo aInfo = r.activityInfo; - if (r.loadedApk == null) { - r.loadedApk = getLoadedApk(aInfo.applicationInfo, r.compatInfo, + if (r.packageInfo == null) { + r.packageInfo = getPackageInfo(aInfo.applicationInfo, r.compatInfo, Context.CONTEXT_INCLUDE_CODE); } @@ -2816,15 +2816,15 @@ public final class ActivityThread extends ClientTransactionHandler { } try { - Application app = r.loadedApk.makeApplication(false, mInstrumentation); + Application app = r.packageInfo.makeApplication(false, mInstrumentation); if (localLOGV) Slog.v(TAG, "Performing launch of " + r); if (localLOGV) Slog.v( TAG, r + ": app=" + app + ", appName=" + app.getPackageName() - + ", pkg=" + r.loadedApk.getPackageName() + + ", pkg=" + r.packageInfo.getPackageName() + ", comp=" + r.intent.getComponent().toShortString() - + ", dir=" + r.loadedApk.getAppDir()); + + ", dir=" + r.packageInfo.getAppDir()); if (activity != null) { CharSequence title = r.activityInfo.loadLabel(appContext.getPackageManager()); @@ -2969,7 +2969,7 @@ public final class ActivityThread extends ClientTransactionHandler { } ContextImpl appContext = ContextImpl.createActivityContext( - this, r.loadedApk, r.activityInfo, r.token, displayId, r.overrideConfig); + this, r.packageInfo, r.activityInfo, r.token, displayId, r.overrideConfig); final DisplayManagerGlobal dm = DisplayManagerGlobal.getInstance(); // For debugging purposes, if the activity's package name contains the value of @@ -2977,7 +2977,7 @@ public final class ActivityThread extends ClientTransactionHandler { // its content on a secondary display if there is one. String pkgName = SystemProperties.get("debug.second-display.pkg"); if (pkgName != null && !pkgName.isEmpty() - && r.loadedApk.mPackageName.contains(pkgName)) { + && r.packageInfo.mPackageName.contains(pkgName)) { for (int id : dm.getDisplayIds()) { if (id != Display.DEFAULT_DISPLAY) { Display display = @@ -3309,7 +3309,7 @@ public final class ActivityThread extends ClientTransactionHandler { String component = data.intent.getComponent().getClassName(); - LoadedApk loadedApk = getLoadedApkNoCheck( + LoadedApk packageInfo = getPackageInfoNoCheck( data.info.applicationInfo, data.compatInfo); IActivityManager mgr = ActivityManager.getService(); @@ -3318,7 +3318,7 @@ public final class ActivityThread extends ClientTransactionHandler { BroadcastReceiver receiver; ContextImpl context; try { - app = loadedApk.makeApplication(false, mInstrumentation); + app = packageInfo.makeApplication(false, mInstrumentation); context = (ContextImpl) app.getBaseContext(); if (data.info.splitName != null) { context = (ContextImpl) context.createContextForSplit(data.info.splitName); @@ -3327,7 +3327,7 @@ public final class ActivityThread extends ClientTransactionHandler { data.intent.setExtrasClassLoader(cl); data.intent.prepareToEnterProcess(); data.setExtrasClassLoader(cl); - receiver = loadedApk.getAppFactory() + receiver = packageInfo.getAppFactory() .instantiateReceiver(cl, data.info.name, data.intent); } catch (Exception e) { if (DEBUG_BROADCAST) Slog.i(TAG, @@ -3343,9 +3343,9 @@ public final class ActivityThread extends ClientTransactionHandler { TAG, "Performing receive of " + data.intent + ": app=" + app + ", appName=" + app.getPackageName() - + ", pkg=" + loadedApk.getPackageName() + + ", pkg=" + packageInfo.getPackageName() + ", comp=" + data.intent.getComponent().toShortString() - + ", dir=" + loadedApk.getAppDir()); + + ", dir=" + packageInfo.getAppDir()); sCurrentBroadcastIntent.set(data.intent); receiver.setPendingResult(data); @@ -3390,8 +3390,8 @@ public final class ActivityThread extends ClientTransactionHandler { unscheduleGcIdler(); // instantiate the BackupAgent class named in the manifest - LoadedApk loadedApk = getLoadedApkNoCheck(data.appInfo, data.compatInfo); - String packageName = loadedApk.mPackageName; + LoadedApk packageInfo = getPackageInfoNoCheck(data.appInfo, data.compatInfo); + String packageName = packageInfo.mPackageName; if (packageName == null) { Slog.d(TAG, "Asked to create backup agent for nonexistent package"); return; @@ -3417,11 +3417,11 @@ public final class ActivityThread extends ClientTransactionHandler { try { if (DEBUG_BACKUP) Slog.v(TAG, "Initializing agent class " + classname); - java.lang.ClassLoader cl = loadedApk.getClassLoader(); + java.lang.ClassLoader cl = packageInfo.getClassLoader(); agent = (BackupAgent) cl.loadClass(classname).newInstance(); // set up the agent's context - ContextImpl context = ContextImpl.createAppContext(this, loadedApk); + ContextImpl context = ContextImpl.createAppContext(this, packageInfo); context.setOuterContext(agent); agent.attach(context); @@ -3457,8 +3457,8 @@ public final class ActivityThread extends ClientTransactionHandler { private void handleDestroyBackupAgent(CreateBackupAgentData data) { if (DEBUG_BACKUP) Slog.v(TAG, "handleDestroyBackupAgent: " + data); - LoadedApk loadedApk = getLoadedApkNoCheck(data.appInfo, data.compatInfo); - String packageName = loadedApk.mPackageName; + LoadedApk packageInfo = getPackageInfoNoCheck(data.appInfo, data.compatInfo); + String packageName = packageInfo.mPackageName; BackupAgent agent = mBackupAgents.get(packageName); if (agent != null) { try { @@ -3478,12 +3478,12 @@ public final class ActivityThread extends ClientTransactionHandler { // we are back active so skip it. unscheduleGcIdler(); - LoadedApk loadedApk = getLoadedApkNoCheck( + LoadedApk packageInfo = getPackageInfoNoCheck( data.info.applicationInfo, data.compatInfo); Service service = null; try { - java.lang.ClassLoader cl = loadedApk.getClassLoader(); - service = loadedApk.getAppFactory() + java.lang.ClassLoader cl = packageInfo.getClassLoader(); + service = packageInfo.getAppFactory() .instantiateService(cl, data.info.name, data.intent); } catch (Exception e) { if (!mInstrumentation.onException(service, e)) { @@ -3496,10 +3496,10 @@ public final class ActivityThread extends ClientTransactionHandler { try { if (localLOGV) Slog.v(TAG, "Creating service " + data.info.name); - ContextImpl context = ContextImpl.createAppContext(this, loadedApk); + ContextImpl context = ContextImpl.createAppContext(this, packageInfo); context.setOuterContext(service); - Application app = loadedApk.makeApplication(false, mInstrumentation); + Application app = packageInfo.makeApplication(false, mInstrumentation); service.attach(context, this, data.info.name, data.token, app, ActivityManager.getService()); service.onCreate(); @@ -3969,8 +3969,7 @@ public final class ActivityThread extends ClientTransactionHandler { } r.activity.mConfigChangeFlags |= configChanges; - performPauseActivity(token, finished, r.isPreHoneycomb(), "handlePauseActivity", - pendingActions); + performPauseActivity(r, finished, "handlePauseActivity", pendingActions); // Make sure any pending writes are now committed. if (r.isPreHoneycomb()) { @@ -3993,16 +3992,18 @@ public final class ActivityThread extends ClientTransactionHandler { mInstrumentation.callActivityOnUserLeaving(r.activity); } - final Bundle performPauseActivity(IBinder token, boolean finished, - boolean saveState, String reason, PendingTransactionActions pendingActions) { + final Bundle performPauseActivity(IBinder token, boolean finished, String reason, + PendingTransactionActions pendingActions) { ActivityClientRecord r = mActivities.get(token); - return r != null - ? performPauseActivity(r, finished, saveState, reason, pendingActions) - : null; + return r != null ? performPauseActivity(r, finished, reason, pendingActions) : null; } - private Bundle performPauseActivity(ActivityClientRecord r, boolean finished, boolean saveState, - String reason, PendingTransactionActions pendingActions) { + /** + * Pause the activity. + * @return Saved instance state for pre-Honeycomb apps if it was saved, {@code null} otherwise. + */ + private Bundle performPauseActivity(ActivityClientRecord r, boolean finished, String reason, + PendingTransactionActions pendingActions) { if (r.paused) { if (r.activity.mFinished) { // If we are finishing, we won't call onResume() in certain cases. @@ -4019,9 +4020,10 @@ public final class ActivityThread extends ClientTransactionHandler { r.activity.mFinished = true; } - // Next have the activity save its current state and managed dialogs... - if (!r.activity.mFinished && saveState) { - callCallActivityOnSaveInstanceState(r); + // Pre-Honeycomb apps always save their state before pausing + final boolean shouldSaveState = !r.activity.mFinished && r.isPreHoneycomb(); + if (shouldSaveState) { + callActivityOnSaveInstanceState(r); } performPauseActivityIfNeeded(r, reason); @@ -4048,7 +4050,7 @@ public final class ActivityThread extends ClientTransactionHandler { } } - return !r.activity.mFinished && saveState ? r.state : null; + return shouldSaveState ? r.state : null; } private void performPauseActivityIfNeeded(ActivityClientRecord r, String reason) { @@ -4149,30 +4151,40 @@ public final class ActivityThread extends ClientTransactionHandler { } } - // Next have the activity save its current state and managed dialogs... - if (!r.activity.mFinished && saveState) { - if (r.state == null) { - callCallActivityOnSaveInstanceState(r); - } + if (!keepShown) { + callActivityOnStop(r, saveState, reason); } + } + } - if (!keepShown) { - try { - // Now we are idle. - r.activity.performStop(false /*preserveWindow*/); - } catch (Exception e) { - if (!mInstrumentation.onException(r.activity, e)) { - throw new RuntimeException( - "Unable to stop activity " + /** + * Calls {@link Activity#onStop()} and {@link Activity#onSaveInstanceState(Bundle)}, and updates + * the client record's state. + * All calls to stop an activity must be done through this method to make sure that + * {@link Activity#onSaveInstanceState(Bundle)} is also executed in the same call. + */ + private void callActivityOnStop(ActivityClientRecord r, boolean saveState, String reason) { + final boolean shouldSaveState = saveState && !r.activity.mFinished && r.state == null + && !r.isPreHoneycomb(); + if (shouldSaveState) { + callActivityOnSaveInstanceState(r); + } + + try { + r.activity.performStop(false /*preserveWindow*/); + } catch (SuperNotCalledException e) { + throw e; + } catch (Exception e) { + if (!mInstrumentation.onException(r.activity, e)) { + throw new RuntimeException( + "Unable to stop activity " + r.intent.getComponent().toShortString() + ": " + e.toString(), e); - } - } - r.setState(ON_STOP); - EventLog.writeEvent(LOG_AM_ON_STOP_CALLED, UserHandle.myUserId(), - r.activity.getComponentName().getClassName(), reason); } } + r.setState(ON_STOP); + EventLog.writeEvent(LOG_AM_ON_STOP_CALLED, UserHandle.myUserId(), + r.activity.getComponentName().getClassName(), reason); } private void updateVisibility(ActivityClientRecord r, boolean show) { @@ -4292,24 +4304,7 @@ public final class ActivityThread extends ClientTransactionHandler { if (sleeping) { if (!r.stopped && !r.isPreHoneycomb()) { - if (!r.activity.mFinished && r.state == null) { - callCallActivityOnSaveInstanceState(r); - } - - try { - // Now we are idle. - r.activity.performStop(false /*preserveWindow*/); - } catch (Exception e) { - if (!mInstrumentation.onException(r.activity, e)) { - throw new RuntimeException( - "Unable to stop activity " - + r.intent.getComponent().toShortString() - + ": " + e.toString(), e); - } - } - r.setState(ON_STOP); - EventLog.writeEvent(LOG_AM_ON_STOP_CALLED, UserHandle.myUserId(), - r.activity.getComponentName().getClassName(), "sleeping"); + callActivityOnStop(r, true /* saveState */, "sleeping"); } // Make sure any pending writes are now committed. @@ -4363,11 +4358,11 @@ public final class ActivityThread extends ClientTransactionHandler { } private void handleUpdatePackageCompatibilityInfo(UpdateCompatibilityData data) { - LoadedApk apk = peekLoadedApk(data.pkg, false); + LoadedApk apk = peekPackageInfo(data.pkg, false); if (apk != null) { apk.setCompatibilityInfo(data.info); } - apk = peekLoadedApk(data.pkg, true); + apk = peekPackageInfo(data.pkg, true); if (apk != null) { apk.setCompatibilityInfo(data.info); } @@ -4459,21 +4454,7 @@ public final class ActivityThread extends ClientTransactionHandler { performPauseActivityIfNeeded(r, "destroy"); if (!r.stopped) { - try { - r.activity.performStop(r.mPreserveWindow); - } catch (SuperNotCalledException e) { - throw e; - } catch (Exception e) { - if (!mInstrumentation.onException(r.activity, e)) { - throw new RuntimeException( - "Unable to stop activity " - + safeToComponentShortString(r.intent) - + ": " + e.toString(), e); - } - } - r.setState(ON_STOP); - EventLog.writeEvent(LOG_AM_ON_STOP_CALLED, UserHandle.myUserId(), - r.activity.getComponentName().getClassName(), "destroy"); + callActivityOnStop(r, false /* saveState */, "destroy"); } if (getNonConfigInstance) { try { @@ -4779,11 +4760,11 @@ public final class ActivityThread extends ClientTransactionHandler { // Need to ensure state is saved. if (!r.paused) { - performPauseActivity(r.token, false, r.isPreHoneycomb(), "handleRelaunchActivity", + performPauseActivity(r, false, "handleRelaunchActivity", null /* pendingActions */); } - if (r.state == null && !r.stopped && !r.isPreHoneycomb()) { - callCallActivityOnSaveInstanceState(r); + if (!r.stopped) { + callActivityOnStop(r, true /* saveState */, "handleRelaunchActivity"); } handleDestroyActivity(r.token, false, configChanges, true); @@ -4816,8 +4797,7 @@ public final class ActivityThread extends ClientTransactionHandler { handleStartActivity(r, pendingActions); handleResumeActivity(r.token, false /* clearHide */, r.isForward, "relaunch"); if (r.startsNotResumed) { - performPauseActivity(r, false /* finished */, r.isPreHoneycomb(), "relaunch", - pendingActions); + performPauseActivity(r, false /* finished */, "relaunch", pendingActions); } if (!tmp.onlyLocalRequest) { @@ -4832,7 +4812,7 @@ public final class ActivityThread extends ClientTransactionHandler { } } - private void callCallActivityOnSaveInstanceState(ActivityClientRecord r) { + private void callActivityOnSaveInstanceState(ActivityClientRecord r) { r.state = new Bundle(); r.state.setAllowFds(false); if (r.isPersistable()) { @@ -4861,7 +4841,7 @@ public final class ActivityThread extends ClientTransactionHandler { if (a != null) { Configuration thisConfig = applyConfigCompatMainThread( mCurDefaultDisplayDpi, newConfig, - ar.loadedApk.getCompatibilityInfo()); + ar.packageInfo.getCompatibilityInfo()); if (!ar.activity.mFinished && (allActivities || !ar.paused)) { // If the activity is currently resumed, its configuration // needs to change right now. @@ -5347,7 +5327,7 @@ public final class ActivityThread extends ClientTransactionHandler { } final void handleDispatchPackageBroadcast(int cmd, String[] packages) { - boolean hasLoadedApk = false; + boolean hasPkgInfo = false; switch (cmd) { case ApplicationThreadConstants.PACKAGE_REMOVED: case ApplicationThreadConstants.PACKAGE_REMOVED_DONT_KILL: @@ -5358,14 +5338,14 @@ public final class ActivityThread extends ClientTransactionHandler { } synchronized (mResourcesManager) { for (int i = packages.length - 1; i >= 0; i--) { - if (!hasLoadedApk) { + if (!hasPkgInfo) { WeakReference<LoadedApk> ref = mPackages.get(packages[i]); if (ref != null && ref.get() != null) { - hasLoadedApk = true; + hasPkgInfo = true; } else { ref = mResourcePackages.get(packages[i]); if (ref != null && ref.get() != null) { - hasLoadedApk = true; + hasPkgInfo = true; } } } @@ -5385,21 +5365,21 @@ public final class ActivityThread extends ClientTransactionHandler { synchronized (mResourcesManager) { for (int i = packages.length - 1; i >= 0; i--) { WeakReference<LoadedApk> ref = mPackages.get(packages[i]); - LoadedApk loadedApk = ref != null ? ref.get() : null; - if (loadedApk != null) { - hasLoadedApk = true; + LoadedApk pkgInfo = ref != null ? ref.get() : null; + if (pkgInfo != null) { + hasPkgInfo = true; } else { ref = mResourcePackages.get(packages[i]); - loadedApk = ref != null ? ref.get() : null; - if (loadedApk != null) { - hasLoadedApk = true; + pkgInfo = ref != null ? ref.get() : null; + if (pkgInfo != null) { + hasPkgInfo = true; } } // If the package is being replaced, yet it still has a valid // LoadedApk object, the package was updated with _DONT_KILL. // Adjust it's internal references to the application info and // resources. - if (loadedApk != null) { + if (pkgInfo != null) { try { final String packageName = packages[i]; final ApplicationInfo aInfo = @@ -5413,13 +5393,13 @@ public final class ActivityThread extends ClientTransactionHandler { if (ar.activityInfo.applicationInfo.packageName .equals(packageName)) { ar.activityInfo.applicationInfo = aInfo; - ar.loadedApk = loadedApk; + ar.packageInfo = pkgInfo; } } } final List<String> oldPaths = sPackageManager.getPreviousCodePaths(packageName); - loadedApk.updateApplicationInfo(aInfo, oldPaths); + pkgInfo.updateApplicationInfo(aInfo, oldPaths); } catch (RemoteException e) { } } @@ -5428,7 +5408,7 @@ public final class ActivityThread extends ClientTransactionHandler { break; } } - ApplicationPackageManager.handlePackageBroadcast(cmd, packages, hasLoadedApk); + ApplicationPackageManager.handlePackageBroadcast(cmd, packages, hasPkgInfo); } final void handleLowMemory() { @@ -5636,10 +5616,10 @@ public final class ActivityThread extends ClientTransactionHandler { applyCompatConfiguration(mCurDefaultDisplayDpi); } - data.loadedApk = getLoadedApkNoCheck(data.appInfo, data.compatInfo); + data.info = getPackageInfoNoCheck(data.appInfo, data.compatInfo); if (agent != null) { - handleAttachAgent(agent, data.loadedApk); + handleAttachAgent(agent, data.info); } /** @@ -5684,7 +5664,7 @@ public final class ActivityThread extends ClientTransactionHandler { // XXX should have option to change the port. Debug.changeDebugPort(8100); if (data.debugMode == ApplicationThreadConstants.DEBUG_WAIT) { - Slog.w(TAG, "Application " + data.loadedApk.getPackageName() + Slog.w(TAG, "Application " + data.info.getPackageName() + " is waiting for the debugger on port 8100..."); IActivityManager mgr = ActivityManager.getService(); @@ -5703,7 +5683,7 @@ public final class ActivityThread extends ClientTransactionHandler { } } else { - Slog.w(TAG, "Application " + data.loadedApk.getPackageName() + Slog.w(TAG, "Application " + data.info.getPackageName() + " can be debugged on port 8100..."); } } @@ -5751,14 +5731,14 @@ public final class ActivityThread extends ClientTransactionHandler { mInstrumentationAppDir = ii.sourceDir; mInstrumentationSplitAppDirs = ii.splitSourceDirs; mInstrumentationLibDir = getInstrumentationLibrary(data.appInfo, ii); - mInstrumentedAppDir = data.loadedApk.getAppDir(); - mInstrumentedSplitAppDirs = data.loadedApk.getSplitAppDirs(); - mInstrumentedLibDir = data.loadedApk.getLibDir(); + mInstrumentedAppDir = data.info.getAppDir(); + mInstrumentedSplitAppDirs = data.info.getSplitAppDirs(); + mInstrumentedLibDir = data.info.getLibDir(); } else { ii = null; } - final ContextImpl appContext = ContextImpl.createAppContext(this, data.loadedApk); + final ContextImpl appContext = ContextImpl.createAppContext(this, data.info); updateLocaleListFromAppContext(appContext, mResourcesManager.getConfiguration().getLocales()); @@ -5802,9 +5782,9 @@ public final class ActivityThread extends ClientTransactionHandler { } ii.copyTo(instrApp); instrApp.initForUser(UserHandle.myUserId()); - final LoadedApk loadedApk = getLoadedApk(instrApp, data.compatInfo, + final LoadedApk pi = getPackageInfo(instrApp, data.compatInfo, appContext.getClassLoader(), false, true, false); - final ContextImpl instrContext = ContextImpl.createAppContext(this, loadedApk); + final ContextImpl instrContext = ContextImpl.createAppContext(this, pi); try { final ClassLoader cl = instrContext.getClassLoader(); @@ -5849,7 +5829,7 @@ public final class ActivityThread extends ClientTransactionHandler { try { // If the app is being launched for full backup or restore, bring it up in // a restricted environment with the base application class. - app = data.loadedApk.makeApplication(data.restrictedBackupMode, null); + app = data.info.makeApplication(data.restrictedBackupMode, null); mInitialApplication = app; // don't bring up providers in restricted mode; they may depend on the @@ -5903,7 +5883,7 @@ public final class ActivityThread extends ClientTransactionHandler { final int preloadedFontsResource = info.metaData.getInt( ApplicationInfo.METADATA_PRELOADED_FONTS, 0); if (preloadedFontsResource != 0) { - data.loadedApk.getResources().preloadFonts(preloadedFontsResource); + data.info.getResources().preloadFonts(preloadedFontsResource); } } } catch (RemoteException e) { @@ -6361,12 +6341,12 @@ public final class ActivityThread extends ClientTransactionHandler { try { final java.lang.ClassLoader cl = c.getClassLoader(); - LoadedApk loadedApk = peekLoadedApk(ai.packageName, true); - if (loadedApk == null) { + LoadedApk packageInfo = peekPackageInfo(ai.packageName, true); + if (packageInfo == null) { // System startup case. - loadedApk = getSystemContext().mLoadedApk; + packageInfo = getSystemContext().mPackageInfo; } - localProvider = loadedApk.getAppFactory() + localProvider = packageInfo.getAppFactory() .instantiateProvider(cl, info.name); provider = localProvider.getIContentProvider(); if (provider == null) { @@ -6515,8 +6495,8 @@ public final class ActivityThread extends ClientTransactionHandler { mInstrumentation = new Instrumentation(); mInstrumentation.basicInit(this); ContextImpl context = ContextImpl.createAppContext( - this, getSystemContext().mLoadedApk); - mInitialApplication = context.mLoadedApk.makeApplication(true, null); + this, getSystemContext().mPackageInfo); + mInitialApplication = context.mPackageInfo.makeApplication(true, null); mInitialApplication.onCreate(); } catch (Exception e) { throw new RuntimeException( diff --git a/core/java/android/app/Application.java b/core/java/android/app/Application.java index 5a739ea6d3e4..a13ac49bb553 100644 --- a/core/java/android/app/Application.java +++ b/core/java/android/app/Application.java @@ -197,7 +197,7 @@ public class Application extends ContextWrapper implements ComponentCallbacks2 { */ /* package */ final void attach(Context context) { attachBaseContext(context); - mLoadedApk = ContextImpl.getImpl(context).mLoadedApk; + mLoadedApk = ContextImpl.getImpl(context).mPackageInfo; } /* package */ void dispatchActivityCreated(Activity activity, Bundle savedInstanceState) { diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java index cc68c0518b40..6b0a2f90c38d 100644 --- a/core/java/android/app/ApplicationPackageManager.java +++ b/core/java/android/app/ApplicationPackageManager.java @@ -1409,7 +1409,7 @@ public class ApplicationPackageManager extends PackageManager { sameUid ? app.sourceDir : app.publicSourceDir, sameUid ? app.splitSourceDirs : app.splitPublicSourceDirs, app.resourceDirs, app.sharedLibraryFiles, Display.DEFAULT_DISPLAY, - mContext.mLoadedApk); + mContext.mPackageInfo); if (r != null) { return r; } diff --git a/core/java/android/app/ClientTransactionHandler.java b/core/java/android/app/ClientTransactionHandler.java index 0f66652af76c..5b61fdf8677f 100644 --- a/core/java/android/app/ClientTransactionHandler.java +++ b/core/java/android/app/ClientTransactionHandler.java @@ -111,7 +111,7 @@ public abstract class ClientTransactionHandler { PendingTransactionActions pendingActions); /** Get package info. */ - public abstract LoadedApk getLoadedApkNoCheck(ApplicationInfo ai, + public abstract LoadedApk getPackageInfoNoCheck(ApplicationInfo ai, CompatibilityInfo compatInfo); /** Deliver app configuration change notification. */ diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java index ea940426f5a5..6496110ac9c6 100644 --- a/core/java/android/app/ContextImpl.java +++ b/core/java/android/app/ContextImpl.java @@ -159,7 +159,7 @@ class ContextImpl extends Context { private ArrayMap<String, File> mSharedPrefsPaths; final @NonNull ActivityThread mMainThread; - final @NonNull LoadedApk mLoadedApk; + final @NonNull LoadedApk mPackageInfo; private @Nullable ClassLoader mClassLoader; private final @Nullable IBinder mActivityToken; @@ -257,8 +257,8 @@ class ContextImpl extends Context { @Override public Context getApplicationContext() { - return (mLoadedApk != null) ? - mLoadedApk.getApplication() : mMainThread.getApplication(); + return (mPackageInfo != null) ? + mPackageInfo.getApplication() : mMainThread.getApplication(); } @Override @@ -302,15 +302,15 @@ class ContextImpl extends Context { @Override public ClassLoader getClassLoader() { - return mClassLoader != null ? mClassLoader : (mLoadedApk != null ? mLoadedApk.getClassLoader() : ClassLoader.getSystemClassLoader()); + return mClassLoader != null ? mClassLoader : (mPackageInfo != null ? mPackageInfo.getClassLoader() : ClassLoader.getSystemClassLoader()); } @Override public String getPackageName() { - if (mLoadedApk != null) { - return mLoadedApk.getPackageName(); + if (mPackageInfo != null) { + return mPackageInfo.getPackageName(); } - // No mLoadedApk means this is a Context for the system itself, + // No mPackageInfo means this is a Context for the system itself, // and this here is its name. return "android"; } @@ -329,24 +329,24 @@ class ContextImpl extends Context { @Override public ApplicationInfo getApplicationInfo() { - if (mLoadedApk != null) { - return mLoadedApk.getApplicationInfo(); + if (mPackageInfo != null) { + return mPackageInfo.getApplicationInfo(); } throw new RuntimeException("Not supported in system context"); } @Override public String getPackageResourcePath() { - if (mLoadedApk != null) { - return mLoadedApk.getResDir(); + if (mPackageInfo != null) { + return mPackageInfo.getResDir(); } throw new RuntimeException("Not supported in system context"); } @Override public String getPackageCodePath() { - if (mLoadedApk != null) { - return mLoadedApk.getAppDir(); + if (mPackageInfo != null) { + return mPackageInfo.getAppDir(); } throw new RuntimeException("Not supported in system context"); } @@ -356,7 +356,7 @@ class ContextImpl extends Context { // At least one application in the world actually passes in a null // name. This happened to work because when we generated the file name // we would stringify it to "null.xml". Nice. - if (mLoadedApk.getApplicationInfo().targetSdkVersion < + if (mPackageInfo.getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.KITKAT) { if (name == null) { name = "null"; @@ -1104,11 +1104,11 @@ class ContextImpl extends Context { warnIfCallingFromSystemProcess(); IIntentReceiver rd = null; if (resultReceiver != null) { - if (mLoadedApk != null) { + if (mPackageInfo != null) { if (scheduler == null) { scheduler = mMainThread.getHandler(); } - rd = mLoadedApk.getReceiverDispatcher( + rd = mPackageInfo.getReceiverDispatcher( resultReceiver, getOuterContext(), scheduler, mMainThread.getInstrumentation(), false); } else { @@ -1208,11 +1208,11 @@ class ContextImpl extends Context { Handler scheduler, int initialCode, String initialData, Bundle initialExtras) { IIntentReceiver rd = null; if (resultReceiver != null) { - if (mLoadedApk != null) { + if (mPackageInfo != null) { if (scheduler == null) { scheduler = mMainThread.getHandler(); } - rd = mLoadedApk.getReceiverDispatcher( + rd = mPackageInfo.getReceiverDispatcher( resultReceiver, getOuterContext(), scheduler, mMainThread.getInstrumentation(), false); } else { @@ -1262,11 +1262,11 @@ class ContextImpl extends Context { warnIfCallingFromSystemProcess(); IIntentReceiver rd = null; if (resultReceiver != null) { - if (mLoadedApk != null) { + if (mPackageInfo != null) { if (scheduler == null) { scheduler = mMainThread.getHandler(); } - rd = mLoadedApk.getReceiverDispatcher( + rd = mPackageInfo.getReceiverDispatcher( resultReceiver, getOuterContext(), scheduler, mMainThread.getInstrumentation(), false); } else { @@ -1344,11 +1344,11 @@ class ContextImpl extends Context { Bundle initialExtras) { IIntentReceiver rd = null; if (resultReceiver != null) { - if (mLoadedApk != null) { + if (mPackageInfo != null) { if (scheduler == null) { scheduler = mMainThread.getHandler(); } - rd = mLoadedApk.getReceiverDispatcher( + rd = mPackageInfo.getReceiverDispatcher( resultReceiver, getOuterContext(), scheduler, mMainThread.getInstrumentation(), false); } else { @@ -1425,11 +1425,11 @@ class ContextImpl extends Context { Handler scheduler, Context context, int flags) { IIntentReceiver rd = null; if (receiver != null) { - if (mLoadedApk != null && context != null) { + if (mPackageInfo != null && context != null) { if (scheduler == null) { scheduler = mMainThread.getHandler(); } - rd = mLoadedApk.getReceiverDispatcher( + rd = mPackageInfo.getReceiverDispatcher( receiver, context, scheduler, mMainThread.getInstrumentation(), true); } else { @@ -1456,8 +1456,8 @@ class ContextImpl extends Context { @Override public void unregisterReceiver(BroadcastReceiver receiver) { - if (mLoadedApk != null) { - IIntentReceiver rd = mLoadedApk.forgetReceiverDispatcher( + if (mPackageInfo != null) { + IIntentReceiver rd = mPackageInfo.forgetReceiverDispatcher( getOuterContext(), receiver); try { ActivityManager.getService().unregisterReceiver(rd); @@ -1590,7 +1590,7 @@ class ContextImpl extends Context { @Override public IServiceConnection getServiceDispatcher(ServiceConnection conn, Handler handler, int flags) { - return mLoadedApk.getServiceDispatcher(conn, getOuterContext(), handler, flags); + return mPackageInfo.getServiceDispatcher(conn, getOuterContext(), handler, flags); } /** @hide */ @@ -1612,16 +1612,16 @@ class ContextImpl extends Context { if (conn == null) { throw new IllegalArgumentException("connection is null"); } - if (mLoadedApk != null) { - sd = mLoadedApk.getServiceDispatcher(conn, getOuterContext(), handler, flags); + if (mPackageInfo != null) { + sd = mPackageInfo.getServiceDispatcher(conn, getOuterContext(), handler, flags); } else { throw new RuntimeException("Not supported in system context"); } validateServiceIntent(service); try { IBinder token = getActivityToken(); - if (token == null && (flags&BIND_AUTO_CREATE) == 0 && mLoadedApk != null - && mLoadedApk.getApplicationInfo().targetSdkVersion + if (token == null && (flags&BIND_AUTO_CREATE) == 0 && mPackageInfo != null + && mPackageInfo.getApplicationInfo().targetSdkVersion < android.os.Build.VERSION_CODES.ICE_CREAM_SANDWICH) { flags |= BIND_WAIVE_PRIORITY; } @@ -1645,8 +1645,8 @@ class ContextImpl extends Context { if (conn == null) { throw new IllegalArgumentException("connection is null"); } - if (mLoadedApk != null) { - IServiceConnection sd = mLoadedApk.forgetServiceDispatcher( + if (mPackageInfo != null) { + IServiceConnection sd = mPackageInfo.forgetServiceDispatcher( getOuterContext(), conn); try { ActivityManager.getService().unbindService(sd); @@ -1991,20 +1991,40 @@ class ContextImpl extends Context { } } + private static Resources createResources(IBinder activityToken, LoadedApk pi, String splitName, + int displayId, Configuration overrideConfig, CompatibilityInfo compatInfo) { + final String[] splitResDirs; + final ClassLoader classLoader; + try { + splitResDirs = pi.getSplitPaths(splitName); + classLoader = pi.getSplitClassLoader(splitName); + } catch (NameNotFoundException e) { + throw new RuntimeException(e); + } + return ResourcesManager.getInstance().getResources(activityToken, + pi.getResDir(), + splitResDirs, + pi.getOverlayDirs(), + pi.getApplicationInfo().sharedLibraryFiles, + displayId, + overrideConfig, + compatInfo, + classLoader); + } + @Override public Context createApplicationContext(ApplicationInfo application, int flags) throws NameNotFoundException { - LoadedApk loadedApk = mMainThread.getLoadedApk(application, - mResources.getCompatibilityInfo(), + LoadedApk pi = mMainThread.getPackageInfo(application, mResources.getCompatibilityInfo(), flags | CONTEXT_REGISTER_PACKAGE); - if (loadedApk != null) { - ContextImpl c = new ContextImpl(this, mMainThread, loadedApk, null, mActivityToken, + if (pi != null) { + ContextImpl c = new ContextImpl(this, mMainThread, pi, null, mActivityToken, new UserHandle(UserHandle.getUserId(application.uid)), flags, null); final int displayId = mDisplay != null ? mDisplay.getDisplayId() : Display.DEFAULT_DISPLAY; - c.setResources(loadedApk.createResources(mActivityToken, null, displayId, null, + c.setResources(createResources(mActivityToken, pi, null, displayId, null, getDisplayAdjustments(displayId).getCompatibilityInfo())); if (c.mResources != null) { return c; @@ -2028,21 +2048,20 @@ class ContextImpl extends Context { if (packageName.equals("system") || packageName.equals("android")) { // The system resources are loaded in every application, so we can safely copy // the context without reloading Resources. - return new ContextImpl(this, mMainThread, mLoadedApk, null, mActivityToken, user, + return new ContextImpl(this, mMainThread, mPackageInfo, null, mActivityToken, user, flags, null); } - LoadedApk loadedApk = mMainThread.getLoadedApkForPackageName(packageName, - mResources.getCompatibilityInfo(), + LoadedApk pi = mMainThread.getPackageInfo(packageName, mResources.getCompatibilityInfo(), flags | CONTEXT_REGISTER_PACKAGE, user.getIdentifier()); - if (loadedApk != null) { - ContextImpl c = new ContextImpl(this, mMainThread, loadedApk, null, mActivityToken, user, + if (pi != null) { + ContextImpl c = new ContextImpl(this, mMainThread, pi, null, mActivityToken, user, flags, null); final int displayId = mDisplay != null ? mDisplay.getDisplayId() : Display.DEFAULT_DISPLAY; - c.setResources(loadedApk.createResources(mActivityToken, null, displayId, null, + c.setResources(createResources(mActivityToken, pi, null, displayId, null, getDisplayAdjustments(displayId).getCompatibilityInfo())); if (c.mResources != null) { return c; @@ -2056,21 +2075,30 @@ class ContextImpl extends Context { @Override public Context createContextForSplit(String splitName) throws NameNotFoundException { - if (!mLoadedApk.getApplicationInfo().requestsIsolatedSplitLoading()) { + if (!mPackageInfo.getApplicationInfo().requestsIsolatedSplitLoading()) { // All Splits are always loaded. return this; } - final ClassLoader classLoader = mLoadedApk.getSplitClassLoader(splitName); + final ClassLoader classLoader = mPackageInfo.getSplitClassLoader(splitName); + final String[] paths = mPackageInfo.getSplitPaths(splitName); - final ContextImpl context = new ContextImpl(this, mMainThread, mLoadedApk, splitName, + final ContextImpl context = new ContextImpl(this, mMainThread, mPackageInfo, splitName, mActivityToken, mUser, mFlags, classLoader); final int displayId = mDisplay != null ? mDisplay.getDisplayId() : Display.DEFAULT_DISPLAY; - context.setResources(mLoadedApk.getOrCreateResourcesForSplit(splitName, - mActivityToken, displayId)); + context.setResources(ResourcesManager.getInstance().getResources( + mActivityToken, + mPackageInfo.getResDir(), + paths, + mPackageInfo.getOverlayDirs(), + mPackageInfo.getApplicationInfo().sharedLibraryFiles, + displayId, + null, + mPackageInfo.getCompatibilityInfo(), + classLoader)); return context; } @@ -2080,11 +2108,11 @@ class ContextImpl extends Context { throw new IllegalArgumentException("overrideConfiguration must not be null"); } - ContextImpl context = new ContextImpl(this, mMainThread, mLoadedApk, mSplitName, + ContextImpl context = new ContextImpl(this, mMainThread, mPackageInfo, mSplitName, mActivityToken, mUser, mFlags, mClassLoader); final int displayId = mDisplay != null ? mDisplay.getDisplayId() : Display.DEFAULT_DISPLAY; - context.setResources(mLoadedApk.createResources(mActivityToken, mSplitName, displayId, + context.setResources(createResources(mActivityToken, mPackageInfo, mSplitName, displayId, overrideConfiguration, getDisplayAdjustments(displayId).getCompatibilityInfo())); return context; } @@ -2095,11 +2123,11 @@ class ContextImpl extends Context { throw new IllegalArgumentException("display must not be null"); } - ContextImpl context = new ContextImpl(this, mMainThread, mLoadedApk, mSplitName, + ContextImpl context = new ContextImpl(this, mMainThread, mPackageInfo, mSplitName, mActivityToken, mUser, mFlags, mClassLoader); final int displayId = display.getDisplayId(); - context.setResources(mLoadedApk.createResources(mActivityToken, mSplitName, displayId, + context.setResources(createResources(mActivityToken, mPackageInfo, mSplitName, displayId, null, getDisplayAdjustments(displayId).getCompatibilityInfo())); context.mDisplay = display; return context; @@ -2109,7 +2137,7 @@ class ContextImpl extends Context { public Context createDeviceProtectedStorageContext() { final int flags = (mFlags & ~Context.CONTEXT_CREDENTIAL_PROTECTED_STORAGE) | Context.CONTEXT_DEVICE_PROTECTED_STORAGE; - return new ContextImpl(this, mMainThread, mLoadedApk, mSplitName, mActivityToken, mUser, + return new ContextImpl(this, mMainThread, mPackageInfo, mSplitName, mActivityToken, mUser, flags, mClassLoader); } @@ -2117,7 +2145,7 @@ class ContextImpl extends Context { public Context createCredentialProtectedStorageContext() { final int flags = (mFlags & ~Context.CONTEXT_DEVICE_PROTECTED_STORAGE) | Context.CONTEXT_CREDENTIAL_PROTECTED_STORAGE; - return new ContextImpl(this, mMainThread, mLoadedApk, mSplitName, mActivityToken, mUser, + return new ContextImpl(this, mMainThread, mPackageInfo, mSplitName, mActivityToken, mUser, flags, mClassLoader); } @@ -2166,14 +2194,14 @@ class ContextImpl extends Context { @Override public File getDataDir() { - if (mLoadedApk != null) { + if (mPackageInfo != null) { File res = null; if (isCredentialProtectedStorage()) { - res = mLoadedApk.getCredentialProtectedDataDirFile(); + res = mPackageInfo.getCredentialProtectedDataDirFile(); } else if (isDeviceProtectedStorage()) { - res = mLoadedApk.getDeviceProtectedDataDirFile(); + res = mPackageInfo.getDeviceProtectedDataDirFile(); } else { - res = mLoadedApk.getDataDirFile(); + res = mPackageInfo.getDataDirFile(); } if (res != null) { @@ -2224,10 +2252,10 @@ class ContextImpl extends Context { } static ContextImpl createSystemContext(ActivityThread mainThread) { - LoadedApk loadedApk = new LoadedApk(mainThread); - ContextImpl context = new ContextImpl(null, mainThread, loadedApk, null, null, null, 0, + LoadedApk packageInfo = new LoadedApk(mainThread); + ContextImpl context = new ContextImpl(null, mainThread, packageInfo, null, null, null, 0, null); - context.setResources(loadedApk.getResources()); + context.setResources(packageInfo.getResources()); context.mResources.updateConfiguration(context.mResourcesManager.getConfiguration(), context.mResourcesManager.getDisplayMetrics()); return context; @@ -2238,35 +2266,35 @@ class ContextImpl extends Context { * Make sure that the created system UI context shares the same LoadedApk as the system context. */ static ContextImpl createSystemUiContext(ContextImpl systemContext) { - final LoadedApk loadedApk = systemContext.mLoadedApk; - ContextImpl context = new ContextImpl(null, systemContext.mMainThread, loadedApk, null, + final LoadedApk packageInfo = systemContext.mPackageInfo; + ContextImpl context = new ContextImpl(null, systemContext.mMainThread, packageInfo, null, null, null, 0, null); - context.setResources(loadedApk.createResources(null, null, Display.DEFAULT_DISPLAY, null, - loadedApk.getCompatibilityInfo())); + context.setResources(createResources(null, packageInfo, null, Display.DEFAULT_DISPLAY, null, + packageInfo.getCompatibilityInfo())); return context; } - static ContextImpl createAppContext(ActivityThread mainThread, LoadedApk loadedApk) { - if (loadedApk == null) throw new IllegalArgumentException("loadedApk"); - ContextImpl context = new ContextImpl(null, mainThread, loadedApk, null, null, null, 0, + static ContextImpl createAppContext(ActivityThread mainThread, LoadedApk packageInfo) { + if (packageInfo == null) throw new IllegalArgumentException("packageInfo"); + ContextImpl context = new ContextImpl(null, mainThread, packageInfo, null, null, null, 0, null); - context.setResources(loadedApk.getResources()); + context.setResources(packageInfo.getResources()); return context; } static ContextImpl createActivityContext(ActivityThread mainThread, - LoadedApk loadedApk, ActivityInfo activityInfo, IBinder activityToken, int displayId, + LoadedApk packageInfo, ActivityInfo activityInfo, IBinder activityToken, int displayId, Configuration overrideConfiguration) { - if (loadedApk == null) throw new IllegalArgumentException("loadedApk"); + if (packageInfo == null) throw new IllegalArgumentException("packageInfo"); - String[] splitDirs = loadedApk.getSplitResDirs(); - ClassLoader classLoader = loadedApk.getClassLoader(); + String[] splitDirs = packageInfo.getSplitResDirs(); + ClassLoader classLoader = packageInfo.getClassLoader(); - if (loadedApk.getApplicationInfo().requestsIsolatedSplitLoading()) { + if (packageInfo.getApplicationInfo().requestsIsolatedSplitLoading()) { Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, "SplitDependencies"); try { - classLoader = loadedApk.getSplitClassLoader(activityInfo.splitName); - splitDirs = loadedApk.getSplitPaths(activityInfo.splitName); + classLoader = packageInfo.getSplitClassLoader(activityInfo.splitName); + splitDirs = packageInfo.getSplitPaths(activityInfo.splitName); } catch (NameNotFoundException e) { // Nothing above us can handle a NameNotFoundException, better crash. throw new RuntimeException(e); @@ -2275,14 +2303,14 @@ class ContextImpl extends Context { } } - ContextImpl context = new ContextImpl(null, mainThread, loadedApk, activityInfo.splitName, + ContextImpl context = new ContextImpl(null, mainThread, packageInfo, activityInfo.splitName, activityToken, null, 0, classLoader); // Clamp display ID to DEFAULT_DISPLAY if it is INVALID_DISPLAY. displayId = (displayId != Display.INVALID_DISPLAY) ? displayId : Display.DEFAULT_DISPLAY; final CompatibilityInfo compatInfo = (displayId == Display.DEFAULT_DISPLAY) - ? loadedApk.getCompatibilityInfo() + ? packageInfo.getCompatibilityInfo() : CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO; final ResourcesManager resourcesManager = ResourcesManager.getInstance(); @@ -2290,10 +2318,10 @@ class ContextImpl extends Context { // Create the base resources for which all configuration contexts for this Activity // will be rebased upon. context.setResources(resourcesManager.createBaseActivityResources(activityToken, - loadedApk.getResDir(), + packageInfo.getResDir(), splitDirs, - loadedApk.getOverlayDirs(), - loadedApk.getApplicationInfo().sharedLibraryFiles, + packageInfo.getOverlayDirs(), + packageInfo.getApplicationInfo().sharedLibraryFiles, displayId, overrideConfiguration, compatInfo, @@ -2304,7 +2332,7 @@ class ContextImpl extends Context { } private ContextImpl(@Nullable ContextImpl container, @NonNull ActivityThread mainThread, - @NonNull LoadedApk loadedApk, @Nullable String splitName, + @NonNull LoadedApk packageInfo, @Nullable String splitName, @Nullable IBinder activityToken, @Nullable UserHandle user, int flags, @Nullable ClassLoader classLoader) { mOuterContext = this; @@ -2313,10 +2341,10 @@ class ContextImpl extends Context { // location for application. if ((flags & (Context.CONTEXT_CREDENTIAL_PROTECTED_STORAGE | Context.CONTEXT_DEVICE_PROTECTED_STORAGE)) == 0) { - final File dataDir = loadedApk.getDataDirFile(); - if (Objects.equals(dataDir, loadedApk.getCredentialProtectedDataDirFile())) { + final File dataDir = packageInfo.getDataDirFile(); + if (Objects.equals(dataDir, packageInfo.getCredentialProtectedDataDirFile())) { flags |= Context.CONTEXT_CREDENTIAL_PROTECTED_STORAGE; - } else if (Objects.equals(dataDir, loadedApk.getDeviceProtectedDataDirFile())) { + } else if (Objects.equals(dataDir, packageInfo.getDeviceProtectedDataDirFile())) { flags |= Context.CONTEXT_DEVICE_PROTECTED_STORAGE; } } @@ -2330,7 +2358,7 @@ class ContextImpl extends Context { } mUser = user; - mLoadedApk = loadedApk; + mPackageInfo = packageInfo; mSplitName = splitName; mClassLoader = classLoader; mResourcesManager = ResourcesManager.getInstance(); @@ -2341,8 +2369,8 @@ class ContextImpl extends Context { setResources(container.mResources); mDisplay = container.mDisplay; } else { - mBasePackageName = loadedApk.mPackageName; - ApplicationInfo ainfo = loadedApk.getApplicationInfo(); + mBasePackageName = packageInfo.mPackageName; + ApplicationInfo ainfo = packageInfo.getApplicationInfo(); if (ainfo.uid == Process.SYSTEM_UID && ainfo.uid != Process.myUid()) { // Special case: system components allow themselves to be loaded in to other // processes. For purposes of app ops, we must then consider the context as @@ -2365,7 +2393,7 @@ class ContextImpl extends Context { } void installSystemApplicationInfo(ApplicationInfo info, ClassLoader classLoader) { - mLoadedApk.installSystemApplicationInfo(info, classLoader); + mPackageInfo.installSystemApplicationInfo(info, classLoader); } final void scheduleFinalCleanup(String who, String what) { @@ -2374,7 +2402,7 @@ class ContextImpl extends Context { final void performFinalCleanup(String who, String what) { //Log.i(TAG, "Cleanup up context: " + this); - mLoadedApk.removeContextRegistrations(getOuterContext(), who, what); + mPackageInfo.removeContextRegistrations(getOuterContext(), who, what); } final Context getReceiverRestrictedContext() { diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl index 6dcecf197ed2..02be00268a45 100644 --- a/core/java/android/app/IActivityManager.aidl +++ b/core/java/android/app/IActivityManager.aidl @@ -456,11 +456,10 @@ interface IActivityManager { boolean isTopOfTask(in IBinder token); void notifyLaunchTaskBehindComplete(in IBinder token); void notifyEnterAnimationComplete(in IBinder token); - IBinder requestStartActivityPermissionToken(in IBinder delegatorToken); int startActivityAsCaller(in IApplicationThread caller, in String callingPackage, in Intent intent, in String resolvedType, in IBinder resultTo, in String resultWho, int requestCode, int flags, in ProfilerInfo profilerInfo, in Bundle options, - in IBinder permissionToken, boolean ignoreTargetSecurity, int userId); + boolean ignoreTargetSecurity, int userId); int addAppTask(in IBinder activityToken, in Intent intent, in ActivityManager.TaskDescription description, in Bitmap thumbnail); Point getAppTaskThumbnailSize(); diff --git a/core/java/android/app/Instrumentation.java b/core/java/android/app/Instrumentation.java index f90b276ac17f..67f5a2e641ae 100644 --- a/core/java/android/app/Instrumentation.java +++ b/core/java/android/app/Instrumentation.java @@ -1216,10 +1216,10 @@ public class Instrumentation { + " disabling AppComponentFactory", new Throwable()); return AppComponentFactory.DEFAULT; } - LoadedApk loadedApk = mThread.peekLoadedApk(pkg, true); + LoadedApk apk = mThread.peekPackageInfo(pkg, true); // This is in the case of starting up "android". - if (loadedApk == null) loadedApk = mThread.getSystemContext().mLoadedApk; - return loadedApk.getAppFactory(); + if (apk == null) apk = mThread.getSystemContext().mPackageInfo; + return apk.getAppFactory(); } private void prePerformCreate(Activity activity) { @@ -1879,8 +1879,8 @@ public class Instrumentation { */ public ActivityResult execStartActivityAsCaller( Context who, IBinder contextThread, IBinder token, Activity target, - Intent intent, int requestCode, Bundle options, IBinder permissionToken, - boolean ignoreTargetSecurity, int userId) { + Intent intent, int requestCode, Bundle options, boolean ignoreTargetSecurity, + int userId) { IApplicationThread whoThread = (IApplicationThread) contextThread; if (mActivityMonitors != null) { synchronized (mSync) { @@ -1911,8 +1911,7 @@ public class Instrumentation { .startActivityAsCaller(whoThread, who.getBasePackageName(), intent, intent.resolveTypeIfNeeded(who.getContentResolver()), token, target != null ? target.mEmbeddedID : null, - requestCode, 0, null, options, permissionToken, - ignoreTargetSecurity, userId); + requestCode, 0, null, options, ignoreTargetSecurity, userId); checkStartActivityResult(result, intent); } catch (RemoteException e) { throw new RuntimeException("Failure from system", e); diff --git a/core/java/android/app/LoadedApk.java b/core/java/android/app/LoadedApk.java index d24d4f341aea..ea5932cd4ae4 100644 --- a/core/java/android/app/LoadedApk.java +++ b/core/java/android/app/LoadedApk.java @@ -32,7 +32,6 @@ import android.content.pm.dex.ArtManager; import android.content.pm.split.SplitDependencyLoader; import android.content.res.AssetManager; import android.content.res.CompatibilityInfo; -import android.content.res.Configuration; import android.content.res.Resources; import android.os.Build; import android.os.Bundle; @@ -964,78 +963,14 @@ public final class LoadedApk { throw new AssertionError("null split not found"); } - mResources = ResourcesManager.getInstance().getResources( - null, - mResDir, - splitPaths, - mOverlayDirs, - mApplicationInfo.sharedLibraryFiles, - Display.DEFAULT_DISPLAY, - null, - getCompatibilityInfo(), + mResources = ResourcesManager.getInstance().getResources(null, mResDir, + splitPaths, mOverlayDirs, mApplicationInfo.sharedLibraryFiles, + Display.DEFAULT_DISPLAY, null, getCompatibilityInfo(), getClassLoader()); } return mResources; } - public Resources getOrCreateResourcesForSplit(@NonNull String splitName, - @Nullable IBinder activityToken, int displayId) throws NameNotFoundException { - return ResourcesManager.getInstance().getResources( - activityToken, - mResDir, - getSplitPaths(splitName), - mOverlayDirs, - mApplicationInfo.sharedLibraryFiles, - displayId, - null, - getCompatibilityInfo(), - getSplitClassLoader(splitName)); - } - - /** - * Creates the top level resources for the given package. Will return an existing - * Resources if one has already been created. - */ - public Resources getOrCreateTopLevelResources(@NonNull ApplicationInfo appInfo) { - // Request for this app, short circuit - if (appInfo.uid == Process.myUid()) { - return getResources(); - } - - // Get resources for a different package - return ResourcesManager.getInstance().getResources( - null, - appInfo.publicSourceDir, - appInfo.splitPublicSourceDirs, - appInfo.resourceDirs, - appInfo.sharedLibraryFiles, - Display.DEFAULT_DISPLAY, - null, - getCompatibilityInfo(), - getClassLoader()); - } - - public Resources createResources(IBinder activityToken, String splitName, - int displayId, Configuration overrideConfig, CompatibilityInfo compatInfo) { - final String[] splitResDirs; - final ClassLoader classLoader; - try { - splitResDirs = getSplitPaths(splitName); - classLoader = getSplitClassLoader(splitName); - } catch (NameNotFoundException e) { - throw new RuntimeException(e); - } - return ResourcesManager.getInstance().getResources(activityToken, - mResDir, - splitResDirs, - mOverlayDirs, - mApplicationInfo.sharedLibraryFiles, - displayId, - overrideConfig, - compatInfo, - classLoader); - } - public Application makeApplication(boolean forceDefaultAppClass, Instrumentation instrumentation) { if (mApplication != null) { diff --git a/core/java/android/app/LocalActivityManager.java b/core/java/android/app/LocalActivityManager.java index c34f4d9f0fe8..1d3459534f0c 100644 --- a/core/java/android/app/LocalActivityManager.java +++ b/core/java/android/app/LocalActivityManager.java @@ -224,8 +224,8 @@ public class LocalActivityManager { private void performPause(LocalActivityRecord r, boolean finishing) { final boolean needState = r.instanceState == null; - final Bundle instanceState = mActivityThread.performPauseActivity( - r, finishing, needState, "performPause", null /* pendingActions */); + final Bundle instanceState = mActivityThread.performPauseActivity(r, finishing, + "performPause", null /* pendingActions */); if (needState) { r.instanceState = instanceState; } diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index 0b5b363ddecd..6e4098646b27 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -493,7 +493,7 @@ public class Notification implements Parcelable * </ul> * <p> * Since hardware varies, you are not guaranteed that any of the values - * you pass are honored exactly. Use the system defaults (TODO) if possible + * you pass are honored exactly. Use the system defaults if possible * because they will be set to values that work on any given hardware. * <p> * The alpha channel must be set for forward compatibility. diff --git a/core/java/android/app/admin/DeviceAdminInfo.java b/core/java/android/app/admin/DeviceAdminInfo.java index 1de1d2fbb822..ed2aaf915ef2 100644 --- a/core/java/android/app/admin/DeviceAdminInfo.java +++ b/core/java/android/app/admin/DeviceAdminInfo.java @@ -75,6 +75,10 @@ public final class DeviceAdminInfo implements Parcelable { * * <p>To control this policy, the device admin must have a "limit-password" * tag in the "uses-policies" section of its meta-data. + * + * <p>This policy is deprecated for use by a device admin. In future releases, it will + * only be possible for a device owner or profile owner to enforce constraints on user + * passwords. */ public static final int USES_POLICY_LIMIT_PASSWORD = 0; @@ -136,6 +140,9 @@ public final class DeviceAdminInfo implements Parcelable { * * <p>To control this policy, the device admin must have an "expire-password" * tag in the "uses-policies" section of its meta-data. + * + * <p>This policy is deprecated for use by a device admin. In future releases, it will + * only be possible for a device owner or profile owner to enforce password expiry. */ public static final int USES_POLICY_EXPIRE_PASSWORD = 6; @@ -152,6 +159,9 @@ public final class DeviceAdminInfo implements Parcelable { * * <p>To control this policy, the device admin must have a "disable-camera" * tag in the "uses-policies" section of its meta-data. + * + * <p>This policy is deprecated for use by a device admin. In future releases, it will + * only be possible for a device owner or profile owner to disable use of the camera. */ public static final int USES_POLICY_DISABLE_CAMERA = 8; @@ -160,6 +170,10 @@ public final class DeviceAdminInfo implements Parcelable { * * <p>To control this policy, the device admin must have a "disable-keyguard-features" * tag in the "uses-policies" section of its meta-data. + * + * <p>This policy is deprecated for use by a device admin. In future releases, it will + * only be possible for a device owner or profile owner to disable use of keyguard + * features. */ public static final int USES_POLICY_DISABLE_KEYGUARD_FEATURES = 9; diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index 33028e3e6209..e190fd4cf6cd 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -6609,15 +6609,81 @@ public class DevicePolicyManager { } /** + * Indicates user operation is successful. + * + * @see #startUserInBackground(ComponentName, UserHandle) + * @see #stopUser(ComponentName, UserHandle) + * @see #logoutUser(ComponentName) + */ + public static final int USER_OPERATION_SUCCESS = 0; + + /** + * Indicates user operation failed for unknown reason. + * + * @see #startUserInBackground(ComponentName, UserHandle) + * @see #stopUser(ComponentName, UserHandle) + * @see #logoutUser(ComponentName) + */ + public static final int USER_OPERATION_ERROR_UNKNOWN = 1; + + /** + * Indicates user operation failed because target user is a managed profile. + * + * @see #startUserInBackground(ComponentName, UserHandle) + * @see #stopUser(ComponentName, UserHandle) + * @see #logoutUser(ComponentName) + */ + public static final int USER_OPERATION_ERROR_MANAGED_PROFILE = 2; + + /** + * Indicates user operation failed because maximum running user limit has reached. + * + * @see #startUserInBackground(ComponentName, UserHandle) + */ + public static final int USER_OPERATION_ERROR_MAX_RUNNING_USERS = 3; + + /** + * Indicates user operation failed because the target user is in foreground. + * + * @see #stopUser(ComponentName, UserHandle) + * @see #logoutUser(ComponentName) + */ + public static final int USER_OPERATION_ERROR_CURRENT_USER = 4; + + /** + * Result returned from + * <ul> + * <li>{@link #startUserInBackground(ComponentName, UserHandle)}</li> + * <li>{@link #stopUser(ComponentName, UserHandle)}</li> + * <li>{@link #logoutUser(ComponentName)}</li> + * </ul> + * + * @hide + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(prefix = { "USER_OPERATION_" }, value = { + USER_OPERATION_SUCCESS, + USER_OPERATION_ERROR_UNKNOWN, + USER_OPERATION_ERROR_MANAGED_PROFILE, + USER_OPERATION_ERROR_MAX_RUNNING_USERS, + USER_OPERATION_ERROR_CURRENT_USER + }) + public @interface UserOperationResult {} + + /** * Called by a device owner to start the specified secondary user in background. * * @param admin Which {@link DeviceAdminReceiver} this request is associated with. - * @param userHandle the user to be stopped. - * @return {@code true} if the user can be started, {@code false} otherwise. + * @param userHandle the user to be started in background. + * @return one of the following result codes: + * {@link #USER_OPERATION_ERROR_UNKNOWN}, + * {@link #USER_OPERATION_SUCCESS}, + * {@link #USER_OPERATION_ERROR_MANAGED_PROFILE}, + * {@link #USER_OPERATION_ERROR_MAX_RUNNING_USERS}, * @throws SecurityException if {@code admin} is not a device owner. * @see #getSecondaryUsers(ComponentName) */ - public boolean startUserInBackground( + public @UserOperationResult int startUserInBackground( @NonNull ComponentName admin, @NonNull UserHandle userHandle) { throwIfParentInstance("startUserInBackground"); try { @@ -6632,11 +6698,16 @@ public class DevicePolicyManager { * * @param admin Which {@link DeviceAdminReceiver} this request is associated with. * @param userHandle the user to be stopped. - * @return {@code true} if the user can be stopped, {@code false} otherwise. + * @return one of the following result codes: + * {@link #USER_OPERATION_ERROR_UNKNOWN}, + * {@link #USER_OPERATION_SUCCESS}, + * {@link #USER_OPERATION_ERROR_MANAGED_PROFILE}, + * {@link #USER_OPERATION_ERROR_CURRENT_USER} * @throws SecurityException if {@code admin} is not a device owner. * @see #getSecondaryUsers(ComponentName) */ - public boolean stopUser(@NonNull ComponentName admin, @NonNull UserHandle userHandle) { + public @UserOperationResult int stopUser( + @NonNull ComponentName admin, @NonNull UserHandle userHandle) { throwIfParentInstance("stopUser"); try { return mService.stopUser(admin, userHandle); @@ -6650,11 +6721,15 @@ public class DevicePolicyManager { * calling user and switch back to primary. * * @param admin Which {@link DeviceAdminReceiver} this request is associated with. - * @return {@code true} if the exit was successful, {@code false} otherwise. + * @return one of the following result codes: + * {@link #USER_OPERATION_ERROR_UNKNOWN}, + * {@link #USER_OPERATION_SUCCESS}, + * {@link #USER_OPERATION_ERROR_MANAGED_PROFILE}, + * {@link #USER_OPERATION_ERROR_CURRENT_USER} * @throws SecurityException if {@code admin} is not a profile owner affiliated with the device. * @see #getSecondaryUsers(ComponentName) */ - public boolean logoutUser(@NonNull ComponentName admin) { + public @UserOperationResult int logoutUser(@NonNull ComponentName admin) { throwIfParentInstance("logoutUser"); try { return mService.logoutUser(admin); @@ -8261,7 +8336,7 @@ public class DevicePolicyManager { } /** - * Called by a device or profile owner to restrict packages from accessing metered data. + * Called by a device or profile owner to restrict packages from using metered data. * * @param admin which {@link DeviceAdminReceiver} this request is associated with. * @param packageNames the list of package names to be restricted. @@ -8283,7 +8358,7 @@ public class DevicePolicyManager { /** * Called by a device or profile owner to retrieve the list of packages which are restricted - * by the admin from accessing metered data. + * by the admin from using metered data. * * @param admin which {@link DeviceAdminReceiver} this request is associated with. * @return the list of restricted package names. @@ -8302,6 +8377,30 @@ public class DevicePolicyManager { } /** + * Called by the system to check if a package is restricted from using metered data + * by {@param admin}. + * + * @param admin which {@link DeviceAdminReceiver} this request is associated with. + * @param packageName the package whose restricted status is needed. + * @param userId the user to which {@param packageName} belongs. + * @return {@code true} if the package is restricted by admin, otherwise {@code false} + * @throws SecurityException if the caller doesn't run with {@link Process#SYSTEM_UID} + * @hide + */ + public boolean isMeteredDataDisabledForUser(@NonNull ComponentName admin, String packageName, + @UserIdInt int userId) { + throwIfParentInstance("getMeteredDataDisabledForUser"); + if (mService != null) { + try { + return mService.isMeteredDataDisabledForUser(admin, packageName, userId); + } catch (RemoteException re) { + throw re.rethrowFromSystemServer(); + } + } + return false; + } + + /** * Called by device owners to retrieve device logs from before the device's last reboot. * <p> * <strong> This API is not supported on all devices. Calling this API on unsupported devices @@ -9078,15 +9177,15 @@ public class DevicePolicyManager { * @param executor The executor through which the listener should be invoked. * @param listener A callback object that will inform the caller when the clearing is done. * @throws SecurityException if the caller is not the device owner/profile owner. - * @return whether the clearing succeeded. */ - public boolean clearApplicationUserData(@NonNull ComponentName admin, + public void clearApplicationUserData(@NonNull ComponentName admin, @NonNull String packageName, @NonNull @CallbackExecutor Executor executor, @NonNull OnClearApplicationUserDataListener listener) { throwIfParentInstance("clearAppData"); Preconditions.checkNotNull(executor); + Preconditions.checkNotNull(listener); try { - return mService.clearApplicationUserData(admin, packageName, + mService.clearApplicationUserData(admin, packageName, new IPackageDataObserver.Stub() { public void onRemoveCompleted(String pkg, boolean succeeded) { executor.execute(() -> diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl index 2afaaa782c7f..5197de4cef9f 100644 --- a/core/java/android/app/admin/IDevicePolicyManager.aidl +++ b/core/java/android/app/admin/IDevicePolicyManager.aidl @@ -227,9 +227,9 @@ interface IDevicePolicyManager { UserHandle createAndManageUser(in ComponentName who, in String name, in ComponentName profileOwner, in PersistableBundle adminExtras, in int flags); boolean removeUser(in ComponentName who, in UserHandle userHandle); boolean switchUser(in ComponentName who, in UserHandle userHandle); - boolean startUserInBackground(in ComponentName who, in UserHandle userHandle); - boolean stopUser(in ComponentName who, in UserHandle userHandle); - boolean logoutUser(in ComponentName who); + int startUserInBackground(in ComponentName who, in UserHandle userHandle); + int stopUser(in ComponentName who, in UserHandle userHandle); + int logoutUser(in ComponentName who); List<UserHandle> getSecondaryUsers(in ComponentName who); void enableSystemApp(in ComponentName admin, in String callerPackage, in String packageName); @@ -386,7 +386,7 @@ interface IDevicePolicyManager { boolean isCurrentInputMethodSetByOwner(); StringParceledListSlice getOwnerInstalledCaCerts(in UserHandle user); - boolean clearApplicationUserData(in ComponentName admin, in String packageName, in IPackageDataObserver callback); + void clearApplicationUserData(in ComponentName admin, in String packageName, in IPackageDataObserver callback); void setLogoutEnabled(in ComponentName admin, boolean enabled); boolean isLogoutEnabled(); @@ -413,4 +413,6 @@ interface IDevicePolicyManager { List<ApnSetting> getOverrideApns(in ComponentName admin); void setOverrideApnsEnabled(in ComponentName admin, boolean enabled); boolean isOverrideApnEnabled(in ComponentName admin); + + boolean isMeteredDataDisabledForUser(in ComponentName admin, String packageName, int userId); } diff --git a/core/java/android/app/admin/SecurityLog.java b/core/java/android/app/admin/SecurityLog.java index d3b66d0d144a..08effd9c148a 100644 --- a/core/java/android/app/admin/SecurityLog.java +++ b/core/java/android/app/admin/SecurityLog.java @@ -18,6 +18,7 @@ package android.app.admin; import android.annotation.IntDef; import android.annotation.TestApi; +import android.content.ComponentName; import android.os.Parcel; import android.os.Parcelable; import android.os.SystemProperties; @@ -53,64 +54,367 @@ public class SecurityLog { TAG_APP_PROCESS_START, TAG_KEYGUARD_DISMISSED, TAG_KEYGUARD_DISMISS_AUTH_ATTEMPT, - TAG_KEYGUARD_SECURED + TAG_KEYGUARD_SECURED, + TAG_OS_STARTUP, + TAG_OS_SHUTDOWN, + TAG_LOGGING_STARTED, + TAG_LOGGING_STOPPED, + TAG_MEDIA_MOUNT, + TAG_MEDIA_UNMOUNT, + TAG_LOG_BUFFER_SIZE_CRITICAL, + TAG_PASSWORD_EXPIRATION_SET, + TAG_PASSWORD_COMPLEXITY_SET, + TAG_PASSWORD_HISTORY_LENGTH_SET, + TAG_MAX_SCREEN_LOCK_TIMEOUT_SET, + TAG_MAX_PASSWORD_ATTEMPTS_SET, + TAG_KEYGUARD_DISABLED_FEATURES_SET, + TAG_REMOTE_LOCK, + TAG_USER_RESTRICTION_ADDED, + TAG_USER_RESTRICTION_REMOVED, + TAG_WIPE_FAILURE, + TAG_KEY_GENERATED, + TAG_KEY_IMPORT, + TAG_KEY_DESTRUCTION, + TAG_CERT_AUTHORITY_INSTALLED, + TAG_CERT_AUTHORITY_REMOVED, }) public @interface SecurityLogTag {} + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(prefix = { "LEVEL_" }, value = { + LEVEL_INFO, + LEVEL_WARNING, + LEVEL_ERROR + }) + public @interface SecurityLogLevel {} + /** - * Indicate that an ADB interactive shell was opened via "adb shell". + * Indicates that an ADB interactive shell was opened via "adb shell". * There is no extra payload in the log event. */ public static final int TAG_ADB_SHELL_INTERACTIVE = SecurityLogTags.SECURITY_ADB_SHELL_INTERACTIVE; + /** - * Indicate that an shell command was issued over ADB via "adb shell command" - * The log entry contains a string data of the shell command, accessible via - * {@link SecurityEvent#getData()} + * Indicates that a shell command was issued over ADB via {@code adb shell <command>} + * The log entry contains a {@code String} payload containing the shell command, accessible + * via {@link SecurityEvent#getData()}. */ public static final int TAG_ADB_SHELL_CMD = SecurityLogTags.SECURITY_ADB_SHELL_COMMAND; + /** - * Indicate that a file was pulled from the device via the adb daemon, for example via - * "adb pull". The log entry contains a string data of the path of the pulled file, - * accessible via {@link SecurityEvent#getData()} + * Indicates that a file was pulled from the device via the adb daemon, for example via + * {@code adb pull}. The log entry contains a {@code String} payload containing the path of the + * pulled file on the device, accessible via {@link SecurityEvent#getData()}. */ public static final int TAG_SYNC_RECV_FILE = SecurityLogTags.SECURITY_ADB_SYNC_RECV; + /** - * Indicate that a file was pushed to the device via the adb daemon, for example via - * "adb push". The log entry contains a string data of the destination path of the - * pushed file, accessible via {@link SecurityEvent#getData()} + * Indicates that a file was pushed to the device via the adb daemon, for example via + * {@code adb push}. The log entry contains a {@code String} payload containing the destination + * path of the pushed file, accessible via {@link SecurityEvent#getData()}. */ public static final int TAG_SYNC_SEND_FILE = SecurityLogTags.SECURITY_ADB_SYNC_SEND; + /** - * Indicate that an app process was started. The log entry contains the following + * Indicates that an app process was started. The log entry contains the following * information about the process encapsulated in an {@link Object} array, accessible via * {@link SecurityEvent#getData()}: - * process name (String), exact start time (long), app Uid (integer), app Pid (integer), - * seinfo tag (String), SHA-256 hash of the base APK in hexadecimal (String) + * <li> [0] process name ({@code String}) + * <li> [1] exact start time in milliseconds according to {@code System.currentTimeMillis()} + * ({@code Long}) + * <li> [2] app uid ({@code Integer}) + * <li> [3] app pid ({@code Integer}) + * <li> [4] seinfo tag ({@code String}) + * <li> [5] SHA-256 hash of the base APK in hexadecimal ({@code String}) */ public static final int TAG_APP_PROCESS_START = SecurityLogTags.SECURITY_APP_PROCESS_START; + /** - * Indicate that keyguard is being dismissed. + * Indicates that keyguard has been dismissed. * There is no extra payload in the log event. */ - public static final int TAG_KEYGUARD_DISMISSED = - SecurityLogTags.SECURITY_KEYGUARD_DISMISSED; + public static final int TAG_KEYGUARD_DISMISSED = SecurityLogTags.SECURITY_KEYGUARD_DISMISSED; + /** - * Indicate that there has been an authentication attempt to dismiss the keyguard. The log entry - * contains the following information about the attempt encapsulated in an {@link Object} array, - * accessible via {@link SecurityEvent#getData()}: - * attempt result (integer, 1 for successful, 0 for unsuccessful), strength of auth method - * (integer, 1 if strong auth method was used, 0 otherwise) + * Indicates that there has been an authentication attempt to dismiss the keyguard. The log + * entry contains the following information about the attempt encapsulated in an {@link Object} + * array, accessible via {@link SecurityEvent#getData()}: + * <li> [0] attempt result ({@code Integer}, 1 for successful, 0 for unsuccessful) + * <li> [1] strength of authentication method ({@code Integer}, 1 if strong authentication + * method was used, 0 otherwise) */ public static final int TAG_KEYGUARD_DISMISS_AUTH_ATTEMPT = SecurityLogTags.SECURITY_KEYGUARD_DISMISS_AUTH_ATTEMPT; + /** - * Indicate that the device has been locked, either by user or by timeout. - * There is no extra payload in the log event. + * Indicates that the device has been locked, either by the user or by a timeout. There is no + * extra payload in the log event. */ public static final int TAG_KEYGUARD_SECURED = SecurityLogTags.SECURITY_KEYGUARD_SECURED; /** + * Indicates that the Android OS has started. The log entry contains the following information + * about the startup time software integrity check encapsulated in an {@link Object} array, + * accessible via {@link SecurityEvent#getData()}: + * <li> [0] Verified Boot state ({@code String}) + * <li> [1] dm-verity mode ({@code String}). + * <p>Verified Boot state can be one of the following: + * <li> {@code green} indicates that there is a full chain of trust extending from the + * bootloader to verified partitions including the bootloader, boot partition, and all verified + * partitions. + * <li> {@code yellow} indicates that the boot partition has been verified using the embedded + * certificate and the signature is valid. + * <li> {@code orange} indicates that the device may be freely modified. Device integrity is + * left to the user to verify out-of-band. + * <p>dm-verity mode can be one of the following: + * <li> {@code enforcing} indicates that the device will be restarted when corruption is + * detected. + * <li> {@code eio} indicates that an I/O error will be returned for an attempt to read + * corrupted data blocks. + * For details see Verified Boot documentation. + */ + public static final int TAG_OS_STARTUP = SecurityLogTags.SECURITY_OS_STARTUP; + + /** + * Indicates that the Android OS has shutdown. There is no extra payload in the log event. + */ + public static final int TAG_OS_SHUTDOWN = SecurityLogTags.SECURITY_OS_SHUTDOWN; + + /** + * Indicates start-up of audit logging. There is no extra payload in the log event. + */ + public static final int TAG_LOGGING_STARTED = SecurityLogTags.SECURITY_LOGGING_STARTED; + + /** + * Indicates shutdown of audit logging. There is no extra payload in the log event. + */ + public static final int TAG_LOGGING_STOPPED = SecurityLogTags.SECURITY_LOGGING_STOPPED; + + /** + * Indicates that removable media has been mounted on the device. The log entry contains the + * following information about the event, encapsulated in an {@link Object} array and + * accessible via {@link SecurityEvent#getData()}: + * <li> [0] mount point ({@code String}) + * <li> [1] volume label ({@code String}). + */ + public static final int TAG_MEDIA_MOUNT = SecurityLogTags.SECURITY_MEDIA_MOUNTED; + + /** + * Indicates that removable media was unmounted from the device. The log entry contains the + * following information about the event, encapsulated in an {@link Object} array and + * accessible via {@link SecurityEvent#getData()}: + * <li> [0] mount point ({@code String}) + * <li> [1] volume label ({@code String}). + */ + public static final int TAG_MEDIA_UNMOUNT = SecurityLogTags.SECURITY_MEDIA_UNMOUNTED; + + /** + * Indicates that the audit log buffer has reached 90% of its capacity. There is no extra + * payload in the log event. + */ + public static final int TAG_LOG_BUFFER_SIZE_CRITICAL = + SecurityLogTags.SECURITY_LOG_BUFFER_SIZE_CRITICAL; + + /** + * Indicates that an admin has set a password expiration timeout. The log entry contains the + * following information about the event, encapsulated in an {@link Object} array and accessible + * via {@link SecurityEvent#getData()}: + * <li> [0] admin package name ({@code String}) + * <li> [1] admin user ID ({@code Integer}) + * <li> [2] target user ID ({@code Integer}) + * <li> [3] new password expiration timeout in milliseconds ({@code Long}). + * @see DevicePolicyManager#setPasswordExpirationTimeout(ComponentName, long) + */ + public static final int TAG_PASSWORD_EXPIRATION_SET = + SecurityLogTags.SECURITY_PASSWORD_EXPIRATION_SET; + + /** + * Indicates that an admin has set a requirement for password complexity. The log entry contains + * the following information about the event, encapsulated in an {@link Object} array and + * accessible via {@link SecurityEvent#getData()}: + * <li> [0] admin package name ({@code String}) + * <li> [1] admin user ID ({@code Integer}) + * <li> [2] target user ID ({@code Integer}) + * <li> [3] minimum password length ({@code Integer}) + * <li> [4] password quality constraint ({@code Integer}) + * <li> [5] minimum number of letters ({@code Integer}) + * <li> [6] minimum number of non-letters ({@code Integer}) + * <li> [7] minimum number of digits ({@code Integer}) + * <li> [8] minimum number of uppercase letters ({@code Integer}) + * <li> [9] minimum number of lowercase letters ({@code Integer}) + * <li> [10] minimum number of symbols ({@code Integer}) + * + * @see DevicePolicyManager#setPasswordMinimumLength(ComponentName, int) + * @see DevicePolicyManager#setPasswordQuality(ComponentName, int) + * @see DevicePolicyManager#setPasswordMinimumLetters(ComponentName, int) + * @see DevicePolicyManager#setPasswordMinimumNonLetter(ComponentName, int) + * @see DevicePolicyManager#setPasswordMinimumLowerCase(ComponentName, int) + * @see DevicePolicyManager#setPasswordMinimumUpperCase(ComponentName, int) + * @see DevicePolicyManager#setPasswordMinimumNumeric(ComponentName, int) + * @see DevicePolicyManager#setPasswordMinimumSymbols(ComponentName, int) + */ + public static final int TAG_PASSWORD_COMPLEXITY_SET = + SecurityLogTags.SECURITY_PASSWORD_COMPLEXITY_SET; + + /** + * Indicates that an admin has set a password history length. The log entry contains the + * following information about the event encapsulated in an {@link Object} array, accessible + * via {@link SecurityEvent#getData()}: + * <li> [0] admin package name ({@code String}) + * <li> [1] admin user ID ({@code Integer}) + * <li> [2] target user ID ({@code Integer}) + * <li> [3] new password history length value ({@code Integer}) + * @see DevicePolicyManager#setPasswordHistoryLength(ComponentName, int) + */ + public static final int TAG_PASSWORD_HISTORY_LENGTH_SET = + SecurityLogTags.SECURITY_PASSWORD_HISTORY_LENGTH_SET; + + /** + * Indicates that an admin has set a maximum screen lock timeout. The log entry contains the + * following information about the event encapsulated in an {@link Object} array, accessible + * via {@link SecurityEvent#getData()}: + * <li> [0] admin package name ({@code String}) + * <li> [1] admin user ID ({@code Integer}) + * <li> [2] target user ID ({@code Integer}) + * <li> [3] new screen lock timeout in milliseconds ({@code Long}) + * @see DevicePolicyManager#setMaximumTimeToLock(ComponentName, long) + */ + public static final int TAG_MAX_SCREEN_LOCK_TIMEOUT_SET = + SecurityLogTags.SECURITY_MAX_SCREEN_LOCK_TIMEOUT_SET; + + /** + * Indicates that an admin has set a maximum number of failed password attempts before wiping + * data. The log entry contains the following information about the event encapsulated in an + * {@link Object} array, accessible via {@link SecurityEvent#getData()}: + * <li> [0] admin package name ({@code String}) + * <li> [1] admin user ID ({@code Integer}) + * <li> [2] target user ID ({@code Integer}) + * <li> [3] new maximum number of failed password attempts ({@code Integer}) + * @see DevicePolicyManager#setMaximumTimeToLock(ComponentName, long) + */ + public static final int TAG_MAX_PASSWORD_ATTEMPTS_SET = + SecurityLogTags.SECURITY_MAX_PASSWORD_ATTEMPTS_SET; + + /** + * Indicates that an admin has set disabled keyguard features. The log entry contains the + * following information about the event encapsulated in an {@link Object} array, accessible via + * {@link SecurityEvent#getData()}: + * <li> [0] admin package name ({@code String}) + * <li> [1] admin user ID ({@code Integer}) + * <li> [2] target user ID ({@code Integer}) + * <li> [3] disabled keyguard feature mask ({@code Integer}). + * @see DevicePolicyManager#setKeyguardDisabledFeatures(ComponentName, int) + */ + public static final int TAG_KEYGUARD_DISABLED_FEATURES_SET = + SecurityLogTags.SECURITY_KEYGUARD_DISABLED_FEATURES_SET; + + /** + * Indicates that an admin remotely locked the device or profile. The log entry contains the + * following information about the event encapsulated in an {@link Object} array, accessible via + * {@link SecurityEvent#getData()}: + * <li> [0] admin package name ({@code String}), + * <li> [1] admin user ID ({@code Integer}). + */ + public static final int TAG_REMOTE_LOCK = SecurityLogTags.SECURITY_REMOTE_LOCK; + + /** + * Indicates a failure to wipe device or user data. There is no extra payload in the log event. + */ + public static final int TAG_WIPE_FAILURE = SecurityLogTags.SECURITY_WIPE_FAILED; + + /** + * Indicates that an authentication key was generated. The log entry contains the following + * information about the event, encapsulated in an {@link Object} array and accessible via + * {@link SecurityEvent#getData()}: + * <li> [0] result ({@code Integer}, 0 if operation failed, 1 if succeeded) + * <li> [1] alias of the key ({@code String}) + * <li> [2] requesting process uid ({@code Integer}). + */ + public static final int TAG_KEY_GENERATED = + SecurityLogTags.SECURITY_KEY_GENERATED; + + /** + * Indicates that a cryptographic key was imported. The log entry contains the following + * information about the event, encapsulated in an {@link Object} array and accessible via + * {@link SecurityEvent#getData()}: + * <li> [0] result ({@code Integer}, 0 if operation failed, 1 if succeeded) + * <li> [1] alias of the key ({@code String}) + * <li> [2] requesting process uid ({@code Integer}). + */ + public static final int TAG_KEY_IMPORT = SecurityLogTags.SECURITY_KEY_IMPORTED; + + /** + * Indicates that a cryptographic key was destroyed. The log entry contains the following + * information about the event, encapsulated in an {@link Object} array and accessible via + * {@link SecurityEvent#getData()}: + * <li> [0] result ({@code Integer}, 0 if operation failed, 1 if succeeded) + * <li> [1] alias of the key ({@code String}) + * <li> [2] requesting process uid ({@code Integer}). + */ + public static final int TAG_KEY_DESTRUCTION = SecurityLogTags.SECURITY_KEY_DESTROYED; + + /** + * Indicates that a new root certificate has been installed into system's trusted credential + * storage. The log entry contains the following information about the event, encapsulated in an + * {@link Object} array and accessible via {@link SecurityEvent#getData()}: + * <li> [0] result ({@code Integer}, 0 if operation failed, 1 if succeeded) + * <li> [1] subject of the certificate ({@code String}). + */ + public static final int TAG_CERT_AUTHORITY_INSTALLED = + SecurityLogTags.SECURITY_CERT_AUTHORITY_INSTALLED; + + /** + * Indicates that a new oot certificate has been removed from system's trusted credential + * storage. The log entry contains the following information about the event, encapsulated in an + * {@link Object} array and accessible via {@link SecurityEvent#getData()}: + * <li> [0] result ({@code Integer}, 0 if operation failed, 1 if succeeded) + * <li> [1] subject of the certificate ({@code String}). + */ + public static final int TAG_CERT_AUTHORITY_REMOVED = + SecurityLogTags.SECURITY_CERT_AUTHORITY_REMOVED; + + /** + * Indicates that an admin has set a user restriction. The log entry contains the following + * information about the event, encapsulated in an {@link Object} array and accessible via + * {@link SecurityEvent#getData()}: + * <li> [0] admin package name ({@code String}) + * <li> [1] admin user ID ({@code Integer}) + * <li> [2] user restriction ({@code String}) + * @see DevicePolicyManager#addUserRestriction(ComponentName, String) + */ + public static final int TAG_USER_RESTRICTION_ADDED = + SecurityLogTags.SECURITY_USER_RESTRICTION_ADDED; + + /** + * Indicates that an admin has removed a user restriction. The log entry contains the following + * information about the event, encapsulated in an {@link Object} array and accessible via + * {@link SecurityEvent#getData()}: + * <li> [0] admin package name ({@code String}) + * <li> [1] admin user ID ({@code Integer}) + * <li> [2] user restriction ({@code String}) + * @see DevicePolicyManager#clearUserRestriction(ComponentName, String) + */ + public static final int TAG_USER_RESTRICTION_REMOVED = + SecurityLogTags.SECURITY_USER_RESTRICTION_REMOVED; + + /** + * Event severity level indicating that the event corresponds to normal workflow. + */ + public static final int LEVEL_INFO = 1; + + /** + * Event severity level indicating that the event may require admin attention. + */ + public static final int LEVEL_WARNING = 2; + + /** + * Event severity level indicating that the event requires urgent admin action. + */ + public static final int LEVEL_ERROR = 3; + + /** * Returns if security logging is enabled. Log producers should only write new logs if this is * true. Under the hood this is the logical AND of whether device owner exists and whether * it enables logging by setting the system property {@link #PROPERTY_LOGGING_ENABLED}. @@ -198,6 +502,60 @@ public class SecurityLog { return mId; } + /** + * Returns severity level for the event. + */ + public @SecurityLogLevel int getLogLevel() { + switch (mEvent.getTag()) { + case TAG_ADB_SHELL_INTERACTIVE: + case TAG_ADB_SHELL_CMD: + case TAG_SYNC_RECV_FILE: + case TAG_SYNC_SEND_FILE: + case TAG_APP_PROCESS_START: + case TAG_KEYGUARD_DISMISSED: + case TAG_KEYGUARD_SECURED: + case TAG_OS_STARTUP: + case TAG_OS_SHUTDOWN: + case TAG_LOGGING_STARTED: + case TAG_LOGGING_STOPPED: + case TAG_MEDIA_MOUNT: + case TAG_MEDIA_UNMOUNT: + case TAG_PASSWORD_EXPIRATION_SET: + case TAG_PASSWORD_COMPLEXITY_SET: + case TAG_PASSWORD_HISTORY_LENGTH_SET: + case TAG_MAX_SCREEN_LOCK_TIMEOUT_SET: + case TAG_MAX_PASSWORD_ATTEMPTS_SET: + case TAG_USER_RESTRICTION_ADDED: + case TAG_USER_RESTRICTION_REMOVED: + return LEVEL_INFO; + case TAG_CERT_AUTHORITY_REMOVED: + return getSuccess() ? LEVEL_INFO : LEVEL_ERROR; + case TAG_CERT_AUTHORITY_INSTALLED: + case TAG_KEYGUARD_DISMISS_AUTH_ATTEMPT: + case TAG_KEY_IMPORT: + case TAG_KEY_DESTRUCTION: + case TAG_KEY_GENERATED: + return getSuccess() ? LEVEL_INFO : LEVEL_WARNING; + case TAG_LOG_BUFFER_SIZE_CRITICAL: + case TAG_WIPE_FAILURE: + return LEVEL_ERROR; + default: + return LEVEL_INFO; + } + } + + // Success/failure if present is encoded as an integer in the first (0th) element of data. + private boolean getSuccess() { + final Object data = getData(); + if (data == null || !(data instanceof Object[])) { + return false; + } + + final Object[] array = (Object[]) data; + return array.length >= 1 && array[0] instanceof Integer && (Integer) array[0] != 0; + } + + @Override public int describeContents() { return 0; @@ -263,8 +621,8 @@ public class SecurityLog { throws IOException; /** - * Retrieve all security logs whose timestamp (in nanosceonds) is equal to or greater than the - * given timestamp. This method will block until either the last log earlier than the given + * Retrieve all security logs whose timestamp is equal to or greater than the given timestamp in + * nanoseconds. This method will block until either the last log earlier than the given * timestamp is about to be pruned, or after a 2-hour timeout has passed. * @hide */ diff --git a/core/java/android/app/admin/SecurityLogTags.logtags b/core/java/android/app/admin/SecurityLogTags.logtags index 39371c7923e0..be626786c3c6 100644 --- a/core/java/android/app/admin/SecurityLogTags.logtags +++ b/core/java/android/app/admin/SecurityLogTags.logtags @@ -10,3 +10,28 @@ option java_package android.app.admin 210006 security_keyguard_dismissed 210007 security_keyguard_dismiss_auth_attempt (success|1),(method_strength|1) 210008 security_keyguard_secured + +# Additional event types for NIAP MDFPP 3.1 compliant audit logging. + +210009 security_os_startup (boot_state|3),(verity_mode|3) +210010 security_os_shutdown +210011 security_logging_started +210012 security_logging_stopped +210013 security_media_mounted (path|3),(label|3) +210014 security_media_unmounted (path|3),(label|3) +210015 security_log_buffer_size_critical +210016 security_password_expiration_set (package|3),(admin_user|1),(target_user|1),(timeout|2|3) +210017 security_password_complexity_set (package|3),(admin_user|1),(target_user|1),(length|1),(quality|1),(num_letters|1),(num_non_letters|1),(num_numeric|1),(num_uppercase|1),(num_lowercase|1),(num_symbols|1) +210018 security_password_history_length_set (package|3),(admin_user|1),(target_user|1),(length|1) +210019 security_max_screen_lock_timeout_set (package|3),(admin_user|1),(target_user|1),(timeout|2|3) +210020 security_max_password_attempts_set (package|3),(admin_user|1),(target_user|1),(num_failures|1) +210021 security_keyguard_disabled_features_set (package|3),(admin_user|1),(target_user|1),(features|1) +210022 security_remote_lock (package|3),(admin_user|1),(target_user|1) +210023 security_wipe_failed (package|3),(admin_user|1) +210024 security_key_generated (success|1),(key_id|3),(uid|1) +210025 security_key_imported (success|1),(key_id|3),(uid|1) +210026 security_key_destroyed (success|1),(key_id|3),(uid|1) +210027 security_user_restriction_added (package|3),(admin_user|1),(restriction|3) +210028 security_user_restriction_removed (package|3),(admin_user|1),(restriction|3) +210029 security_cert_authority_installed (success|1),(subject|3) +210030 security_cert_authority_removed (success|1),(subject|3)
\ No newline at end of file diff --git a/core/java/android/app/servertransaction/PendingTransactionActions.java b/core/java/android/app/servertransaction/PendingTransactionActions.java index 8304c1c5a2d8..073d28cfa27f 100644 --- a/core/java/android/app/servertransaction/PendingTransactionActions.java +++ b/core/java/android/app/servertransaction/PendingTransactionActions.java @@ -134,7 +134,7 @@ public class PendingTransactionActions { Bundle.dumpStats(pw, mPersistentState); if (ex instanceof TransactionTooLargeException - && mActivity.loadedApk.getTargetSdkVersion() < Build.VERSION_CODES.N) { + && mActivity.packageInfo.getTargetSdkVersion() < Build.VERSION_CODES.N) { Log.e(TAG, "App sent too much data in instance state, so it was ignored", ex); return; } diff --git a/core/java/android/app/slice/Slice.java b/core/java/android/app/slice/Slice.java index 5808f8b510d9..126deefae282 100644 --- a/core/java/android/app/slice/Slice.java +++ b/core/java/android/app/slice/Slice.java @@ -146,11 +146,6 @@ public final class Slice implements Parcelable { */ public static final String HINT_PARTIAL = "partial"; /** - * A hint representing that this item is the max value possible for the slice containing this. - * Used to indicate the maximum integer value for a {@link #SUBTYPE_SLIDER}. - */ - public static final String HINT_MAX = "max"; - /** * A hint representing that this item should be used to indicate that there's more * content associated with this slice. */ @@ -168,6 +163,16 @@ public final class Slice implements Parcelable { */ public static final String EXTRA_TOGGLE_STATE = "android.app.slice.extra.TOGGLE_STATE"; /** + * Key to retrieve an extra added to an intent when the value of a slider is changed. + * @deprecated remove once support lib is update to use EXTRA_RANGE_VALUE instead + */ + @Deprecated + public static final String EXTRA_SLIDER_VALUE = "android.app.slice.extra.SLIDER_VALUE"; + /** + * Key to retrieve an extra added to an intent when the value of an input range is changed. + */ + public static final String EXTRA_RANGE_VALUE = "android.app.slice.extra.RANGE_VALUE"; + /** * Subtype to indicate that this is a message as part of a communication * sequence in this slice. */ @@ -181,10 +186,24 @@ public final class Slice implements Parcelable { */ public static final String SUBTYPE_COLOR = "color"; /** - * Subtype to tag an item represents a slider. + * Subtype to tag an item as representing a slider. + * @deprecated remove once support lib is update to use SUBTYPE_RANGE instead */ + @Deprecated public static final String SUBTYPE_SLIDER = "slider"; /** + * Subtype to tag an item as representing a range. + */ + public static final String SUBTYPE_RANGE = "range"; + /** + * Subtype to tag an item as representing the max int value for a {@link #SUBTYPE_RANGE}. + */ + public static final String SUBTYPE_MAX = "max"; + /** + * Subtype to tag an item as representing the current int value for a {@link #SUBTYPE_RANGE}. + */ + public static final String SUBTYPE_VALUE = "value"; + /** * Subtype to indicate that this content has a toggle action associated with it. To indicate * that the toggle is on, use {@link #HINT_SELECTED}. When the toggle state changes, the * intent associated with it will be sent along with an extra {@link #EXTRA_TOGGLE_STATE} diff --git a/core/java/android/app/slice/SliceProvider.java b/core/java/android/app/slice/SliceProvider.java index 00e8ccad0f5f..336bd4782156 100644 --- a/core/java/android/app/slice/SliceProvider.java +++ b/core/java/android/app/slice/SliceProvider.java @@ -147,6 +147,14 @@ public abstract class SliceProvider extends ContentProvider { * @hide */ public static final String EXTRA_OVERRIDE_PKG = "override_pkg"; + /** + * @hide + */ + public static final String EXTRA_OVERRIDE_UID = "override_uid"; + /** + * @hide + */ + public static final String EXTRA_OVERRIDE_PID = "override_pid"; private static final boolean DEBUG = false; @@ -302,13 +310,20 @@ public abstract class SliceProvider extends ContentProvider { List<SliceSpec> supportedSpecs = extras.getParcelableArrayList(EXTRA_SUPPORTED_SPECS); String callingPackage = getCallingPackage(); + int callingUid = Binder.getCallingUid(); + int callingPid = Binder.getCallingPid(); if (extras.containsKey(EXTRA_OVERRIDE_PKG)) { if (Binder.getCallingUid() != Process.SYSTEM_UID) { throw new SecurityException("Only the system can override calling pkg"); } + // This is safe because we would grant SYSTEM_UID access to all slices + // and want to allow it to bind slices as if it were a less privileged app + // to check their permission levels. callingPackage = extras.getString(EXTRA_OVERRIDE_PKG); + callingUid = extras.getInt(EXTRA_OVERRIDE_UID); + callingPid = extras.getInt(EXTRA_OVERRIDE_PID); } - Slice s = handleBindSlice(uri, supportedSpecs, callingPackage); + Slice s = handleBindSlice(uri, supportedSpecs, callingPackage, callingUid, callingPid); Bundle b = new Bundle(); b.putParcelable(EXTRA_SLICE, s); return b; @@ -319,7 +334,8 @@ public abstract class SliceProvider extends ContentProvider { List<SliceSpec> supportedSpecs = extras.getParcelableArrayList(EXTRA_SUPPORTED_SPECS); Bundle b = new Bundle(); if (uri != null) { - Slice s = handleBindSlice(uri, supportedSpecs, getCallingPackage()); + Slice s = handleBindSlice(uri, supportedSpecs, getCallingPackage(), + Binder.getCallingUid(), Binder.getCallingPid()); b.putParcelable(EXTRA_SLICE, s); } else { b.putParcelable(EXTRA_SLICE, null); @@ -401,15 +417,15 @@ public abstract class SliceProvider extends ContentProvider { } private Slice handleBindSlice(Uri sliceUri, List<SliceSpec> supportedSpecs, - String callingPkg) { + String callingPkg, int callingUid, int callingPid) { // This can be removed once Slice#bindSlice is removed and everyone is using // SliceManager#bindSlice. String pkg = callingPkg != null ? callingPkg - : getContext().getPackageManager().getNameForUid(Binder.getCallingUid()); - if (!UserHandle.isSameApp(Binder.getCallingUid(), Process.myUid())) { + : getContext().getPackageManager().getNameForUid(callingUid); + if (!UserHandle.isSameApp(callingUid, Process.myUid())) { try { mSliceManager.enforceSlicePermission(sliceUri, pkg, - Binder.getCallingPid(), Binder.getCallingUid()); + callingPid, callingUid); } catch (SecurityException e) { return createPermissionSlice(getContext(), sliceUri, pkg); } diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java index 4923171bd3a6..e02a29494296 100644 --- a/core/java/android/content/Intent.java +++ b/core/java/android/content/Intent.java @@ -7028,7 +7028,7 @@ public class Intent implements Parcelable, Cloneable { * * @param name The name of the desired item. * - * @return the value of an item that previously added with putExtra() + * @return the value of an item previously added with putExtra(), * or null if none was found. * * @deprecated @@ -7046,7 +7046,7 @@ public class Intent implements Parcelable, Cloneable { * @param defaultValue the value to be returned if no value of the desired * type is stored with the given name. * - * @return the value of an item that previously added with putExtra() + * @return the value of an item previously added with putExtra(), * or the default value if none was found. * * @see #putExtra(String, boolean) @@ -7063,7 +7063,7 @@ public class Intent implements Parcelable, Cloneable { * @param defaultValue the value to be returned if no value of the desired * type is stored with the given name. * - * @return the value of an item that previously added with putExtra() + * @return the value of an item previously added with putExtra(), * or the default value if none was found. * * @see #putExtra(String, byte) @@ -7080,7 +7080,7 @@ public class Intent implements Parcelable, Cloneable { * @param defaultValue the value to be returned if no value of the desired * type is stored with the given name. * - * @return the value of an item that previously added with putExtra() + * @return the value of an item previously added with putExtra(), * or the default value if none was found. * * @see #putExtra(String, short) @@ -7097,7 +7097,7 @@ public class Intent implements Parcelable, Cloneable { * @param defaultValue the value to be returned if no value of the desired * type is stored with the given name. * - * @return the value of an item that previously added with putExtra() + * @return the value of an item previously added with putExtra(), * or the default value if none was found. * * @see #putExtra(String, char) @@ -7114,7 +7114,7 @@ public class Intent implements Parcelable, Cloneable { * @param defaultValue the value to be returned if no value of the desired * type is stored with the given name. * - * @return the value of an item that previously added with putExtra() + * @return the value of an item previously added with putExtra(), * or the default value if none was found. * * @see #putExtra(String, int) @@ -7131,7 +7131,7 @@ public class Intent implements Parcelable, Cloneable { * @param defaultValue the value to be returned if no value of the desired * type is stored with the given name. * - * @return the value of an item that previously added with putExtra() + * @return the value of an item previously added with putExtra(), * or the default value if none was found. * * @see #putExtra(String, long) @@ -7148,7 +7148,7 @@ public class Intent implements Parcelable, Cloneable { * @param defaultValue the value to be returned if no value of the desired * type is stored with the given name. * - * @return the value of an item that previously added with putExtra(), + * @return the value of an item previously added with putExtra(), * or the default value if no such item is present * * @see #putExtra(String, float) @@ -7165,7 +7165,7 @@ public class Intent implements Parcelable, Cloneable { * @param defaultValue the value to be returned if no value of the desired * type is stored with the given name. * - * @return the value of an item that previously added with putExtra() + * @return the value of an item previously added with putExtra(), * or the default value if none was found. * * @see #putExtra(String, double) @@ -7180,7 +7180,7 @@ public class Intent implements Parcelable, Cloneable { * * @param name The name of the desired item. * - * @return the value of an item that previously added with putExtra() + * @return the value of an item previously added with putExtra(), * or null if no String value was found. * * @see #putExtra(String, String) @@ -7194,7 +7194,7 @@ public class Intent implements Parcelable, Cloneable { * * @param name The name of the desired item. * - * @return the value of an item that previously added with putExtra() + * @return the value of an item previously added with putExtra(), * or null if no CharSequence value was found. * * @see #putExtra(String, CharSequence) @@ -7208,7 +7208,7 @@ public class Intent implements Parcelable, Cloneable { * * @param name The name of the desired item. * - * @return the value of an item that previously added with putExtra() + * @return the value of an item previously added with putExtra(), * or null if no Parcelable value was found. * * @see #putExtra(String, Parcelable) @@ -7222,7 +7222,7 @@ public class Intent implements Parcelable, Cloneable { * * @param name The name of the desired item. * - * @return the value of an item that previously added with putExtra() + * @return the value of an item previously added with putExtra(), * or null if no Parcelable[] value was found. * * @see #putExtra(String, Parcelable[]) @@ -7236,8 +7236,9 @@ public class Intent implements Parcelable, Cloneable { * * @param name The name of the desired item. * - * @return the value of an item that previously added with putExtra() - * or null if no ArrayList<Parcelable> value was found. + * @return the value of an item previously added with + * putParcelableArrayListExtra(), or null if no + * ArrayList<Parcelable> value was found. * * @see #putParcelableArrayListExtra(String, ArrayList) */ @@ -7250,7 +7251,7 @@ public class Intent implements Parcelable, Cloneable { * * @param name The name of the desired item. * - * @return the value of an item that previously added with putExtra() + * @return the value of an item previously added with putExtra(), * or null if no Serializable value was found. * * @see #putExtra(String, Serializable) @@ -7264,8 +7265,9 @@ public class Intent implements Parcelable, Cloneable { * * @param name The name of the desired item. * - * @return the value of an item that previously added with putExtra() - * or null if no ArrayList<Integer> value was found. + * @return the value of an item previously added with + * putIntegerArrayListExtra(), or null if no + * ArrayList<Integer> value was found. * * @see #putIntegerArrayListExtra(String, ArrayList) */ @@ -7278,8 +7280,9 @@ public class Intent implements Parcelable, Cloneable { * * @param name The name of the desired item. * - * @return the value of an item that previously added with putExtra() - * or null if no ArrayList<String> value was found. + * @return the value of an item previously added with + * putStringArrayListExtra(), or null if no + * ArrayList<String> value was found. * * @see #putStringArrayListExtra(String, ArrayList) */ @@ -7292,8 +7295,9 @@ public class Intent implements Parcelable, Cloneable { * * @param name The name of the desired item. * - * @return the value of an item that previously added with putExtra() - * or null if no ArrayList<CharSequence> value was found. + * @return the value of an item previously added with + * putCharSequenceArrayListExtra, or null if no + * ArrayList<CharSequence> value was found. * * @see #putCharSequenceArrayListExtra(String, ArrayList) */ @@ -7306,7 +7310,7 @@ public class Intent implements Parcelable, Cloneable { * * @param name The name of the desired item. * - * @return the value of an item that previously added with putExtra() + * @return the value of an item previously added with putExtra(), * or null if no boolean array value was found. * * @see #putExtra(String, boolean[]) @@ -7320,7 +7324,7 @@ public class Intent implements Parcelable, Cloneable { * * @param name The name of the desired item. * - * @return the value of an item that previously added with putExtra() + * @return the value of an item previously added with putExtra(), * or null if no byte array value was found. * * @see #putExtra(String, byte[]) @@ -7334,7 +7338,7 @@ public class Intent implements Parcelable, Cloneable { * * @param name The name of the desired item. * - * @return the value of an item that previously added with putExtra() + * @return the value of an item previously added with putExtra(), * or null if no short array value was found. * * @see #putExtra(String, short[]) @@ -7348,7 +7352,7 @@ public class Intent implements Parcelable, Cloneable { * * @param name The name of the desired item. * - * @return the value of an item that previously added with putExtra() + * @return the value of an item previously added with putExtra(), * or null if no char array value was found. * * @see #putExtra(String, char[]) @@ -7362,7 +7366,7 @@ public class Intent implements Parcelable, Cloneable { * * @param name The name of the desired item. * - * @return the value of an item that previously added with putExtra() + * @return the value of an item previously added with putExtra(), * or null if no int array value was found. * * @see #putExtra(String, int[]) @@ -7376,7 +7380,7 @@ public class Intent implements Parcelable, Cloneable { * * @param name The name of the desired item. * - * @return the value of an item that previously added with putExtra() + * @return the value of an item previously added with putExtra(), * or null if no long array value was found. * * @see #putExtra(String, long[]) @@ -7390,7 +7394,7 @@ public class Intent implements Parcelable, Cloneable { * * @param name The name of the desired item. * - * @return the value of an item that previously added with putExtra() + * @return the value of an item previously added with putExtra(), * or null if no float array value was found. * * @see #putExtra(String, float[]) @@ -7404,7 +7408,7 @@ public class Intent implements Parcelable, Cloneable { * * @param name The name of the desired item. * - * @return the value of an item that previously added with putExtra() + * @return the value of an item previously added with putExtra(), * or null if no double array value was found. * * @see #putExtra(String, double[]) @@ -7418,7 +7422,7 @@ public class Intent implements Parcelable, Cloneable { * * @param name The name of the desired item. * - * @return the value of an item that previously added with putExtra() + * @return the value of an item previously added with putExtra(), * or null if no String array value was found. * * @see #putExtra(String, String[]) @@ -7432,7 +7436,7 @@ public class Intent implements Parcelable, Cloneable { * * @param name The name of the desired item. * - * @return the value of an item that previously added with putExtra() + * @return the value of an item previously added with putExtra(), * or null if no CharSequence array value was found. * * @see #putExtra(String, CharSequence[]) @@ -7446,7 +7450,7 @@ public class Intent implements Parcelable, Cloneable { * * @param name The name of the desired item. * - * @return the value of an item that previously added with putExtra() + * @return the value of an item previously added with putExtra(), * or null if no Bundle value was found. * * @see #putExtra(String, Bundle) @@ -7460,7 +7464,7 @@ public class Intent implements Parcelable, Cloneable { * * @param name The name of the desired item. * - * @return the value of an item that previously added with putExtra() + * @return the value of an item previously added with putExtra(), * or null if no IBinder value was found. * * @see #putExtra(String, IBinder) @@ -7480,7 +7484,7 @@ public class Intent implements Parcelable, Cloneable { * @param defaultValue The default value to return in case no item is * associated with the key 'name' * - * @return the value of an item that previously added with putExtra() + * @return the value of an item previously added with putExtra(), * or defaultValue if none was found. * * @see #putExtra diff --git a/core/java/android/content/pm/AndroidTestBaseUpdater.java b/core/java/android/content/pm/AndroidTestBaseUpdater.java new file mode 100644 index 000000000000..2aaac0280a0e --- /dev/null +++ b/core/java/android/content/pm/AndroidTestBaseUpdater.java @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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.content.pm; + +import static android.content.pm.SharedLibraryNames.ANDROID_TEST_BASE; +import static android.content.pm.SharedLibraryNames.ANDROID_TEST_RUNNER; + +import android.content.pm.PackageParser.Package; + +import com.android.internal.annotations.VisibleForTesting; + +/** + * Updates a package to ensure that if it targets < P that the android.test.base library is + * included by default. + * + * <p>This is separated out so that it can be conditionally included at build time depending on + * whether android.test.base is on the bootclasspath or not. In order to include this at + * build time, and remove android.test.base from the bootclasspath pass + * REMOVE_ATB_FROM_BCP=true on the build command line, otherwise this class will not be included + * and the + * + * @hide + */ +@VisibleForTesting +public class AndroidTestBaseUpdater extends PackageSharedLibraryUpdater { + + @Override + public void updatePackage(Package pkg) { + // Packages targeted at <= O_MR1 expect the classes in the android.test.base library + // to be accessible so this maintains backward compatibility by adding the + // android.test.base library to those packages. + if (apkTargetsApiLevelLessThanOrEqualToOMR1(pkg)) { + prefixRequiredLibrary(pkg, ANDROID_TEST_BASE); + } else { + // If a package already depends on android.test.runner then add a dependency on + // android.test.base because android.test.runner depends on classes from the + // android.test.base library. + prefixImplicitDependency(pkg, ANDROID_TEST_RUNNER, ANDROID_TEST_BASE); + } + } +} diff --git a/core/java/android/content/pm/OrgApacheHttpLegacyUpdater.java b/core/java/android/content/pm/OrgApacheHttpLegacyUpdater.java index 81041e9d3ba6..81e4105febee 100644 --- a/core/java/android/content/pm/OrgApacheHttpLegacyUpdater.java +++ b/core/java/android/content/pm/OrgApacheHttpLegacyUpdater.java @@ -15,13 +15,12 @@ */ package android.content.pm; +import static android.content.pm.SharedLibraryNames.ORG_APACHE_HTTP_LEGACY; + import android.content.pm.PackageParser.Package; -import android.os.Build; import com.android.internal.annotations.VisibleForTesting; -import java.util.ArrayList; - /** * Updates a package to ensure that if it targets < P that the org.apache.http.legacy library is * included by default. @@ -37,30 +36,13 @@ import java.util.ArrayList; @VisibleForTesting public class OrgApacheHttpLegacyUpdater extends PackageSharedLibraryUpdater { - private static final String APACHE_HTTP_LEGACY = "org.apache.http.legacy"; - @Override public void updatePackage(Package pkg) { - ArrayList<String> usesLibraries = pkg.usesLibraries; - ArrayList<String> usesOptionalLibraries = pkg.usesOptionalLibraries; - // Packages targeted at <= O_MR1 expect the classes in the org.apache.http.legacy library // to be accessible so this maintains backward compatibility by adding the // org.apache.http.legacy library to those packages. if (apkTargetsApiLevelLessThanOrEqualToOMR1(pkg)) { - boolean apacheHttpLegacyPresent = isLibraryPresent( - usesLibraries, usesOptionalLibraries, APACHE_HTTP_LEGACY); - if (!apacheHttpLegacyPresent) { - usesLibraries = prefix(usesLibraries, APACHE_HTTP_LEGACY); - } + prefixRequiredLibrary(pkg, ORG_APACHE_HTTP_LEGACY); } - - pkg.usesLibraries = usesLibraries; - pkg.usesOptionalLibraries = usesOptionalLibraries; - } - - private static boolean apkTargetsApiLevelLessThanOrEqualToOMR1(Package pkg) { - int targetSdkVersion = pkg.applicationInfo.targetSdkVersion; - return targetSdkVersion <= Build.VERSION_CODES.O_MR1; } } diff --git a/core/java/android/content/pm/PackageBackwardCompatibility.java b/core/java/android/content/pm/PackageBackwardCompatibility.java index 9bdb78be5442..a16f81b11ae6 100644 --- a/core/java/android/content/pm/PackageBackwardCompatibility.java +++ b/core/java/android/content/pm/PackageBackwardCompatibility.java @@ -16,14 +16,19 @@ package android.content.pm; +import static android.content.pm.SharedLibraryNames.ANDROID_TEST_BASE; +import static android.content.pm.SharedLibraryNames.ANDROID_TEST_MOCK; +import static android.content.pm.SharedLibraryNames.ANDROID_TEST_RUNNER; +import static android.content.pm.SharedLibraryNames.ORG_APACHE_HTTP_LEGACY; + import android.content.pm.PackageParser.Package; import android.util.Log; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.util.ArrayUtils; import java.util.ArrayList; import java.util.List; +import java.util.function.Supplier; /** * Modifies {@link Package} in order to maintain backwards compatibility. @@ -35,54 +40,90 @@ public class PackageBackwardCompatibility extends PackageSharedLibraryUpdater { private static final String TAG = PackageBackwardCompatibility.class.getSimpleName(); - private static final String ANDROID_TEST_MOCK = "android.test.mock"; - - private static final String ANDROID_TEST_RUNNER = "android.test.runner"; - private static final PackageBackwardCompatibility INSTANCE; static { - String className = "android.content.pm.OrgApacheHttpLegacyUpdater"; + final List<PackageSharedLibraryUpdater> packageUpdaters = new ArrayList<>(); + + // Attempt to load and add the optional updater that will only be available when + // REMOVE_OAHL_FROM_BCP=true. If that could not be found then add the default updater that + // will remove any references to org.apache.http.library from the package so that it does + // not try and load the library when it is on the bootclasspath. + boolean bootClassPathContainsOAHL = !addOptionalUpdater(packageUpdaters, + "android.content.pm.OrgApacheHttpLegacyUpdater", + RemoveUnnecessaryOrgApacheHttpLegacyLibrary::new); + + // Add this before adding AndroidTestBaseUpdater so that android.test.base comes before + // android.test.mock. + packageUpdaters.add(new AndroidTestRunnerSplitUpdater()); + + // Attempt to load and add the optional updater that will only be available when + // REMOVE_ATB_FROM_BCP=true. If that could not be found then add the default updater that + // will remove any references to org.apache.http.library from the package so that it does + // not try and load the library when it is on the bootclasspath. + boolean bootClassPathContainsATB = !addOptionalUpdater(packageUpdaters, + "android.content.pm.AndroidTestBaseUpdater", + RemoveUnnecessaryAndroidTestBaseLibrary::new); + + PackageSharedLibraryUpdater[] updaterArray = packageUpdaters + .toArray(new PackageSharedLibraryUpdater[0]); + INSTANCE = new PackageBackwardCompatibility( + bootClassPathContainsOAHL, bootClassPathContainsATB, updaterArray); + } + + /** + * Add an optional {@link PackageSharedLibraryUpdater} instance to the list, if it could not be + * found then add a default instance instead. + * + * @param packageUpdaters the list to update. + * @param className the name of the optional class. + * @param defaultUpdater the supplier of the default instance. + * @return true if the optional updater was added false otherwise. + */ + private static boolean addOptionalUpdater(List<PackageSharedLibraryUpdater> packageUpdaters, + String className, Supplier<PackageSharedLibraryUpdater> defaultUpdater) { Class<? extends PackageSharedLibraryUpdater> clazz; try { clazz = (PackageBackwardCompatibility.class.getClassLoader() .loadClass(className) .asSubclass(PackageSharedLibraryUpdater.class)); + Log.i(TAG, "Loaded " + className); } catch (ClassNotFoundException e) { Log.i(TAG, "Could not find " + className + ", ignoring"); clazz = null; } - boolean hasOrgApacheHttpLegacy = false; - final List<PackageSharedLibraryUpdater> packageUpdaters = new ArrayList<>(); + boolean usedOptional = false; + PackageSharedLibraryUpdater updater; if (clazz == null) { - // Add an updater that will remove any references to org.apache.http.library from the - // package so that it does not try and load the library when it is on the - // bootclasspath. - packageUpdaters.add(new RemoveUnnecessaryOrgApacheHttpLegacyLibrary()); + updater = defaultUpdater.get(); } else { try { - packageUpdaters.add(clazz.getConstructor().newInstance()); - hasOrgApacheHttpLegacy = true; + updater = clazz.getConstructor().newInstance(); + usedOptional = true; } catch (ReflectiveOperationException e) { throw new IllegalStateException("Could not create instance of " + className, e); } } + packageUpdaters.add(updater); + return usedOptional; + } - packageUpdaters.add(new AndroidTestRunnerSplitUpdater()); - - PackageSharedLibraryUpdater[] updaterArray = packageUpdaters - .toArray(new PackageSharedLibraryUpdater[0]); - INSTANCE = new PackageBackwardCompatibility(hasOrgApacheHttpLegacy, updaterArray); + @VisibleForTesting + public static PackageSharedLibraryUpdater getInstance() { + return INSTANCE; } - private final boolean mRemovedOAHLFromBCP; + private final boolean mBootClassPathContainsOAHL; + + private final boolean mBootClassPathContainsATB; private final PackageSharedLibraryUpdater[] mPackageUpdaters; - public PackageBackwardCompatibility(boolean removedOAHLFromBCP, - PackageSharedLibraryUpdater[] packageUpdaters) { - this.mRemovedOAHLFromBCP = removedOAHLFromBCP; + public PackageBackwardCompatibility(boolean bootClassPathContainsOAHL, + boolean bootClassPathContainsATB, PackageSharedLibraryUpdater[] packageUpdaters) { + this.mBootClassPathContainsOAHL = bootClassPathContainsOAHL; + this.mBootClassPathContainsATB = bootClassPathContainsATB; this.mPackageUpdaters = packageUpdaters; } @@ -99,17 +140,25 @@ public class PackageBackwardCompatibility extends PackageSharedLibraryUpdater { @Override public void updatePackage(Package pkg) { - for (PackageSharedLibraryUpdater packageUpdater : mPackageUpdaters) { packageUpdater.updatePackage(pkg); } } /** - * True if the org.apache.http.legacy has been removed the bootclasspath, false otherwise. + * True if the org.apache.http.legacy is on the bootclasspath, false otherwise. + */ + @VisibleForTesting + public static boolean bootClassPathContainsOAHL() { + return INSTANCE.mBootClassPathContainsOAHL; + } + + /** + * True if the android.test.base is on the bootclasspath, false otherwise. */ - public static boolean removeOAHLFromBCP() { - return INSTANCE.mRemovedOAHLFromBCP; + @VisibleForTesting + public static boolean bootClassPathContainsATB() { + return INSTANCE.mBootClassPathContainsATB; } /** @@ -126,24 +175,9 @@ public class PackageBackwardCompatibility extends PackageSharedLibraryUpdater { @Override public void updatePackage(Package pkg) { - ArrayList<String> usesLibraries = pkg.usesLibraries; - ArrayList<String> usesOptionalLibraries = pkg.usesOptionalLibraries; - // android.test.runner has a dependency on android.test.mock so if android.test.runner // is present but android.test.mock is not then add android.test.mock. - boolean androidTestMockPresent = isLibraryPresent( - usesLibraries, usesOptionalLibraries, ANDROID_TEST_MOCK); - if (ArrayUtils.contains(usesLibraries, ANDROID_TEST_RUNNER) - && !androidTestMockPresent) { - usesLibraries.add(ANDROID_TEST_MOCK); - } - if (ArrayUtils.contains(usesOptionalLibraries, ANDROID_TEST_RUNNER) - && !androidTestMockPresent) { - usesOptionalLibraries.add(ANDROID_TEST_MOCK); - } - - pkg.usesLibraries = usesLibraries; - pkg.usesOptionalLibraries = usesOptionalLibraries; + prefixImplicitDependency(pkg, ANDROID_TEST_RUNNER, ANDROID_TEST_MOCK); } } @@ -155,13 +189,24 @@ public class PackageBackwardCompatibility extends PackageSharedLibraryUpdater { public static class RemoveUnnecessaryOrgApacheHttpLegacyLibrary extends PackageSharedLibraryUpdater { - private static final String APACHE_HTTP_LEGACY = "org.apache.http.legacy"; + @Override + public void updatePackage(Package pkg) { + removeLibrary(pkg, ORG_APACHE_HTTP_LEGACY); + } + + } + + /** + * Remove any usages of android.test.base from the shared library as the library is on the + * bootclasspath. + */ + @VisibleForTesting + public static class RemoveUnnecessaryAndroidTestBaseLibrary + extends PackageSharedLibraryUpdater { @Override public void updatePackage(Package pkg) { - pkg.usesLibraries = ArrayUtils.remove(pkg.usesLibraries, APACHE_HTTP_LEGACY); - pkg.usesOptionalLibraries = - ArrayUtils.remove(pkg.usesOptionalLibraries, APACHE_HTTP_LEGACY); + removeLibrary(pkg, ANDROID_TEST_BASE); } } } diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java index 3d26af1f15bb..2da893771d94 100644 --- a/core/java/android/content/pm/PackageParser.java +++ b/core/java/android/content/pm/PackageParser.java @@ -78,8 +78,10 @@ import android.util.ArrayMap; import android.util.ArraySet; import android.util.AttributeSet; import android.util.Base64; +import android.util.ByteStringUtils; import android.util.DisplayMetrics; import android.util.Log; +import android.util.PackageUtils; import android.util.Pair; import android.util.Slog; import android.util.SparseArray; @@ -5683,7 +5685,10 @@ public class PackageParser { return true; } - /** A container for signing-related data of an application package. */ + /** + * A container for signing-related data of an application package. + * @hide + */ public static final class SigningDetails implements Parcelable { @IntDef({SigningDetails.SignatureSchemeVersion.UNKNOWN, @@ -5705,15 +5710,54 @@ public class PackageParser { public final ArraySet<PublicKey> publicKeys; /** - * Collection of {@code Signature} objects, each of which is formed from a former signing - * certificate of this APK before it was changed by signing certificate rotation. + * APK Signature Scheme v3 includes support for adding a proof-of-rotation record that + * contains two pieces of information: + * 1) the past signing certificates + * 2) the flags that APK wants to assign to each of the past signing certificates. + * + * This collection of {@code Signature} objects, each of which is formed from a former + * signing certificate of this APK before it was changed by signing certificate rotation, + * represents the first piece of information. It is the APK saying to the rest of the + * world: "hey if you trust the old cert, you can trust me!" This is useful, if for + * instance, the platform would like to determine whether or not to allow this APK to do + * something it would've allowed it to do under the old cert (like upgrade). */ @Nullable public final Signature[] pastSigningCertificates; + /** special value used to see if cert is in package - not exposed to callers */ + private static final int PAST_CERT_EXISTS = 0; + + @IntDef( + flag = true, + value = {CertCapabilities.INSTALLED_DATA, + CertCapabilities.SHARED_USER_ID, + CertCapabilities.PERMISSION }) + public @interface CertCapabilities { + + /** accept data from already installed pkg with this cert */ + int INSTALLED_DATA = 1; + + /** accept sharedUserId with pkg with this cert */ + int SHARED_USER_ID = 2; + + /** grant SIGNATURE permissions to pkgs with this cert */ + int PERMISSION = 4; + } + /** - * Flags for the {@code pastSigningCertificates} collection, which indicate the capabilities - * the including APK wishes to grant to its past signing certificates. + * APK Signature Scheme v3 includes support for adding a proof-of-rotation record that + * contains two pieces of information: + * 1) the past signing certificates + * 2) the flags that APK wants to assign to each of the past signing certificates. + * + * These flags, which have a one-to-one relationship for the {@code pastSigningCertificates} + * collection, represent the second piece of information and are viewed as capabilities. + * They are an APK's way of telling the platform: "this is how I want to trust my old certs, + * please enforce that." This is useful for situation where this app itself is using its + * signing certificate as an authorization mechanism, like whether or not to allow another + * app to have its SIGNATURE permission. An app could specify whether to allow other apps + * signed by its old cert 'X' to still get a signature permission it defines, for example. */ @Nullable public final int[] pastSigningCertificatesFlags; @@ -5784,6 +5828,244 @@ public class PackageParser { return pastSigningCertificates != null && pastSigningCertificates.length > 0; } + /** + * Determines if the provided {@code oldDetails} is an ancestor of or the same as this one. + * If the {@code oldDetails} signing certificate appears in our pastSigningCertificates, + * then that means it has authorized a signing certificate rotation, which eventually leads + * to our certificate, and thus can be trusted. If this method evaluates to true, this + * SigningDetails object should be trusted if the previous one is. + */ + public boolean hasAncestorOrSelf(SigningDetails oldDetails) { + if (this == UNKNOWN || oldDetails == UNKNOWN) { + return false; + } + if (oldDetails.signatures.length > 1) { + + // multiple-signer packages cannot rotate signing certs, so we just compare current + // signers for an exact match + return signaturesMatchExactly(oldDetails); + } else { + + // we may have signing certificate rotation history, check to see if the oldDetails + // was one of our old signing certificates + return hasCertificate(oldDetails.signatures[0]); + } + } + + /** + * Similar to {@code hasAncestorOrSelf}. Returns true only if this {@code SigningDetails} + * is a descendant of {@code oldDetails}, not if they're the same. This is used to + * determine if this object is newer than the provided one. + */ + public boolean hasAncestor(SigningDetails oldDetails) { + if (this == UNKNOWN || oldDetails == UNKNOWN) { + return false; + } + if (this.hasPastSigningCertificates() && oldDetails.signatures.length == 1) { + + // the last entry in pastSigningCertificates is the current signer, ignore it + for (int i = 0; i < pastSigningCertificates.length - 1; i++) { + if (pastSigningCertificates[i].equals(oldDetails.signatures[i])) { + return true; + } + } + } + return false; + } + + /** + * Determines if the provided {@code oldDetails} is an ancestor of this one, and whether or + * not this one grants it the provided capability, represented by the {@code flags} + * parameter. In the event of signing certificate rotation, a package may still interact + * with entities signed by its old signing certificate and not want to break previously + * functioning behavior. The {@code flags} value determines which capabilities the app + * signed by the newer signing certificate would like to continue to give to its previous + * signing certificate(s). + */ + public boolean checkCapability(SigningDetails oldDetails, @CertCapabilities int flags) { + if (this == UNKNOWN || oldDetails == UNKNOWN) { + return false; + } + if (oldDetails.signatures.length > 1) { + + // multiple-signer packages cannot rotate signing certs, so we must have an exact + // match, which also means all capabilities are granted + return signaturesMatchExactly(oldDetails); + } else { + + // we may have signing certificate rotation history, check to see if the oldDetails + // was one of our old signing certificates, and if we grant it the capability it's + // requesting + return hasCertificate(oldDetails.signatures[0], flags); + } + } + + /** + * A special case of {@code checkCapability} which re-encodes both sets of signing + * certificates to counteract a previous re-encoding. + */ + public boolean checkCapabilityRecover(SigningDetails oldDetails, + @CertCapabilities int flags) throws CertificateException { + if (oldDetails == UNKNOWN || this == UNKNOWN) { + return false; + } + if (hasPastSigningCertificates() && oldDetails.signatures.length == 1) { + + // signing certificates may have rotated, check entire history for effective match + for (int i = 0; i < pastSigningCertificates.length; i++) { + if (Signature.areEffectiveMatch( + oldDetails.signatures[0], + pastSigningCertificates[i]) + && pastSigningCertificatesFlags[i] == flags) { + return true; + } + } + } else { + return Signature.areEffectiveMatch(oldDetails.signatures, signatures); + } + return false; + } + + /** + * Determine if {@code signature} is in this SigningDetails' signing certificate history, + * including the current signer. Automatically returns false if this object has multiple + * signing certificates, since rotation is only supported for single-signers; this is + * enforced by {@code hasCertificateInternal}. + */ + public boolean hasCertificate(Signature signature) { + return hasCertificateInternal(signature, PAST_CERT_EXISTS); + } + + /** + * Determine if {@code signature} is in this SigningDetails' signing certificate history, + * including the current signer, and whether or not it has the given permission. + * Certificates which match our current signer automatically get all capabilities. + * Automatically returns false if this object has multiple signing certificates, since + * rotation is only supported for single-signers. + */ + public boolean hasCertificate(Signature signature, @CertCapabilities int flags) { + return hasCertificateInternal(signature, flags); + } + + /** Convenient wrapper for calling {@code hasCertificate} with certificate's raw bytes. */ + public boolean hasCertificate(byte[] certificate) { + Signature signature = new Signature(certificate); + return hasCertificate(signature); + } + + private boolean hasCertificateInternal(Signature signature, int flags) { + if (this == UNKNOWN) { + return false; + } + + // only single-signed apps can have pastSigningCertificates + if (hasPastSigningCertificates()) { + + // check all past certs, except for the current one, which automatically gets all + // capabilities, since it is the same as the current signature + for (int i = 0; i < pastSigningCertificates.length - 1; i++) { + if (pastSigningCertificates[i].equals(signature)) { + if (flags == PAST_CERT_EXISTS + || (flags & pastSigningCertificatesFlags[i]) == flags) { + return true; + } + } + } + } + + // not in previous certs signing history, just check the current signer and make sure + // we are singly-signed + return signatures.length == 1 && signatures[0].equals(signature); + } + + /** + * Determines if the provided {@code sha256String} is an ancestor of this one, and whether + * or not this one grants it the provided capability, represented by the {@code flags} + * parameter. In the event of signing certificate rotation, a package may still interact + * with entities signed by its old signing certificate and not want to break previously + * functioning behavior. The {@code flags} value determines which capabilities the app + * signed by the newer signing certificate would like to continue to give to its previous + * signing certificate(s). + * + * @param sha256String A hex-encoded representation of a sha256 digest. In the case of an + * app with multiple signers, this represents the hex-encoded sha256 + * digest of the combined hex-encoded sha256 digests of each individual + * signing certificate according to {@link + * PackageUtils#computeSignaturesSha256Digest(Signature[])} + */ + public boolean checkCapability(String sha256String, @CertCapabilities int flags) { + if (this == UNKNOWN) { + return false; + } + + // first see if the hash represents a single-signer in our signing history + byte[] sha256Bytes = ByteStringUtils.fromHexToByteArray(sha256String); + if (hasSha256Certificate(sha256Bytes, flags)) { + return true; + } + + // Not in signing history, either represents multiple signatures or not a match. + // Multiple signers can't rotate, so no need to check flags, just see if the SHAs match. + // We already check the single-signer case above as part of hasSha256Certificate, so no + // need to verify we have multiple signers, just run the old check + // just consider current signing certs + final String[] mSignaturesSha256Digests = + PackageUtils.computeSignaturesSha256Digests(signatures); + final String mSignaturesSha256Digest = + PackageUtils.computeSignaturesSha256Digest(mSignaturesSha256Digests); + return mSignaturesSha256Digest.equals(sha256String); + } + + /** + * Determine if the {@code sha256Certificate} is in this SigningDetails' signing certificate + * history, including the current signer. Automatically returns false if this object has + * multiple signing certificates, since rotation is only supported for single-signers. + */ + public boolean hasSha256Certificate(byte[] sha256Certificate) { + return hasSha256CertificateInternal(sha256Certificate, PAST_CERT_EXISTS); + } + + /** + * Determine if the {@code sha256Certificate} certificate hash corresponds to a signing + * certificate in this SigningDetails' signing certificate history, including the current + * signer, and whether or not it has the given permission. Certificates which match our + * current signer automatically get all capabilities. Automatically returns false if this + * object has multiple signing certificates, since rotation is only supported for + * single-signers. + */ + public boolean hasSha256Certificate(byte[] sha256Certificate, @CertCapabilities int flags) { + return hasSha256CertificateInternal(sha256Certificate, flags); + } + + private boolean hasSha256CertificateInternal(byte[] sha256Certificate, int flags) { + if (this == UNKNOWN) { + return false; + } + if (hasPastSigningCertificates()) { + + // check all past certs, except for the last one, which automatically gets all + // capabilities, since it is the same as the current signature, and is checked below + for (int i = 0; i < pastSigningCertificates.length - 1; i++) { + byte[] digest = PackageUtils.computeSha256DigestBytes( + pastSigningCertificates[i].toByteArray()); + if (Arrays.equals(sha256Certificate, digest)) { + if (flags == PAST_CERT_EXISTS + || (flags & pastSigningCertificatesFlags[i]) == flags) { + return true; + } + } + } + } + + // not in previous certs signing history, just check the current signer + if (signatures.length == 1) { + byte[] digest = + PackageUtils.computeSha256DigestBytes(signatures[0].toByteArray()); + return Arrays.equals(sha256Certificate, digest); + } + return false; + } + /** Returns true if the signatures in this and other match exactly. */ public boolean signaturesMatchExactly(SigningDetails other) { return Signature.areExactMatch(this.signatures, other.signatures); diff --git a/core/java/android/content/pm/PackageSharedLibraryUpdater.java b/core/java/android/content/pm/PackageSharedLibraryUpdater.java index 49d884ca2f34..fa894320b7d0 100644 --- a/core/java/android/content/pm/PackageSharedLibraryUpdater.java +++ b/core/java/android/content/pm/PackageSharedLibraryUpdater.java @@ -17,6 +17,7 @@ package android.content.pm; import android.annotation.NonNull; import android.annotation.Nullable; +import android.os.Build; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.ArrayUtils; @@ -38,6 +39,12 @@ public abstract class PackageSharedLibraryUpdater { */ public abstract void updatePackage(PackageParser.Package pkg); + static void removeLibrary(PackageParser.Package pkg, String libraryName) { + pkg.usesLibraries = ArrayUtils.remove(pkg.usesLibraries, libraryName); + pkg.usesOptionalLibraries = + ArrayUtils.remove(pkg.usesOptionalLibraries, libraryName); + } + static @NonNull <T> ArrayList<T> prefix(@Nullable ArrayList<T> cur, T val) { if (cur == null) { @@ -47,9 +54,54 @@ public abstract class PackageSharedLibraryUpdater { return cur; } - static boolean isLibraryPresent(ArrayList<String> usesLibraries, + private static boolean isLibraryPresent(ArrayList<String> usesLibraries, ArrayList<String> usesOptionalLibraries, String apacheHttpLegacy) { return ArrayUtils.contains(usesLibraries, apacheHttpLegacy) || ArrayUtils.contains(usesOptionalLibraries, apacheHttpLegacy); } + + static boolean apkTargetsApiLevelLessThanOrEqualToOMR1(PackageParser.Package pkg) { + int targetSdkVersion = pkg.applicationInfo.targetSdkVersion; + return targetSdkVersion <= Build.VERSION_CODES.O_MR1; + } + + /** + * Add an implicit dependency. + * + * <p>If the package has an existing dependency on {@code existingLibrary} then prefix it with + * the {@code implicitDependency} if it is not already in the list of libraries. + * + * @param pkg the {@link PackageParser.Package} to update. + * @param existingLibrary the existing library. + * @param implicitDependency the implicit dependency to add + */ + void prefixImplicitDependency(PackageParser.Package pkg, String existingLibrary, + String implicitDependency) { + ArrayList<String> usesLibraries = pkg.usesLibraries; + ArrayList<String> usesOptionalLibraries = pkg.usesOptionalLibraries; + + if (!isLibraryPresent(usesLibraries, usesOptionalLibraries, implicitDependency)) { + if (ArrayUtils.contains(usesLibraries, existingLibrary)) { + prefix(usesLibraries, implicitDependency); + } else if (ArrayUtils.contains(usesOptionalLibraries, existingLibrary)) { + prefix(usesOptionalLibraries, implicitDependency); + } + + pkg.usesLibraries = usesLibraries; + pkg.usesOptionalLibraries = usesOptionalLibraries; + } + } + + void prefixRequiredLibrary(PackageParser.Package pkg, String libraryName) { + ArrayList<String> usesLibraries = pkg.usesLibraries; + ArrayList<String> usesOptionalLibraries = pkg.usesOptionalLibraries; + + boolean alreadyPresent = isLibraryPresent( + usesLibraries, usesOptionalLibraries, libraryName); + if (!alreadyPresent) { + usesLibraries = prefix(usesLibraries, libraryName); + + pkg.usesLibraries = usesLibraries; + } + } } diff --git a/core/java/android/content/pm/SharedLibraryNames.java b/core/java/android/content/pm/SharedLibraryNames.java new file mode 100644 index 000000000000..83e86636424a --- /dev/null +++ b/core/java/android/content/pm/SharedLibraryNames.java @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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.content.pm; + +/** + * A set of shared library names + * + * @hide + */ +public class SharedLibraryNames { + + static final String ANDROID_TEST_BASE = "android.test.base"; + + static final String ANDROID_TEST_MOCK = "android.test.mock"; + + static final String ANDROID_TEST_RUNNER = "android.test.runner"; + + static final String ORG_APACHE_HTTP_LEGACY = "org.apache.http.legacy"; +} diff --git a/core/java/android/content/pm/Signature.java b/core/java/android/content/pm/Signature.java index fdc54aedac96..a2a14eddd59f 100644 --- a/core/java/android/content/pm/Signature.java +++ b/core/java/android/content/pm/Signature.java @@ -285,6 +285,29 @@ public class Signature implements Parcelable { } /** + * Test if given {@link Signature} objects are effectively equal. In rare + * cases, certificates can have slightly malformed encoding which causes + * exact-byte checks to fail. + * <p> + * To identify effective equality, we bounce the certificates through an + * decode/encode pass before doing the exact-byte check. To reduce attack + * surface area, we only allow a byte size delta of a few bytes. + * + * @throws CertificateException if the before/after length differs + * substantially, usually a signal of something fishy going on. + * @hide + */ + public static boolean areEffectiveMatch(Signature a, Signature b) + throws CertificateException { + final CertificateFactory cf = CertificateFactory.getInstance("X.509"); + + final Signature aPrime = bounce(cf, a); + final Signature bPrime = bounce(cf, b); + + return aPrime.equals(bPrime); + } + + /** * Bounce the given {@link Signature} through a decode/encode cycle. * * @throws CertificateException if the before/after length differs diff --git a/core/java/android/database/CursorWindow.java b/core/java/android/database/CursorWindow.java index f84ec65fe0ae..a748f4d2cbce 100644 --- a/core/java/android/database/CursorWindow.java +++ b/core/java/android/database/CursorWindow.java @@ -28,6 +28,7 @@ import android.util.Log; import android.util.LongSparseArray; import android.util.SparseIntArray; +import dalvik.annotation.optimization.FastNative; import dalvik.system.CloseGuard; /** @@ -62,28 +63,43 @@ public class CursorWindow extends SQLiteClosable implements Parcelable { private static native void nativeDispose(long windowPtr); private static native void nativeWriteToParcel(long windowPtr, Parcel parcel); + private static native String nativeGetName(long windowPtr); + private static native byte[] nativeGetBlob(long windowPtr, int row, int column); + private static native String nativeGetString(long windowPtr, int row, int column); + private static native void nativeCopyStringToBuffer(long windowPtr, int row, int column, + CharArrayBuffer buffer); + private static native boolean nativePutBlob(long windowPtr, byte[] value, int row, int column); + private static native boolean nativePutString(long windowPtr, String value, + int row, int column); + + // Below native methods don't do unconstrained work, so are FastNative for performance + + @FastNative private static native void nativeClear(long windowPtr); + @FastNative private static native int nativeGetNumRows(long windowPtr); + @FastNative private static native boolean nativeSetNumColumns(long windowPtr, int columnNum); + @FastNative private static native boolean nativeAllocRow(long windowPtr); + @FastNative private static native void nativeFreeLastRow(long windowPtr); + @FastNative private static native int nativeGetType(long windowPtr, int row, int column); - private static native byte[] nativeGetBlob(long windowPtr, int row, int column); - private static native String nativeGetString(long windowPtr, int row, int column); + @FastNative private static native long nativeGetLong(long windowPtr, int row, int column); + @FastNative private static native double nativeGetDouble(long windowPtr, int row, int column); - private static native void nativeCopyStringToBuffer(long windowPtr, int row, int column, - CharArrayBuffer buffer); - private static native boolean nativePutBlob(long windowPtr, byte[] value, int row, int column); - private static native boolean nativePutString(long windowPtr, String value, int row, int column); + @FastNative private static native boolean nativePutLong(long windowPtr, long value, int row, int column); + @FastNative private static native boolean nativePutDouble(long windowPtr, double value, int row, int column); + @FastNative private static native boolean nativePutNull(long windowPtr, int row, int column); - private static native String nativeGetName(long windowPtr); /** * Creates a new empty cursor window and gives it a name. diff --git a/core/java/android/hardware/Sensor.java b/core/java/android/hardware/Sensor.java index 7fb0c89e7f92..7297426130ac 100644 --- a/core/java/android/hardware/Sensor.java +++ b/core/java/android/hardware/Sensor.java @@ -22,7 +22,9 @@ import android.os.Build; /** * Class representing a sensor. Use {@link SensorManager#getSensorList} to get - * the list of available Sensors. + * the list of available sensors. For more information about Android sensors, + * read the + * <a href="/guide/topics/sensors/sensors_motion.html">Motion Sensors guide</a>.</p> * * @see SensorManager * @see SensorEventListener diff --git a/core/java/android/inputmethodservice/KeyboardView.java b/core/java/android/inputmethodservice/KeyboardView.java index 13b9206b12ee..2306e5fa3dc1 100644 --- a/core/java/android/inputmethodservice/KeyboardView.java +++ b/core/java/android/inputmethodservice/KeyboardView.java @@ -61,6 +61,7 @@ import java.util.Map; * @attr ref android.R.styleable#KeyboardView_keyBackground * @attr ref android.R.styleable#KeyboardView_keyPreviewLayout * @attr ref android.R.styleable#KeyboardView_keyPreviewOffset + * @attr ref android.R.styleable#KeyboardView_keyPreviewHeight * @attr ref android.R.styleable#KeyboardView_labelTextSize * @attr ref android.R.styleable#KeyboardView_keyTextSize * @attr ref android.R.styleable#KeyboardView_keyTextColor diff --git a/core/java/android/os/BatteryManagerInternal.java b/core/java/android/os/BatteryManagerInternal.java index f3a95b90d5c1..a86237dd271f 100644 --- a/core/java/android/os/BatteryManagerInternal.java +++ b/core/java/android/os/BatteryManagerInternal.java @@ -24,26 +24,63 @@ package android.os; public abstract class BatteryManagerInternal { /** * Returns true if the device is plugged into any of the specified plug types. + * + * This is a simple accessor that's safe to be called from any locks, but internally it may + * wait on the battery service lock. */ public abstract boolean isPowered(int plugTypeSet); /** * Returns the current plug type. + * + * This is a simple accessor that's safe to be called from any locks, but internally it may + * wait on the battery service lock. */ public abstract int getPlugType(); /** * Returns battery level as a percentage. + * + * This is a simple accessor that's safe to be called from any locks, but internally it may + * wait on the battery service lock. */ public abstract int getBatteryLevel(); /** + * Instantaneous battery capacity in uA-h, as defined in the HealthInfo HAL struct. + * Please note apparently it could be bigger than {@link #getBatteryFullCharge}. + * + * This is a simple accessor that's safe to be called from any locks, but internally it may + * wait on the battery service lock. + * + * @see android.hardware.health.V1_0.HealthInfo#batteryChargeCounter + */ + public abstract int getBatteryChargeCounter(); + + /** + * Battery charge value when it is considered to be "full" in uA-h , as defined in the + * HealthInfo HAL struct. + * + * This is a simple accessor that's safe to be called from any locks, but internally it may + * wait on the battery service lock. + * + * @see android.hardware.health.V1_0.HealthInfo#batteryFullCharge + */ + public abstract int getBatteryFullCharge(); + + /** * Returns whether we currently consider the battery level to be low. + * + * This is a simple accessor that's safe to be called from any locks, but internally it may + * wait on the battery service lock. */ public abstract boolean getBatteryLevelLow(); /** * Returns a non-zero value if an unsupported charger is attached. + * + * This is a simple accessor that's safe to be called from any locks, but internally it may + * wait on the battery service lock. */ public abstract int getInvalidCharger(); } diff --git a/core/java/android/os/Binder.java b/core/java/android/os/Binder.java index eb264d6d308c..4aadc5b49e71 100644 --- a/core/java/android/os/Binder.java +++ b/core/java/android/os/Binder.java @@ -21,7 +21,9 @@ import android.annotation.Nullable; import android.util.ExceptionUtils; import android.util.Log; import android.util.Slog; +import android.util.SparseIntArray; +import com.android.internal.os.BinderInternal; import com.android.internal.util.FastPrintWriter; import com.android.internal.util.FunctionalUtils.ThrowingRunnable; import com.android.internal.util.FunctionalUtils.ThrowingSupplier; @@ -934,6 +936,7 @@ final class BinderProxy implements IBinder { final int totalUnclearedSize = unclearedSize(); if (totalUnclearedSize >= CRASH_AT_SIZE) { dumpProxyInterfaceCounts(); + dumpPerUidProxyCounts(); Runtime.getRuntime().gc(); throw new AssertionError("Binder ProxyMap has too many entries: " + totalSize + " (total), " + totalUnclearedSize + " (uncleared), " @@ -987,6 +990,20 @@ final class BinderProxy implements IBinder { } } + /** + * Dump per uid binder proxy counts to the logcat. + */ + private void dumpPerUidProxyCounts() { + SparseIntArray counts = BinderInternal.nGetBinderProxyPerUidCounts(); + if (counts.size() == 0) return; + Log.d(Binder.TAG, "Per Uid Binder Proxy Counts:"); + for (int i = 0; i < counts.size(); i++) { + final int uid = counts.keyAt(i); + final int binderCount = counts.valueAt(i); + Log.d(Binder.TAG, "UID : " + uid + " count = " + binderCount); + } + } + // Corresponding ArrayLists in the following two arrays always have the same size. // They contain no empty entries. However WeakReferences in the values ArrayLists // may have been cleared. diff --git a/core/java/android/os/Debug.java b/core/java/android/os/Debug.java index f83bda06b0b2..e6069644caa5 100644 --- a/core/java/android/os/Debug.java +++ b/core/java/android/os/Debug.java @@ -116,6 +116,8 @@ public final class Debug /** The proportional set size that is swappable for dalvik heap. */ /** @hide We may want to expose this, eventually. */ public int dalvikSwappablePss; + /** @hide The resident set size for dalvik heap. (Without other Dalvik overhead.) */ + public int dalvikRss; /** The private dirty pages used by dalvik heap. */ public int dalvikPrivateDirty; /** The shared dirty pages used by dalvik heap. */ @@ -138,6 +140,8 @@ public final class Debug /** The proportional set size that is swappable for the native heap. */ /** @hide We may want to expose this, eventually. */ public int nativeSwappablePss; + /** @hide The resident set size for the native heap. */ + public int nativeRss; /** The private dirty pages used by the native heap. */ public int nativePrivateDirty; /** The shared dirty pages used by the native heap. */ @@ -160,6 +164,8 @@ public final class Debug /** The proportional set size that is swappable for everything else. */ /** @hide We may want to expose this, eventually. */ public int otherSwappablePss; + /** @hide The resident set size for everything else. */ + public int otherRss; /** The private dirty pages used by everything else. */ public int otherPrivateDirty; /** The shared dirty pages used by everything else. */ @@ -288,24 +294,26 @@ public final class Debug public static final int NUM_DVK_STATS = 14; /** @hide */ - public static final int NUM_CATEGORIES = 8; + public static final int NUM_CATEGORIES = 9; /** @hide */ - public static final int offsetPss = 0; + public static final int OFFSET_PSS = 0; /** @hide */ - public static final int offsetSwappablePss = 1; + public static final int OFFSET_SWAPPABLE_PSS = 1; /** @hide */ - public static final int offsetPrivateDirty = 2; + public static final int OFFSET_RSS = 2; /** @hide */ - public static final int offsetSharedDirty = 3; + public static final int OFFSET_PRIVATE_DIRTY = 3; /** @hide */ - public static final int offsetPrivateClean = 4; + public static final int OFFSET_SHARED_DIRTY = 4; /** @hide */ - public static final int offsetSharedClean = 5; + public static final int OFFSET_PRIVATE_CLEAN = 5; /** @hide */ - public static final int offsetSwappedOut = 6; + public static final int OFFSET_SHARED_CLEAN = 6; /** @hide */ - public static final int offsetSwappedOutPss = 7; + public static final int OFFSET_SWAPPED_OUT = 7; + /** @hide */ + public static final int OFFSET_SWAPPED_OUT_PSS = 8; private int[] otherStats = new int[(NUM_OTHER_STATS+NUM_DVK_STATS)*NUM_CATEGORIES]; @@ -337,6 +345,13 @@ public final class Debug } /** + * @hide Return total RSS memory usage in kB. + */ + public int getTotalRss() { + return dalvikRss + nativeRss + otherRss; + } + + /** * Return total private dirty memory usage in kB. */ public int getTotalPrivateDirty() { @@ -382,29 +397,32 @@ public final class Debug /** @hide */ public int getOtherPss(int which) { - return otherStats[which*NUM_CATEGORIES + offsetPss]; + return otherStats[which * NUM_CATEGORIES + OFFSET_PSS]; } - /** @hide */ public int getOtherSwappablePss(int which) { - return otherStats[which*NUM_CATEGORIES + offsetSwappablePss]; + return otherStats[which * NUM_CATEGORIES + OFFSET_SWAPPABLE_PSS]; } + /** @hide */ + public int getOtherRss(int which) { + return otherStats[which * NUM_CATEGORIES + OFFSET_RSS]; + } /** @hide */ public int getOtherPrivateDirty(int which) { - return otherStats[which*NUM_CATEGORIES + offsetPrivateDirty]; + return otherStats[which * NUM_CATEGORIES + OFFSET_PRIVATE_DIRTY]; } /** @hide */ public int getOtherSharedDirty(int which) { - return otherStats[which*NUM_CATEGORIES + offsetSharedDirty]; + return otherStats[which * NUM_CATEGORIES + OFFSET_SHARED_DIRTY]; } /** @hide */ public int getOtherPrivateClean(int which) { - return otherStats[which*NUM_CATEGORIES + offsetPrivateClean]; + return otherStats[which * NUM_CATEGORIES + OFFSET_PRIVATE_CLEAN]; } /** @hide */ @@ -414,17 +432,17 @@ public final class Debug /** @hide */ public int getOtherSharedClean(int which) { - return otherStats[which*NUM_CATEGORIES + offsetSharedClean]; + return otherStats[which * NUM_CATEGORIES + OFFSET_SHARED_CLEAN]; } /** @hide */ public int getOtherSwappedOut(int which) { - return otherStats[which*NUM_CATEGORIES + offsetSwappedOut]; + return otherStats[which * NUM_CATEGORIES + OFFSET_SWAPPED_OUT]; } /** @hide */ public int getOtherSwappedOutPss(int which) { - return otherStats[which*NUM_CATEGORIES + offsetSwappedOutPss]; + return otherStats[which * NUM_CATEGORIES + OFFSET_SWAPPED_OUT_PSS]; } /** @hide */ @@ -741,6 +759,7 @@ public final class Debug public void writeToParcel(Parcel dest, int flags) { dest.writeInt(dalvikPss); dest.writeInt(dalvikSwappablePss); + dest.writeInt(dalvikRss); dest.writeInt(dalvikPrivateDirty); dest.writeInt(dalvikSharedDirty); dest.writeInt(dalvikPrivateClean); @@ -749,6 +768,7 @@ public final class Debug dest.writeInt(dalvikSwappedOutPss); dest.writeInt(nativePss); dest.writeInt(nativeSwappablePss); + dest.writeInt(nativeRss); dest.writeInt(nativePrivateDirty); dest.writeInt(nativeSharedDirty); dest.writeInt(nativePrivateClean); @@ -757,6 +777,7 @@ public final class Debug dest.writeInt(nativeSwappedOutPss); dest.writeInt(otherPss); dest.writeInt(otherSwappablePss); + dest.writeInt(otherRss); dest.writeInt(otherPrivateDirty); dest.writeInt(otherSharedDirty); dest.writeInt(otherPrivateClean); @@ -770,6 +791,7 @@ public final class Debug public void readFromParcel(Parcel source) { dalvikPss = source.readInt(); dalvikSwappablePss = source.readInt(); + dalvikRss = source.readInt(); dalvikPrivateDirty = source.readInt(); dalvikSharedDirty = source.readInt(); dalvikPrivateClean = source.readInt(); @@ -778,6 +800,7 @@ public final class Debug dalvikSwappedOutPss = source.readInt(); nativePss = source.readInt(); nativeSwappablePss = source.readInt(); + nativeRss = source.readInt(); nativePrivateDirty = source.readInt(); nativeSharedDirty = source.readInt(); nativePrivateClean = source.readInt(); @@ -786,6 +809,7 @@ public final class Debug nativeSwappedOutPss = source.readInt(); otherPss = source.readInt(); otherSwappablePss = source.readInt(); + otherRss = source.readInt(); otherPrivateDirty = source.readInt(); otherSharedDirty = source.readInt(); otherPrivateClean = source.readInt(); diff --git a/core/java/android/os/HwBinder.java b/core/java/android/os/HwBinder.java index 2a088a659b0f..cdee1101c27b 100644 --- a/core/java/android/os/HwBinder.java +++ b/core/java/android/os/HwBinder.java @@ -87,6 +87,9 @@ public abstract class HwBinder implements IHwBinder { * Configures how many threads the process-wide hwbinder threadpool * has to process incoming requests. * + * @param maxThreads total number of threads to create (includes this thread if + * callerWillJoin is true) + * @param callerWillJoin whether joinRpcThreadpool will be called in advance * @hide */ @SystemApi @@ -125,6 +128,12 @@ public abstract class HwBinder implements IHwBinder { /** * Enable instrumentation if available. + * + * On a non-user build, this method: + * - tries to enable atracing (if enabled) + * - tries to enable coverage dumps (if running in VTS) + * - tries to enable record and replay (if running in VTS) + * * @hide */ @SystemApi diff --git a/core/java/android/os/IHwBinder.java b/core/java/android/os/IHwBinder.java index 0c592e1f04b8..a565dee5ddd0 100644 --- a/core/java/android/os/IHwBinder.java +++ b/core/java/android/os/IHwBinder.java @@ -30,6 +30,11 @@ public interface IHwBinder { /** * Process a hwbinder transaction. * + * @param code interface specific code for interface. + * @param request parceled transaction + * @param reply object to parcel reply into + * @param flags transaction flags to be chosen by wire protocol + * * @hide */ @SystemApi @@ -39,6 +44,7 @@ public interface IHwBinder { /** * Return as IHwInterface instance only if this implements descriptor. + * * @param descriptor for example foo.bar@1.0::IBaz * @hide */ @@ -53,6 +59,8 @@ public interface IHwBinder { public interface DeathRecipient { /** * Callback for a registered process dying. + * + * @param cookie cookie this death recipient was registered with. */ @SystemApi public void serviceDied(long cookie); @@ -61,11 +69,16 @@ public interface IHwBinder { /** * Notifies the death recipient with the cookie when the process containing * this binder dies. + * + * @param recipient callback object to be called on object death. + * @param cookie value to be given to callback on object death. */ @SystemApi public boolean linkToDeath(DeathRecipient recipient, long cookie); /** * Unregisters the death recipient from this binder. + * + * @param recipient callback to no longer recieve death notifications on this binder. */ @SystemApi public boolean unlinkToDeath(DeathRecipient recipient); diff --git a/core/java/android/os/IHwInterface.java b/core/java/android/os/IHwInterface.java index a2f59a9abb81..1d9e2b0197c7 100644 --- a/core/java/android/os/IHwInterface.java +++ b/core/java/android/os/IHwInterface.java @@ -21,7 +21,7 @@ import android.annotation.SystemApi; @SystemApi public interface IHwInterface { /** - * Returns the binder object that corresponds to an interface. + * @return the binder object that corresponds to this interface. */ @SystemApi public IHwBinder asBinder(); diff --git a/core/java/android/os/IncidentManager.java b/core/java/android/os/IncidentManager.java index 1336c66b7133..9b6d6e56407a 100644 --- a/core/java/android/os/IncidentManager.java +++ b/core/java/android/os/IncidentManager.java @@ -33,10 +33,12 @@ import android.util.Slog; @TestApi @SystemService(Context.INCIDENT_SERVICE) public class IncidentManager { - private static final String TAG = "incident"; + private static final String TAG = "IncidentManager"; private final Context mContext; + private IIncidentManager mService; + /** * @hide */ @@ -96,19 +98,45 @@ public class IncidentManager { reportIncidentInternal(args); } - private void reportIncidentInternal(IncidentReportArgs args) { - final IIncidentManager service = IIncidentManager.Stub.asInterface( - ServiceManager.getService(Context.INCIDENT_SERVICE)); - if (service == null) { - Slog.e(TAG, "reportIncident can't find incident binder service"); - return; + private class IncidentdDeathRecipient implements IBinder.DeathRecipient { + @Override + public void binderDied() { + synchronized (this) { + mService = null; + } } + } + private void reportIncidentInternal(IncidentReportArgs args) { try { + final IIncidentManager service = getIIncidentManagerLocked(); + if (service == null) { + Slog.e(TAG, "reportIncident can't find incident binder service"); + return; + } service.reportIncident(args); } catch (RemoteException ex) { Slog.e(TAG, "reportIncident failed", ex); } } + + private IIncidentManager getIIncidentManagerLocked() throws RemoteException { + if (mService != null) { + return mService; + } + + synchronized (this) { + if (mService != null) { + return mService; + } + mService = IIncidentManager.Stub.asInterface( + ServiceManager.getService(Context.INCIDENT_SERVICE)); + if (mService != null) { + mService.asBinder().linkToDeath(new IncidentdDeathRecipient(), 0); + } + return mService; + } + } + } diff --git a/core/java/android/os/IncidentReportArgs.java b/core/java/android/os/IncidentReportArgs.java index fd0ebcfea080..9fa129cb98b4 100644 --- a/core/java/android/os/IncidentReportArgs.java +++ b/core/java/android/os/IncidentReportArgs.java @@ -32,6 +32,9 @@ import java.util.ArrayList; @TestApi public final class IncidentReportArgs implements Parcelable { + private static final int DEST_EXPLICIT = 100; + private static final int DEST_AUTO = 200; + private final IntArray mSections = new IntArray(); private final ArrayList<byte[]> mHeaders = new ArrayList<byte[]>(); private boolean mAll; @@ -41,6 +44,7 @@ public final class IncidentReportArgs implements Parcelable { * Construct an incident report args with no fields. */ public IncidentReportArgs() { + mDest = DEST_AUTO; } /** @@ -143,7 +147,14 @@ public final class IncidentReportArgs implements Parcelable { * @hide */ public void setPrivacyPolicy(int dest) { - mDest = dest; + switch (dest) { + case DEST_EXPLICIT: + case DEST_AUTO: + mDest = dest; + break; + default: + mDest = DEST_AUTO; + } } /** diff --git a/core/java/android/os/WorkSource.java b/core/java/android/os/WorkSource.java index d59200095e60..17d83db685c8 100644 --- a/core/java/android/os/WorkSource.java +++ b/core/java/android/os/WorkSource.java @@ -1,6 +1,7 @@ package android.os; import android.annotation.Nullable; +import android.annotation.SystemApi; import android.content.Context; import android.os.WorkSourceProto; import android.provider.Settings; @@ -8,6 +9,8 @@ import android.provider.Settings.Global; import android.util.Log; import android.util.proto.ProtoOutputStream; +import com.android.internal.annotations.VisibleForTesting; + import java.util.ArrayList; import java.util.Arrays; @@ -464,6 +467,7 @@ public class WorkSource implements Parcelable { * * @hide */ + @SystemApi public WorkChain createWorkChain() { if (mChains == null) { mChains = new ArrayList<>(4); @@ -854,7 +858,8 @@ public class WorkSource implements Parcelable { * * @hide */ - public static class WorkChain implements Parcelable { + @SystemApi + public static final class WorkChain implements Parcelable { private int mSize; private int[] mUids; private String[] mTags; @@ -866,7 +871,8 @@ public class WorkSource implements Parcelable { mTags = new String[4]; } - // @VisibleForTesting + /** @hide */ + @VisibleForTesting public WorkChain(WorkChain other) { mSize = other.mSize; mUids = other.mUids.clone(); @@ -913,16 +919,22 @@ public class WorkSource implements Parcelable { // TODO: The following three trivial getters are purely for testing and will be removed // once we have higher level logic in place, e.g for serializing this WorkChain to a proto, // diffing it etc. - // - // @VisibleForTesting + + + /** @hide */ + @VisibleForTesting public int[] getUids() { return mUids; } - // @VisibleForTesting + + /** @hide */ + @VisibleForTesting public String[] getTags() { return mTags; } - // @VisibleForTesting + + /** @hide */ + @VisibleForTesting public int getSize() { return mSize; } diff --git a/core/java/android/preference/SeekBarVolumizer.java b/core/java/android/preference/SeekBarVolumizer.java index 3d2e1d1f1d24..d77428193749 100644 --- a/core/java/android/preference/SeekBarVolumizer.java +++ b/core/java/android/preference/SeekBarVolumizer.java @@ -34,6 +34,7 @@ import android.preference.VolumePreference.VolumeStore; import android.provider.Settings; import android.provider.Settings.Global; import android.provider.Settings.System; +import android.service.notification.ZenModeConfig; import android.util.Log; import android.widget.SeekBar; import android.widget.SeekBar.OnSeekBarChangeListener; @@ -87,10 +88,22 @@ public class SeekBarVolumizer implements OnSeekBarChangeListener, Handler.Callba private static final int MSG_INIT_SAMPLE = 3; private static final int CHECK_RINGTONE_PLAYBACK_DELAY_MS = 1000; + private NotificationManager.Policy mNotificationPolicy; + private boolean mAllowAlarms; + private boolean mAllowMediaSystem; + private boolean mAllowRinger; + public SeekBarVolumizer(Context context, int streamType, Uri defaultUri, Callback callback) { mContext = context; mAudioManager = context.getSystemService(AudioManager.class); mNotificationManager = context.getSystemService(NotificationManager.class); + mNotificationPolicy = mNotificationManager.getNotificationPolicy(); + mAllowAlarms = (mNotificationPolicy.priorityCategories & NotificationManager.Policy + .PRIORITY_CATEGORY_ALARMS) != 0; + mAllowMediaSystem = (mNotificationPolicy.priorityCategories & NotificationManager.Policy + .PRIORITY_CATEGORY_MEDIA_SYSTEM_OTHER) != 0; + mAllowRinger = !ZenModeConfig.areAllPriorityOnlyNotificationZenSoundsMuted( + mNotificationPolicy); mStreamType = streamType; mAffectedByRingerMode = mAudioManager.isStreamAffectedByRingerMode(mStreamType); mNotificationOrRing = isNotificationOrRing(mStreamType); @@ -122,6 +135,14 @@ public class SeekBarVolumizer implements OnSeekBarChangeListener, Handler.Callba return stream == AudioManager.STREAM_RING || stream == AudioManager.STREAM_NOTIFICATION; } + private static boolean isAlarmsStream(int stream) { + return stream == AudioManager.STREAM_ALARM; + } + + private static boolean isMediaOrSystemStream(int stream) { + return stream == AudioManager.STREAM_MUSIC || stream == AudioManager.STREAM_SYSTEM; + } + public void setSeekBar(SeekBar seekBar) { if (mSeekBar != null) { mSeekBar.setOnSeekBarChangeListener(null); @@ -135,7 +156,11 @@ public class SeekBarVolumizer implements OnSeekBarChangeListener, Handler.Callba private boolean isZenMuted() { return mNotificationOrRing && mZenMode == Global.ZEN_MODE_ALARMS - || mZenMode == Global.ZEN_MODE_NO_INTERRUPTIONS; + || mZenMode == Global.ZEN_MODE_NO_INTERRUPTIONS + || (mZenMode == Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS + && ((!mAllowAlarms && isAlarmsStream(mStreamType)) + || (!mAllowMediaSystem && isMediaOrSystemStream(mStreamType)) + || (!mAllowRinger && isNotificationOrRing(mStreamType)))); } protected void updateSeekBar() { @@ -396,6 +421,7 @@ public class SeekBarVolumizer implements OnSeekBarChangeListener, Handler.Callba final IntentFilter filter = new IntentFilter(AudioManager.VOLUME_CHANGED_ACTION); filter.addAction(AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION); filter.addAction(NotificationManager.ACTION_INTERRUPTION_FILTER_CHANGED); + filter.addAction(NotificationManager.ACTION_NOTIFICATION_POLICY_CHANGED); filter.addAction(AudioManager.STREAM_DEVICES_CHANGED_ACTION); mContext.registerReceiver(this, filter); } else { @@ -424,6 +450,15 @@ public class SeekBarVolumizer implements OnSeekBarChangeListener, Handler.Callba } else if (NotificationManager.ACTION_INTERRUPTION_FILTER_CHANGED.equals(action)) { mZenMode = mNotificationManager.getZenMode(); updateSlider(); + } else if (NotificationManager.ACTION_NOTIFICATION_POLICY_CHANGED.equals(action)) { + mNotificationPolicy = mNotificationManager.getNotificationPolicy(); + mAllowAlarms = (mNotificationPolicy.priorityCategories & NotificationManager.Policy + .PRIORITY_CATEGORY_ALARMS) != 0; + mAllowMediaSystem = (mNotificationPolicy.priorityCategories + & NotificationManager.Policy.PRIORITY_CATEGORY_MEDIA_SYSTEM_OTHER) != 0; + mAllowRinger = !ZenModeConfig.areAllPriorityOnlyNotificationZenSoundsMuted( + mNotificationPolicy); + updateSlider(); } } diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 2440b489f416..1f0d683192d5 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -10520,6 +10520,18 @@ public final class Settings { public static final String NETWORK_WATCHLIST_ENABLED = "network_watchlist_enabled"; /** + * Flag to keep background restricted profiles running after exiting. If disabled, + * the restricted profile can be put into stopped state as soon as the user leaves it. + * Type: int (0 for false, 1 for true) + * + * Overridden by the system based on device information. If null, the value specified + * by {@code config_keepRestrictedProfilesInBackground} is used. + * + * @hide + */ + public static final String KEEP_PROFILE_IN_BACKGROUND = "keep_profile_in_background"; + + /** * Get the key that retrieves a bluetooth headset's priority. * @hide */ diff --git a/core/java/android/security/keystore/recovery/KeyChainProtectionParams.java b/core/java/android/security/keystore/recovery/KeyChainProtectionParams.java index a43952a81048..aa09f10de070 100644 --- a/core/java/android/security/keystore/recovery/KeyChainProtectionParams.java +++ b/core/java/android/security/keystore/recovery/KeyChainProtectionParams.java @@ -235,17 +235,7 @@ public final class KeyChainProtectionParams implements Parcelable { } /** - * Removes secret from memory than object is no longer used. - * Since finalizer call is not reliable, please use @link {#clearSecret} directly. - */ - @Override - protected void finalize() throws Throwable { - clearSecret(); - super.finalize(); - } - - /** - * Fills mSecret with zeroes. + * Fills secret with zeroes. */ public void clearSecret() { Arrays.fill(mSecret, (byte) 0); diff --git a/core/java/android/service/autofill/AutofillServiceInfo.java b/core/java/android/service/autofill/AutofillServiceInfo.java index 5c7388f79a49..4f2f6cb36d87 100644 --- a/core/java/android/service/autofill/AutofillServiceInfo.java +++ b/core/java/android/service/autofill/AutofillServiceInfo.java @@ -25,16 +25,20 @@ import android.content.pm.ServiceInfo; import android.content.res.Resources; import android.content.res.TypedArray; import android.content.res.XmlResourceParser; +import android.metrics.LogMaker; import android.os.RemoteException; import android.util.AttributeSet; import android.util.Log; import android.util.Xml; import com.android.internal.R; +import com.android.internal.logging.MetricsLogger; +import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; + import java.io.IOException; /** @@ -91,10 +95,20 @@ public final class AutofillServiceInfo { private static TypedArray getMetaDataArray(PackageManager pm, ServiceInfo si) { // Check for permissions. if (!Manifest.permission.BIND_AUTOFILL_SERVICE.equals(si.permission)) { - Log.w(TAG, "AutofillService from '" + si.packageName + "' does not require permission " - + Manifest.permission.BIND_AUTOFILL_SERVICE); - throw new SecurityException("Service does not require permission " - + Manifest.permission.BIND_AUTOFILL_SERVICE); + if (Manifest.permission.BIND_AUTOFILL.equals(si.permission)) { + // Let it go for now... + Log.w(TAG, "AutofillService from '" + si.packageName + "' uses unsupported " + + "permission " + Manifest.permission.BIND_AUTOFILL + ". It works for " + + "now, but might not be supported on future releases"); + new MetricsLogger().write(new LogMaker(MetricsEvent.AUTOFILL_INVALID_PERMISSION) + .setPackageName(si.packageName)); + } else { + Log.w(TAG, "AutofillService from '" + si.packageName + + "' does not require permission " + + Manifest.permission.BIND_AUTOFILL_SERVICE); + throw new SecurityException("Service does not require permission " + + Manifest.permission.BIND_AUTOFILL_SERVICE); + } } // Get the AutoFill metadata, if declared. diff --git a/core/java/android/service/autofill/DateTransformation.java b/core/java/android/service/autofill/DateTransformation.java new file mode 100644 index 000000000000..4e1425d86380 --- /dev/null +++ b/core/java/android/service/autofill/DateTransformation.java @@ -0,0 +1,127 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.service.autofill; + +import static android.view.autofill.Helper.sDebug; + +import android.annotation.NonNull; +import android.annotation.TestApi; +import android.os.Parcel; +import android.os.Parcelable; +import android.util.Log; +import android.view.autofill.AutofillId; +import android.view.autofill.AutofillValue; +import android.widget.RemoteViews; +import android.widget.TextView; + +import com.android.internal.util.Preconditions; + +import java.text.DateFormat; +import java.util.Date; + +/** + * Replaces a {@link TextView} child of a {@link CustomDescription} with the contents of a field + * that is expected to have a {@link AutofillValue#forDate(long) date value}. + * + * <p>For example, a transformation to display a credit card expiration date as month/year would be: + * + * <pre class="prettyprint"> + * new DateTransformation(ccExpDate, new java.text.SimpleDateFormat("MM/yyyy") + * </pre> + */ +public final class DateTransformation extends InternalTransformation implements + Transformation, Parcelable { + private static final String TAG = "DateTransformation"; + + private final AutofillId mFieldId; + private final DateFormat mDateFormat; + + /** + * Creates a new transformation. + * + * @param id id of the screen field. + * @param dateFormat object used to transform the date value of the field to a String. + */ + public DateTransformation(@NonNull AutofillId id, @NonNull DateFormat dateFormat) { + mFieldId = Preconditions.checkNotNull(id); + mDateFormat = Preconditions.checkNotNull(dateFormat); + } + + /** @hide */ + @Override + @TestApi + public void apply(@NonNull ValueFinder finder, @NonNull RemoteViews parentTemplate, + int childViewId) throws Exception { + final AutofillValue value = finder.findRawValueByAutofillId(mFieldId); + if (value == null) { + Log.w(TAG, "No value for id " + mFieldId); + return; + } + if (!value.isDate()) { + Log.w(TAG, "Value for " + mFieldId + " is not date: " + value); + return; + } + + try { + final Date date = new Date(value.getDateValue()); + final String transformed = mDateFormat.format(date); + if (sDebug) Log.d(TAG, "Transformed " + date + " to " + transformed); + + parentTemplate.setCharSequence(childViewId, "setText", transformed); + } catch (Exception e) { + Log.w(TAG, "Could not apply " + mDateFormat + " to " + value + ": " + e); + } + } + + ///////////////////////////////////// + // Object "contract" methods. // + ///////////////////////////////////// + @Override + public String toString() { + if (!sDebug) return super.toString(); + + return "DateTransformation: [id=" + mFieldId + ", format=" + mDateFormat + "]"; + } + + ///////////////////////////////////// + // Parcelable "contract" methods. // + ///////////////////////////////////// + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel parcel, int flags) { + parcel.writeParcelable(mFieldId, flags); + parcel.writeSerializable(mDateFormat); + } + + public static final Parcelable.Creator<DateTransformation> CREATOR = + new Parcelable.Creator<DateTransformation>() { + @Override + public DateTransformation createFromParcel(Parcel parcel) { + return new DateTransformation(parcel.readParcelable(null), + (DateFormat) parcel.readSerializable()); + } + + @Override + public DateTransformation[] newArray(int size) { + return new DateTransformation[size]; + } + }; +} diff --git a/core/java/android/service/autofill/DateValueSanitizer.java b/core/java/android/service/autofill/DateValueSanitizer.java new file mode 100644 index 000000000000..0f7b540f8a24 --- /dev/null +++ b/core/java/android/service/autofill/DateValueSanitizer.java @@ -0,0 +1,123 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.service.autofill; + +import static android.view.autofill.Helper.sDebug; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.TestApi; +import android.os.Parcel; +import android.os.Parcelable; +import android.util.Log; +import android.view.autofill.AutofillValue; + +import com.android.internal.util.Preconditions; + +import java.text.DateFormat; +import java.util.Date; + +/** + * Sanitizes a date {@link AutofillValue} using a {@link DateFormat}. + * + * <p>For example, to sanitize a credit card expiration date to just its month and year: + * + * <pre class="prettyprint"> + * new DateValueSanitizer(new java.text.SimpleDateFormat("MM/yyyy"); + * </pre> + */ +public final class DateValueSanitizer extends InternalSanitizer implements Sanitizer, Parcelable { + + private static final String TAG = "DateValueSanitizer"; + + private final DateFormat mDateFormat; + + /** + * Default constructor. + * + * @param dateFormat date format applied to the actual date value of an input field. + */ + public DateValueSanitizer(@NonNull DateFormat dateFormat) { + mDateFormat = Preconditions.checkNotNull(dateFormat); + } + + /** @hide */ + @Override + @TestApi + @Nullable + public AutofillValue sanitize(@NonNull AutofillValue value) { + if (value == null) { + Log.w(TAG, "sanitize() called with null value"); + return null; + } + if (!value.isDate()) { + if (sDebug) Log.d(TAG, value + " is not a date"); + return null; + } + + try { + final Date date = new Date(value.getDateValue()); + + // First convert it to string + final String converted = mDateFormat.format(date); + if (sDebug) Log.d(TAG, "Transformed " + date + " to " + converted); + // Then parse it back to date + final Date sanitized = mDateFormat.parse(converted); + if (sDebug) Log.d(TAG, "Sanitized to " + sanitized); + return AutofillValue.forDate(sanitized.getTime()); + } catch (Exception e) { + Log.w(TAG, "Could not apply " + mDateFormat + " to " + value + ": " + e); + return null; + } + } + + ///////////////////////////////////// + // Object "contract" methods. // + ///////////////////////////////////// + @Override + public String toString() { + if (!sDebug) return super.toString(); + + return "DateValueSanitizer: [dateFormat=" + mDateFormat + "]"; + } + + ///////////////////////////////////// + // Parcelable "contract" methods. // + ///////////////////////////////////// + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel parcel, int flags) { + parcel.writeSerializable(mDateFormat); + } + + public static final Parcelable.Creator<DateValueSanitizer> CREATOR = + new Parcelable.Creator<DateValueSanitizer>() { + @Override + public DateValueSanitizer createFromParcel(Parcel parcel) { + return new DateValueSanitizer((DateFormat) parcel.readSerializable()); + } + + @Override + public DateValueSanitizer[] newArray(int size) { + return new DateValueSanitizer[size]; + } + }; +} diff --git a/core/java/android/service/autofill/UserData.java b/core/java/android/service/autofill/UserData.java index 9017848055e2..6bab6aa82375 100644 --- a/core/java/android/service/autofill/UserData.java +++ b/core/java/android/service/autofill/UserData.java @@ -30,6 +30,7 @@ import android.os.Parcel; import android.os.Parcelable; import android.provider.Settings; import android.service.autofill.FieldClassification.Match; +import android.text.TextUtils; import android.util.Log; import android.view.autofill.AutofillManager; import android.view.autofill.Helper; @@ -52,12 +53,14 @@ public final class UserData implements Parcelable { private static final int DEFAULT_MIN_VALUE_LENGTH = 5; private static final int DEFAULT_MAX_VALUE_LENGTH = 100; + private final String mId; private final String mAlgorithm; private final Bundle mAlgorithmArgs; private final String[] mRemoteIds; private final String[] mValues; private UserData(Builder builder) { + mId = builder.mId; mAlgorithm = builder.mAlgorithm; mAlgorithmArgs = builder.mAlgorithmArgs; mRemoteIds = new String[builder.mRemoteIds.size()]; @@ -75,6 +78,13 @@ public final class UserData implements Parcelable { return mAlgorithm; } + /** + * Gets the id. + */ + public String getId() { + return mId; + } + /** @hide */ public Bundle getAlgorithmArgs() { return mAlgorithmArgs; @@ -92,6 +102,7 @@ public final class UserData implements Parcelable { /** @hide */ public void dump(String prefix, PrintWriter pw) { + pw.print(prefix); pw.print("id: "); pw.print(mId); pw.print(prefix); pw.print("Algorithm: "); pw.print(mAlgorithm); pw.print(" Args: "); pw.println(mAlgorithmArgs); @@ -121,6 +132,7 @@ public final class UserData implements Parcelable { * A builder for {@link UserData} objects. */ public static final class Builder { + private final String mId; private final ArrayList<String> mRemoteIds; private final ArrayList<String> mValues; private String mAlgorithm; @@ -131,16 +143,28 @@ public final class UserData implements Parcelable { * Creates a new builder for the user data used for <a href="#FieldClassification">field * classification</a>. * + * <p>The user data must contain at least one pair of {@code remoteId} -> {@code value}, and + * more pairs can be added through the {@link #add(String, String)} method. + * + * @param id id used to identify the whole {@link UserData} object. This id is also returned + * by {@link AutofillManager#getUserDataId()}, which can be used to check if the + * {@link UserData} is up-to-date without fetching the whole object (through + * {@link AutofillManager#getUserData()}). + * @param remoteId unique string used to identify a user data value. + * @param value value of the user data. + * * @throws IllegalArgumentException if any of the following occurs: * <ol> + * <li>{@code id} is empty * <li>{@code remoteId} is empty * <li>{@code value} is empty * <li>the length of {@code value} is lower than {@link UserData#getMinValueLength()} * <li>the length of {@code value} is higher than {@link UserData#getMaxValueLength()} * </ol> */ - public Builder(@NonNull String remoteId, @NonNull String value) { - checkValidRemoteId(remoteId); + public Builder(@NonNull String id, @NonNull String remoteId, @NonNull String value) { + mId = checkNotEmpty("id", id); + checkNotEmpty("remoteId", remoteId); checkValidValue(value); final int capacity = getMaxUserDataSize(); mRemoteIds = new ArrayList<>(capacity); @@ -188,7 +212,7 @@ public final class UserData implements Parcelable { */ public Builder add(@NonNull String remoteId, @NonNull String value) { throwIfDestroyed(); - checkValidRemoteId(remoteId); + checkNotEmpty("remoteId", remoteId); checkValidValue(value); Preconditions.checkState(!mRemoteIds.contains(remoteId), @@ -205,9 +229,10 @@ public final class UserData implements Parcelable { return this; } - private void checkValidRemoteId(@Nullable String remoteId) { - Preconditions.checkNotNull(remoteId); - Preconditions.checkArgument(!remoteId.isEmpty(), "remoteId cannot be empty"); + private String checkNotEmpty(@NonNull String name, @Nullable String value) { + Preconditions.checkNotNull(value); + Preconditions.checkArgument(!TextUtils.isEmpty(value), "%s cannot be empty", name); + return value; } private void checkValidValue(@Nullable String value) { @@ -246,7 +271,8 @@ public final class UserData implements Parcelable { public String toString() { if (!sDebug) return super.toString(); - final StringBuilder builder = new StringBuilder("UserData: [algorithm=").append(mAlgorithm); + final StringBuilder builder = new StringBuilder("UserData: [id=").append(mId) + .append(", algorithm=").append(mAlgorithm); // Cannot disclose remote ids or values because they could contain PII builder.append(", remoteIds="); Helper.appendRedacted(builder, mRemoteIds); @@ -266,6 +292,7 @@ public final class UserData implements Parcelable { @Override public void writeToParcel(Parcel parcel, int flags) { + parcel.writeString(mId); parcel.writeStringArray(mRemoteIds); parcel.writeStringArray(mValues); parcel.writeString(mAlgorithm); @@ -279,9 +306,10 @@ public final class UserData implements Parcelable { // Always go through the builder to ensure the data ingested by // the system obeys the contract of the builder to avoid attacks // using specially crafted parcels. + final String id = parcel.readString(); final String[] remoteIds = parcel.readStringArray(); final String[] values = parcel.readStringArray(); - final Builder builder = new Builder(remoteIds[0], values[0]) + final Builder builder = new Builder(id, remoteIds[0], values[0]) .setFieldClassificationAlgorithm(parcel.readString(), parcel.readBundle()); for (int i = 1; i < remoteIds.length; i++) { builder.add(remoteIds[i], values[i]); diff --git a/core/java/android/service/autofill/ValueFinder.java b/core/java/android/service/autofill/ValueFinder.java index 1705b7d922aa..7f195d67c601 100644 --- a/core/java/android/service/autofill/ValueFinder.java +++ b/core/java/android/service/autofill/ValueFinder.java @@ -19,6 +19,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.TestApi; import android.view.autofill.AutofillId; +import android.view.autofill.AutofillValue; /** * Helper object used to obtain the value of a field in the screen being autofilled. @@ -29,7 +30,17 @@ import android.view.autofill.AutofillId; public interface ValueFinder { /** + * Gets the value of a field as String, or {@code null} when not found. + */ + @Nullable + default String findByAutofillId(@NonNull AutofillId id) { + final AutofillValue value = findRawValueByAutofillId(id); + return (value == null || !value.isText()) ? null : value.getTextValue().toString(); + } + + /** * Gets the value of a field, or {@code null} when not found. */ - @Nullable String findByAutofillId(@NonNull AutofillId id); + @Nullable + AutofillValue findRawValueByAutofillId(@NonNull AutofillId id); } diff --git a/core/java/android/service/notification/ZenModeConfig.java b/core/java/android/service/notification/ZenModeConfig.java index bb88e1a20f73..23ae4b96810f 100644 --- a/core/java/android/service/notification/ZenModeConfig.java +++ b/core/java/android/service/notification/ZenModeConfig.java @@ -17,6 +17,7 @@ package android.service.notification; import android.app.ActivityManager; +import android.app.NotificationManager; import android.app.NotificationManager.Policy; import android.content.ComponentName; import android.content.Context; @@ -1410,4 +1411,38 @@ public class ZenModeConfig implements Parcelable { } } + /** + * Determines whether dnd behavior should mute all notification sounds + */ + public static boolean areAllPriorityOnlyNotificationZenSoundsMuted(NotificationManager.Policy + policy) { + boolean allowReminders = (policy.priorityCategories + & NotificationManager.Policy.PRIORITY_CATEGORY_REMINDERS) != 0; + boolean allowCalls = (policy.priorityCategories + & NotificationManager.Policy.PRIORITY_CATEGORY_CALLS) != 0; + boolean allowMessages = (policy.priorityCategories + & NotificationManager.Policy.PRIORITY_CATEGORY_MESSAGES) != 0; + boolean allowEvents = (policy.priorityCategories + & NotificationManager.Policy.PRIORITY_CATEGORY_EVENTS) != 0; + boolean allowRepeatCallers = (policy.priorityCategories + & NotificationManager.Policy.PRIORITY_CATEGORY_REPEAT_CALLERS) != 0; + return !allowReminders && !allowCalls && !allowMessages && !allowEvents + && !allowRepeatCallers; + } + + /** + * Determines whether dnd behavior should mute all notification sounds + */ + public static boolean areAllPriorityOnlyNotificationZenSoundsMuted(ZenModeConfig config) { + return !config.allowReminders && !config.allowCalls && !config.allowMessages + && !config.allowEvents && !config.allowRepeatCallers; + } + + /** + * Determines whether all dnd mutes all sounds + */ + public static boolean areAllZenBehaviorSoundsMuted(ZenModeConfig config) { + return !config.allowAlarms && !config.allowMediaSystemOther + && areAllPriorityOnlyNotificationZenSoundsMuted(config); + } } diff --git a/core/java/android/service/textclassifier/ITextClassificationCallback.aidl b/core/java/android/service/textclassifier/ITextClassificationCallback.aidl new file mode 100644 index 000000000000..10bfe6324509 --- /dev/null +++ b/core/java/android/service/textclassifier/ITextClassificationCallback.aidl @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.service.textclassifier; + +import android.view.textclassifier.TextClassification; + +/** + * Callback for a TextClassification request. + * @hide + */ +oneway interface ITextClassificationCallback { + void onSuccess(in TextClassification classification); + void onFailure(); +} diff --git a/core/java/android/service/textclassifier/ITextClassifierService.aidl b/core/java/android/service/textclassifier/ITextClassifierService.aidl new file mode 100644 index 000000000000..d2ffe345ae38 --- /dev/null +++ b/core/java/android/service/textclassifier/ITextClassifierService.aidl @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.service.textclassifier; + +import android.service.textclassifier.ITextClassificationCallback; +import android.service.textclassifier.ITextLinksCallback; +import android.service.textclassifier.ITextSelectionCallback; +import android.view.textclassifier.TextClassification; +import android.view.textclassifier.TextLinks; +import android.view.textclassifier.TextSelection; + +/** + * TextClassifierService binder interface. + * See TextClassifier for interface documentation. + * {@hide} + */ +oneway interface ITextClassifierService { + + void onSuggestSelection( + in CharSequence text, int selectionStartIndex, int selectionEndIndex, + in TextSelection.Options options, + in ITextSelectionCallback c); + + void onClassifyText( + in CharSequence text, int startIndex, int endIndex, + in TextClassification.Options options, + in ITextClassificationCallback c); + + void onGenerateLinks( + in CharSequence text, + in TextLinks.Options options, + in ITextLinksCallback c); +} diff --git a/core/java/android/service/textclassifier/ITextLinksCallback.aidl b/core/java/android/service/textclassifier/ITextLinksCallback.aidl new file mode 100644 index 000000000000..a9e0dde56773 --- /dev/null +++ b/core/java/android/service/textclassifier/ITextLinksCallback.aidl @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.service.textclassifier; + +import android.view.textclassifier.TextLinks; + +/** + * Callback for a TextLinks request. + * @hide + */ +oneway interface ITextLinksCallback { + void onSuccess(in TextLinks links); + void onFailure(); +}
\ No newline at end of file diff --git a/core/java/android/service/textclassifier/ITextSelectionCallback.aidl b/core/java/android/service/textclassifier/ITextSelectionCallback.aidl new file mode 100644 index 000000000000..1b4c4d10d427 --- /dev/null +++ b/core/java/android/service/textclassifier/ITextSelectionCallback.aidl @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.service.textclassifier; + +import android.view.textclassifier.TextSelection; + +/** + * Callback for a TextSelection request. + * @hide + */ +oneway interface ITextSelectionCallback { + void onSuccess(in TextSelection selection); + void onFailure(); +}
\ No newline at end of file diff --git a/core/java/android/service/textclassifier/TextClassifierService.java b/core/java/android/service/textclassifier/TextClassifierService.java new file mode 100644 index 000000000000..6c8c8bc36127 --- /dev/null +++ b/core/java/android/service/textclassifier/TextClassifierService.java @@ -0,0 +1,290 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.service.textclassifier; + +import android.Manifest; +import android.annotation.IntRange; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SystemApi; +import android.app.Service; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.content.pm.ServiceInfo; +import android.os.CancellationSignal; +import android.os.IBinder; +import android.os.RemoteException; +import android.text.TextUtils; +import android.util.Slog; +import android.view.textclassifier.TextClassification; +import android.view.textclassifier.TextClassifier; +import android.view.textclassifier.TextLinks; +import android.view.textclassifier.TextSelection; + +import com.android.internal.R; + +/** + * Abstract base class for the TextClassifier service. + * + * <p>A TextClassifier service provides text classification related features for the system. + * The system's default TextClassifierService is configured in + * {@code config_defaultTextClassifierService}. If this config has no value, a + * {@link android.view.textclassifier.TextClassifierImpl} is loaded in the calling app's process. + * + * <p>See: {@link TextClassifier}. + * See: {@link android.view.textclassifier.TextClassificationManager}. + * + * <p>Include the following in the manifest: + * + * <pre> + * {@literal + * <service android:name=".YourTextClassifierService" + * android:permission="android.permission.BIND_TEXTCLASSIFIER_SERVICE"> + * <intent-filter> + * <action android:name="android.service.textclassifier.TextClassifierService" /> + * </intent-filter> + * </service>}</pre> + * + * @see TextClassifier + * @hide + */ +@SystemApi +public abstract class TextClassifierService extends Service { + + private static final String LOG_TAG = "TextClassifierService"; + + /** + * The {@link Intent} that must be declared as handled by the service. + * To be supported, the service must also require the + * {@link android.Manifest.permission#BIND_TEXTCLASSIFIER_SERVICE} permission so + * that other applications can not abuse it. + */ + @SystemApi + public static final String SERVICE_INTERFACE = + "android.service.textclassifier.TextClassifierService"; + + private final ITextClassifierService.Stub mBinder = new ITextClassifierService.Stub() { + + // TODO(b/72533911): Implement cancellation signal + @NonNull private final CancellationSignal mCancellationSignal = new CancellationSignal(); + + /** {@inheritDoc} */ + @Override + public void onSuggestSelection( + CharSequence text, int selectionStartIndex, int selectionEndIndex, + TextSelection.Options options, ITextSelectionCallback callback) + throws RemoteException { + TextClassifierService.this.onSuggestSelection( + text, selectionStartIndex, selectionEndIndex, options, mCancellationSignal, + new Callback<TextSelection>() { + @Override + public void onSuccess(TextSelection result) { + try { + callback.onSuccess(result); + } catch (RemoteException e) { + Slog.d(LOG_TAG, "Error calling callback"); + } + } + + @Override + public void onFailure(CharSequence error) { + try { + if (callback.asBinder().isBinderAlive()) { + callback.onFailure(); + } + } catch (RemoteException e) { + Slog.d(LOG_TAG, "Error calling callback"); + } + } + }); + } + + /** {@inheritDoc} */ + @Override + public void onClassifyText( + CharSequence text, int startIndex, int endIndex, + TextClassification.Options options, ITextClassificationCallback callback) + throws RemoteException { + TextClassifierService.this.onClassifyText( + text, startIndex, endIndex, options, mCancellationSignal, + new Callback<TextClassification>() { + @Override + public void onSuccess(TextClassification result) { + try { + callback.onSuccess(result); + } catch (RemoteException e) { + Slog.d(LOG_TAG, "Error calling callback"); + } + } + + @Override + public void onFailure(CharSequence error) { + try { + callback.onFailure(); + } catch (RemoteException e) { + Slog.d(LOG_TAG, "Error calling callback"); + } + } + }); + } + + /** {@inheritDoc} */ + @Override + public void onGenerateLinks( + CharSequence text, TextLinks.Options options, ITextLinksCallback callback) + throws RemoteException { + TextClassifierService.this.onGenerateLinks( + text, options, mCancellationSignal, + new Callback<TextLinks>() { + @Override + public void onSuccess(TextLinks result) { + try { + callback.onSuccess(result); + } catch (RemoteException e) { + Slog.d(LOG_TAG, "Error calling callback"); + } + } + + @Override + public void onFailure(CharSequence error) { + try { + callback.onFailure(); + } catch (RemoteException e) { + Slog.d(LOG_TAG, "Error calling callback"); + } + } + }); + } + }; + + @Nullable + @Override + public final IBinder onBind(Intent intent) { + if (SERVICE_INTERFACE.equals(intent.getAction())) { + return mBinder; + } + return null; + } + + /** + * Returns suggested text selection start and end indices, recognized entity types, and their + * associated confidence scores. The entity types are ordered from highest to lowest scoring. + * + * @param text text providing context for the selected text (which is specified + * by the sub sequence starting at selectionStartIndex and ending at selectionEndIndex) + * @param selectionStartIndex start index of the selected part of text + * @param selectionEndIndex end index of the selected part of text + * @param options optional input parameters + * @param cancellationSignal object to watch for canceling the current operation + * @param callback the callback to return the result to + */ + public abstract void onSuggestSelection( + @NonNull CharSequence text, + @IntRange(from = 0) int selectionStartIndex, + @IntRange(from = 0) int selectionEndIndex, + @Nullable TextSelection.Options options, + @NonNull CancellationSignal cancellationSignal, + @NonNull Callback<TextSelection> callback); + + /** + * Classifies the specified text and returns a {@link TextClassification} object that can be + * used to generate a widget for handling the classified text. + * + * @param text text providing context for the text to classify (which is specified + * by the sub sequence starting at startIndex and ending at endIndex) + * @param startIndex start index of the text to classify + * @param endIndex end index of the text to classify + * @param options optional input parameters + * @param cancellationSignal object to watch for canceling the current operation + * @param callback the callback to return the result to + */ + public abstract void onClassifyText( + @NonNull CharSequence text, + @IntRange(from = 0) int startIndex, + @IntRange(from = 0) int endIndex, + @Nullable TextClassification.Options options, + @NonNull CancellationSignal cancellationSignal, + @NonNull Callback<TextClassification> callback); + + /** + * Generates and returns a {@link TextLinks} that may be applied to the text to annotate it with + * links information. + * + * @param text the text to generate annotations for + * @param options configuration for link generation + * @param cancellationSignal object to watch for canceling the current operation + * @param callback the callback to return the result to + */ + public abstract void onGenerateLinks( + @NonNull CharSequence text, + @Nullable TextLinks.Options options, + @NonNull CancellationSignal cancellationSignal, + @NonNull Callback<TextLinks> callback); + + /** + * Callbacks for TextClassifierService results. + * + * @param <T> the type of the result + * @hide + */ + @SystemApi + public interface Callback<T> { + /** + * Returns the result. + */ + void onSuccess(T result); + + /** + * Signals a failure. + */ + void onFailure(CharSequence error); + } + + /** + * Returns the component name of the system default textclassifier service if it can be found + * on the system. Otherwise, returns null. + * @hide + */ + @Nullable + public static ComponentName getServiceComponentName(Context context) { + final String str = context.getString(R.string.config_defaultTextClassifierService); + if (!TextUtils.isEmpty(str)) { + try { + final ComponentName componentName = ComponentName.unflattenFromString(str); + final Intent intent = new Intent(SERVICE_INTERFACE).setComponent(componentName); + final ServiceInfo si = context.getPackageManager() + .getServiceInfo(intent.getComponent(), 0); + final String permission = si == null ? null : si.permission; + if (Manifest.permission.BIND_TEXTCLASSIFIER_SERVICE.equals(permission)) { + return componentName; + } + Slog.w(LOG_TAG, String.format( + "Service %s should require %s permission. Found %s permission", + intent.getComponent().flattenToString(), + Manifest.permission.BIND_TEXTCLASSIFIER_SERVICE, + si.permission)); + } catch (PackageManager.NameNotFoundException e) { + Slog.w(LOG_TAG, String.format("Service %s not found", str)); + } + } else { + Slog.d(LOG_TAG, "No configured system TextClassifierService"); + } + return null; + } +} diff --git a/core/java/android/text/MeasuredParagraph.java b/core/java/android/text/MeasuredParagraph.java index 45fbf6f58209..aafcf44a73fc 100644 --- a/core/java/android/text/MeasuredParagraph.java +++ b/core/java/android/text/MeasuredParagraph.java @@ -672,6 +672,13 @@ public class MeasuredParagraph { return width; } + /** + * This only works if the MeasuredParagraph is computed with buildForStaticLayout. + */ + @IntRange(from = 0) int getMemoryUsage() { + return nGetMemoryUsage(mNativePtr); + } + private static native /* Non Zero */ long nInitBuilder(); /** @@ -718,4 +725,7 @@ public class MeasuredParagraph { @CriticalNative private static native /* Non Zero */ long nGetReleaseFunc(); + + @CriticalNative + private static native int nGetMemoryUsage(/* Non Zero */ long nativePtr); } diff --git a/core/java/android/text/MeasuredText.java b/core/java/android/text/MeasuredText.java index ff23395d92a5..bb7a9e0b7906 100644 --- a/core/java/android/text/MeasuredText.java +++ b/core/java/android/text/MeasuredText.java @@ -333,6 +333,20 @@ public class MeasuredText implements Spanned { 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 // diff --git a/core/java/android/text/format/DateFormat.java b/core/java/android/text/format/DateFormat.java index de2dcce5bc5d..413cd1035618 100755 --- a/core/java/android/text/format/DateFormat.java +++ b/core/java/android/text/format/DateFormat.java @@ -431,7 +431,7 @@ public class DateFormat { int c = s.charAt(i); if (c == QUOTE) { - count = appendQuotedText(s, i, len); + count = appendQuotedText(s, i); len = s.length(); continue; } @@ -574,36 +574,48 @@ public class DateFormat { : String.format(Locale.getDefault(), "%d", year); } - private static int appendQuotedText(SpannableStringBuilder s, int i, int len) { - if (i + 1 < len && s.charAt(i + 1) == QUOTE) { - s.delete(i, i + 1); + + /** + * Strips quotation marks from the {@code formatString} and appends the result back to the + * {@code formatString}. + * + * @param formatString the format string, as described in + * {@link android.text.format.DateFormat}, to be modified + * @param index index of the first quote + * @return the length of the quoted text that was appended. + * @hide + */ + public static int appendQuotedText(SpannableStringBuilder formatString, int index) { + int length = formatString.length(); + if (index + 1 < length && formatString.charAt(index + 1) == QUOTE) { + formatString.delete(index, index + 1); return 1; } int count = 0; // delete leading quote - s.delete(i, i + 1); - len--; + formatString.delete(index, index + 1); + length--; - while (i < len) { - char c = s.charAt(i); + while (index < length) { + char c = formatString.charAt(index); if (c == QUOTE) { // QUOTEQUOTE -> QUOTE - if (i + 1 < len && s.charAt(i + 1) == QUOTE) { + if (index + 1 < length && formatString.charAt(index + 1) == QUOTE) { - s.delete(i, i + 1); - len--; + formatString.delete(index, index + 1); + length--; count++; - i++; + index++; } else { // Closing QUOTE ends quoted text copying - s.delete(i, i + 1); + formatString.delete(index, index + 1); break; } } else { - i++; + index++; count++; } } diff --git a/core/java/android/text/util/Linkify.java b/core/java/android/text/util/Linkify.java index 768aee91e5b3..d973d4ac076c 100644 --- a/core/java/android/text/util/Linkify.java +++ b/core/java/android/text/util/Linkify.java @@ -19,6 +19,7 @@ package android.text.util; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.UiThread; import android.content.Context; import android.telephony.PhoneNumberUtils; import android.telephony.TelephonyManager; @@ -29,12 +30,16 @@ import android.text.method.LinkMovementMethod; import android.text.method.MovementMethod; import android.text.style.URLSpan; import android.util.Patterns; +import android.view.textclassifier.TextClassifier; +import android.view.textclassifier.TextLinks; +import android.view.textclassifier.TextLinks.TextLinkSpan; import android.webkit.WebView; import android.widget.TextView; import com.android.i18n.phonenumbers.PhoneNumberMatch; import com.android.i18n.phonenumbers.PhoneNumberUtil; import com.android.i18n.phonenumbers.PhoneNumberUtil.Leniency; +import com.android.internal.util.Preconditions; import libcore.util.EmptyArray; @@ -46,6 +51,12 @@ import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.Locale; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Executor; +import java.util.concurrent.Future; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Supplier; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -479,6 +490,195 @@ public class Linkify { return hasMatches; } + /** + * Scans the text of the provided TextView and turns all occurrences of the entity types + * specified by {@code options} into clickable links. If links are found, this method + * removes any pre-existing {@link TextLinkSpan} attached to the text (to avoid + * problems if you call it repeatedly on the same text) and sets the movement method for the + * TextView to LinkMovementMethod. + * + * <p><strong>Note:</strong> This method returns immediately but generates the links with + * the specified classifier on a background thread. The generated links are applied on the + * calling thread. + * + * @param textView TextView whose text is to be marked-up with links + * @param options optional parameters to specify how to generate the links + * + * @return a future that may be used to interrupt or query the background task + */ + @UiThread + public static Future<Void> addLinksAsync( + @NonNull TextView textView, + @Nullable TextLinks.Options options) { + return addLinksAsync(textView, options, null /* executor */, null /* callback */); + } + + /** + * Scans the text of the provided TextView and turns all occurrences of the entity types + * specified by {@code options} into clickable links. If links are found, this method + * removes any pre-existing {@link TextLinkSpan} attached to the text (to avoid + * problems if you call it repeatedly on the same text) and sets the movement method for the + * TextView to LinkMovementMethod. + * + * <p><strong>Note:</strong> This method returns immediately but generates the links with + * the specified classifier on a background thread. The generated links are applied on the + * calling thread. + * + * @param textView TextView whose text is to be marked-up with links + * @param options optional parameters to specify how to generate the links + * @param executor Executor that runs the background task + * @param callback Callback that receives the final status of the background task execution + * + * @return a future that may be used to interrupt or query the background task + */ + @UiThread + public static Future<Void> addLinksAsync( + @NonNull TextView textView, + @Nullable TextLinks.Options options, + @Nullable Executor executor, + @Nullable Consumer<Integer> callback) { + Preconditions.checkNotNull(textView); + final CharSequence text = textView.getText(); + final Spannable spannable = (text instanceof Spannable) + ? (Spannable) text : SpannableString.valueOf(text); + final Runnable modifyTextView = () -> { + addLinkMovementMethod(textView); + if (spannable != text) { + textView.setText(spannable); + } + }; + return addLinksAsync(spannable, textView.getTextClassifier(), + options, executor, callback, modifyTextView); + } + + /** + * Scans the text of the provided TextView and turns all occurrences of the entity types + * specified by {@code options} into clickable links. If links are found, this method + * removes any pre-existing {@link TextLinkSpan} attached to the text to avoid + * problems if you call it repeatedly on the same text. + * + * <p><strong>Note:</strong> This method returns immediately but generates the links with + * the specified classifier on a background thread. The generated links are applied on the + * calling thread. + * + * <p><strong>Note:</strong> If the text is currently attached to a TextView, this method + * should be called on the UI thread. + * + * @param text Spannable whose text is to be marked-up with links + * @param classifier the TextClassifier to use to generate the links + * @param options optional parameters to specify how to generate the links + * + * @return a future that may be used to interrupt or query the background task + */ + public static Future<Void> addLinksAsync( + @NonNull Spannable text, + @NonNull TextClassifier classifier, + @Nullable TextLinks.Options options) { + return addLinksAsync(text, classifier, options, null /* executor */, null /* callback */); + } + + /** + * Scans the text of the provided TextView and turns all occurrences of the entity types + * specified by the link {@code mask} into clickable links. If links are found, this method + * removes any pre-existing {@link TextLinkSpan} attached to the text to avoid + * problems if you call it repeatedly on the same text. + * + * <p><strong>Note:</strong> This method returns immediately but generates the links with + * the specified classifier on a background thread. The generated links are applied on the + * calling thread. + * + * <p><strong>Note:</strong> If the text is currently attached to a TextView, this method + * should be called on the UI thread. + * + * @param text Spannable whose text is to be marked-up with links + * @param classifier the TextClassifier to use to generate the links + * @param mask mask to define which kinds of links will be generated + * + * @return a future that may be used to interrupt or query the background task + */ + public static Future<Void> addLinksAsync( + @NonNull Spannable text, + @NonNull TextClassifier classifier, + @LinkifyMask int mask) { + return addLinksAsync(text, classifier, TextLinks.Options.fromLinkMask(mask), + null /* executor */, null /* callback */); + } + + /** + * Scans the text of the provided TextView and turns all occurrences of the entity types + * specified by {@code options} into clickable links. If links are found, this method + * removes any pre-existing {@link TextLinkSpan} attached to the text to avoid + * problems if you call it repeatedly on the same text. + * + * <p><strong>Note:</strong> This method returns immediately but generates the links with + * the specified classifier on a background thread. The generated links are applied on the + * calling thread. + * + * <p><strong>Note:</strong> If the text is currently attached to a TextView, this method + * should be called on the UI thread. + * + * @param text Spannable whose text is to be marked-up with links + * @param classifier the TextClassifier to use to generate the links + * @param options optional parameters to specify how to generate the links + * @param executor Executor that runs the background task + * @param callback Callback that receives the final status of the background task execution + * + * @return a future that may be used to interrupt or query the background task + */ + public static Future<Void> addLinksAsync( + @NonNull Spannable text, + @NonNull TextClassifier classifier, + @Nullable TextLinks.Options options, + @Nullable Executor executor, + @Nullable Consumer<Integer> callback) { + return addLinksAsync(text, classifier, options, executor, callback, + null /* modifyTextView */); + } + + private static Future<Void> addLinksAsync( + @NonNull Spannable text, + @NonNull TextClassifier classifier, + @Nullable TextLinks.Options options, + @Nullable Executor executor, + @Nullable Consumer<Integer> callback, + @Nullable Runnable modifyTextView) { + Preconditions.checkNotNull(text); + Preconditions.checkNotNull(classifier); + final Supplier<TextLinks> supplier = () -> classifier.generateLinks(text, options); + final Consumer<TextLinks> consumer = links -> { + if (links.getLinks().isEmpty()) { + if (callback != null) { + callback.accept(TextLinks.STATUS_NO_LINKS_FOUND); + } + return; + } + + final TextLinkSpan[] old = text.getSpans(0, text.length(), TextLinkSpan.class); + for (int i = old.length - 1; i >= 0; i--) { + text.removeSpan(old[i]); + } + + final Function<TextLinks.TextLink, TextLinkSpan> spanFactory = (options == null) + ? null : options.getSpanFactory(); + final @TextLinks.ApplyStrategy int applyStrategy = (options == null) + ? TextLinks.APPLY_STRATEGY_IGNORE : options.getApplyStrategy(); + final @TextLinks.Status int result = links.apply(text, applyStrategy, spanFactory); + if (result == TextLinks.STATUS_LINKS_APPLIED) { + if (modifyTextView != null) { + modifyTextView.run(); + } + } + if (callback != null) { + callback.accept(result); + } + }; + if (executor == null) { + return CompletableFuture.supplyAsync(supplier).thenAccept(consumer); + } else { + return CompletableFuture.supplyAsync(supplier, executor).thenAccept(consumer); + } + } + private static final void applyLink(String url, int start, int end, Spannable text) { URLSpan span = new URLSpan(url); diff --git a/core/java/android/util/AtomicFile.java b/core/java/android/util/AtomicFile.java index 6342c8bcb85e..cf7ed9b0566d 100644 --- a/core/java/android/util/AtomicFile.java +++ b/core/java/android/util/AtomicFile.java @@ -17,6 +17,7 @@ package android.util; import android.os.FileUtils; +import android.os.SystemClock; import libcore.io.IoUtils; @@ -47,14 +48,25 @@ import java.util.function.Consumer; public class AtomicFile { private final File mBaseName; private final File mBackupName; + private final String mCommitTag; + private long mStartTime; /** * Create a new AtomicFile for a file located at the given File path. * The secondary backup file will be the same file path with ".bak" appended. */ public AtomicFile(File baseName) { + this(baseName, null); + } + + /** + * @hide Internal constructor that also allows you to have the class + * automatically log commit events. + */ + public AtomicFile(File baseName, String commitTag) { mBaseName = baseName; mBackupName = new File(baseName.getPath() + ".bak"); + mCommitTag = commitTag; } /** @@ -88,6 +100,18 @@ public class AtomicFile { * access to AtomicFile. */ public FileOutputStream startWrite() throws IOException { + return startWrite(mCommitTag != null ? SystemClock.uptimeMillis() : 0); + } + + /** + * @hide Internal version of {@link #startWrite()} that allows you to specify an earlier + * start time of the operation to adjust how the commit is logged. + * @param startTime The effective start time of the operation, in the time + * base of {@link SystemClock#uptimeMillis()}. + */ + public FileOutputStream startWrite(long startTime) throws IOException { + mStartTime = startTime; + // Rename the current file so it may be used as a backup during the next read if (mBaseName.exists()) { if (!mBackupName.exists()) { @@ -135,6 +159,10 @@ public class AtomicFile { } catch (IOException e) { Log.w("AtomicFile", "finishWrite: Got exception:", e); } + if (mCommitTag != null) { + com.android.internal.logging.EventLogTags.writeCommitSysConfigFile( + mCommitTag, SystemClock.uptimeMillis() - mStartTime); + } } } diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java index 25a177edd27c..57d23ced7860 100644 --- a/core/java/android/util/FeatureFlagUtils.java +++ b/core/java/android/util/FeatureFlagUtils.java @@ -38,11 +38,9 @@ public class FeatureFlagUtils { static { DEFAULT_FLAGS = new HashMap<>(); DEFAULT_FLAGS.put("device_info_v2", "true"); - DEFAULT_FLAGS.put("settings_app_info_v2", "true"); DEFAULT_FLAGS.put("settings_connected_device_v2", "true"); DEFAULT_FLAGS.put("settings_battery_v2", "true"); DEFAULT_FLAGS.put("settings_battery_display_app_list", "false"); - DEFAULT_FLAGS.put("settings_security_settings_v2", "true"); DEFAULT_FLAGS.put("settings_zone_picker_v2", "true"); DEFAULT_FLAGS.put("settings_suggestion_ui_v2", "false"); DEFAULT_FLAGS.put("settings_about_phone_v2", "false"); diff --git a/core/java/android/util/apk/ApkSignatureSchemeV3Verifier.java b/core/java/android/util/apk/ApkSignatureSchemeV3Verifier.java index a4c590f1b459..9436b296e9e2 100644 --- a/core/java/android/util/apk/ApkSignatureSchemeV3Verifier.java +++ b/core/java/android/util/apk/ApkSignatureSchemeV3Verifier.java @@ -438,8 +438,8 @@ public class ApkSignatureSchemeV3Verifier { List<Integer> flagsList = new ArrayList<>(); // Proof-of-rotation struct: - // is basically a singly linked list of nodes, called levels here, each of which have the - // following structure: + // A uint32 version code followed by basically a singly linked list of nodes, called levels + // here, each of which have the following structure: // * length-prefix for the entire level // - length-prefixed signed data (if previous level exists) // * length-prefixed X509 Certificate @@ -450,9 +450,13 @@ public class ApkSignatureSchemeV3Verifier { // - length-prefixed signature over the signed data in this level. The signature here // is verified using the certificate from the previous level. // The linking is provided by the certificate of each level signing the one of the next. - while (porBuf.hasRemaining()) { - levelCount++; - try { + + try { + + // get the version code, but don't do anything with it: creator knew about all our flags + porBuf.getInt(); + while (porBuf.hasRemaining()) { + levelCount++; ByteBuffer level = getLengthPrefixedSlice(porBuf); ByteBuffer signedData = getLengthPrefixedSlice(level); int flags = level.getInt(); @@ -491,17 +495,17 @@ public class ApkSignatureSchemeV3Verifier { lastSigAlgorithm = sigAlgorithm; certs.add(lastCert); flagsList.add(flags); - } catch (IOException | BufferUnderflowException e) { - throw new IOException("Failed to parse Proof-of-rotation record", e); - } catch (NoSuchAlgorithmException | InvalidKeyException - | InvalidAlgorithmParameterException | SignatureException e) { - throw new SecurityException( - "Failed to verify signature over signed data for certificate #" - + levelCount + " when verifying Proof-of-rotation record", e); - } catch (CertificateException e) { - throw new SecurityException("Failed to decode certificate #" + levelCount - + " when verifying Proof-of-rotation record", e); } + } catch (IOException | BufferUnderflowException e) { + throw new IOException("Failed to parse Proof-of-rotation record", e); + } catch (NoSuchAlgorithmException | InvalidKeyException + | InvalidAlgorithmParameterException | SignatureException e) { + throw new SecurityException( + "Failed to verify signature over signed data for certificate #" + + levelCount + " when verifying Proof-of-rotation record", e); + } catch (CertificateException e) { + throw new SecurityException("Failed to decode certificate #" + levelCount + + " when verifying Proof-of-rotation record", e); } return new VerifiedProofOfRotation(certs, flagsList); } diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 29246fd34748..7bd197e824db 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -896,6 +896,26 @@ public final class ViewRootImpl implements ViewParent, return mWindowAttributes.getTitle(); } + /** + * @return the width of the root view. Note that this will return {@code -1} until the first + * layout traversal, when the width is set. + * + * @hide + */ + public int getWidth() { + return mWidth; + } + + /** + * @return the height of the root view. Note that this will return {@code -1} until the first + * layout traversal, when the height is set. + * + * @hide + */ + public int getHeight() { + return mHeight; + } + void destroyHardwareResources() { if (mAttachInfo.mThreadedRenderer != null) { mAttachInfo.mThreadedRenderer.destroyHardwareResources(mView); @@ -1975,6 +1995,7 @@ public final class ViewRootImpl implements ViewParent, final boolean outsetsChanged = !mPendingOutsets.equals(mAttachInfo.mOutsets); final boolean surfaceSizeChanged = (relayoutResult & WindowManagerGlobal.RELAYOUT_RES_SURFACE_RESIZED) != 0; + surfaceChanged |= surfaceSizeChanged; final boolean alwaysConsumeNavBarChanged = mPendingAlwaysConsumeNavBar != mAttachInfo.mAlwaysConsumeNavBar; if (contentInsetsChanged) { diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java index 4b24a71c8bfb..dac19987f9f2 100644 --- a/core/java/android/view/autofill/AutofillManager.java +++ b/core/java/android/view/autofill/AutofillManager.java @@ -1099,6 +1099,30 @@ public final class AutofillManager { } /** + * Gets the id of the {@link UserData} used for + * <a href="AutofillService.html#FieldClassification">field classification</a>. + * + * <p>This method is useful when the service must check the status of the {@link UserData} in + * the device without fetching the whole object. + * + * <p><b>Note:</b> This method should only be called by an app providing an autofill service, + * and it's ignored if the caller currently doesn't have an enabled autofill service for + * the user. + * + * @return id of the {@link UserData} previously set by {@link #setUserData(UserData)} + * or {@code null} if it was reset or if the caller currently does not have an enabled autofill + * service for the user. + */ + @Nullable public String getUserDataId() { + try { + return mService.getUserDataId(); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + return null; + } + } + + /** * Gets the user data used for * <a href="AutofillService.html#FieldClassification">field classification</a>. * @@ -1119,7 +1143,7 @@ public final class AutofillManager { } /** - * Sets the user data used for + * Sets the {@link UserData} used for * <a href="AutofillService.html#FieldClassification">field classification</a> * * <p><b>Note:</b> This method should only be called by an app providing an autofill service, diff --git a/core/java/android/view/autofill/IAutoFillManager.aidl b/core/java/android/view/autofill/IAutoFillManager.aidl index 1a11fbba0011..001854716715 100644 --- a/core/java/android/view/autofill/IAutoFillManager.aidl +++ b/core/java/android/view/autofill/IAutoFillManager.aidl @@ -56,6 +56,7 @@ interface IAutoFillManager { boolean isServiceEnabled(int userId, String packageName); void onPendingSaveUi(int operation, IBinder token); UserData getUserData(); + String getUserDataId(); void setUserData(in UserData userData); boolean isFieldClassificationEnabled(); ComponentName getAutofillServiceComponentName(); diff --git a/core/java/android/view/textclassifier/SystemTextClassifier.java b/core/java/android/view/textclassifier/SystemTextClassifier.java new file mode 100644 index 000000000000..af55dcd0ed72 --- /dev/null +++ b/core/java/android/view/textclassifier/SystemTextClassifier.java @@ -0,0 +1,197 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.view.textclassifier; + +import android.annotation.IntRange; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.WorkerThread; +import android.content.Context; +import android.os.Looper; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.service.textclassifier.ITextClassificationCallback; +import android.service.textclassifier.ITextClassifierService; +import android.service.textclassifier.ITextLinksCallback; +import android.service.textclassifier.ITextSelectionCallback; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +/** + * Proxy to the system's default TextClassifier. + */ +final class SystemTextClassifier implements TextClassifier { + + private static final String LOG_TAG = "SystemTextClassifier"; + + private final ITextClassifierService mManagerService; + private final TextClassifier mFallback; + + SystemTextClassifier(Context context) throws ServiceManager.ServiceNotFoundException { + mManagerService = ITextClassifierService.Stub.asInterface( + ServiceManager.getServiceOrThrow(Context.TEXT_CLASSIFICATION_SERVICE)); + mFallback = new TextClassifierImpl(context); + } + + /** + * @inheritDoc + */ + @WorkerThread + public TextSelection suggestSelection( + @NonNull CharSequence text, + @IntRange(from = 0) int selectionStartIndex, + @IntRange(from = 0) int selectionEndIndex, + @Nullable TextSelection.Options options) { + Utils.validate(text, selectionStartIndex, selectionEndIndex, false /* allowInMainThread */); + try { + final TextSelectionCallback callback = new TextSelectionCallback(); + mManagerService.onSuggestSelection( + text, selectionStartIndex, selectionEndIndex, options, callback); + final TextSelection selection = callback.mReceiver.get(); + if (selection != null) { + return selection; + } + } catch (RemoteException e) { + e.rethrowAsRuntimeException(); + } catch (InterruptedException e) { + Log.d(LOG_TAG, e.getMessage()); + } + return mFallback.suggestSelection(text, selectionStartIndex, selectionEndIndex, options); + } + + /** + * @inheritDoc + */ + @WorkerThread + public TextClassification classifyText( + @NonNull CharSequence text, + @IntRange(from = 0) int startIndex, + @IntRange(from = 0) int endIndex, + @Nullable TextClassification.Options options) { + Utils.validate(text, startIndex, endIndex, false /* allowInMainThread */); + try { + final TextClassificationCallback callback = new TextClassificationCallback(); + mManagerService.onClassifyText(text, startIndex, endIndex, options, callback); + final TextClassification classification = callback.mReceiver.get(); + if (classification != null) { + return classification; + } + } catch (RemoteException e) { + e.rethrowAsRuntimeException(); + } catch (InterruptedException e) { + Log.d(LOG_TAG, e.getMessage()); + } + return mFallback.classifyText(text, startIndex, endIndex, options); + } + + /** + * @inheritDoc + */ + @WorkerThread + public TextLinks generateLinks( + @NonNull CharSequence text, @Nullable TextLinks.Options options) { + Utils.validate(text, false /* allowInMainThread */); + try { + final TextLinksCallback callback = new TextLinksCallback(); + mManagerService.onGenerateLinks(text, options, callback); + final TextLinks links = callback.mReceiver.get(); + if (links != null) { + return links; + } + } catch (RemoteException e) { + e.rethrowAsRuntimeException(); + } catch (InterruptedException e) { + Log.d(LOG_TAG, e.getMessage()); + } + return mFallback.generateLinks(text, options); + } + + private static final class TextSelectionCallback extends ITextSelectionCallback.Stub { + + final ResponseReceiver<TextSelection> mReceiver = new ResponseReceiver<>(); + + @Override + public void onSuccess(TextSelection selection) { + mReceiver.onSuccess(selection); + } + + @Override + public void onFailure() { + mReceiver.onFailure(); + } + } + + private static final class TextClassificationCallback extends ITextClassificationCallback.Stub { + + final ResponseReceiver<TextClassification> mReceiver = new ResponseReceiver<>(); + + @Override + public void onSuccess(TextClassification classification) { + mReceiver.onSuccess(classification); + } + + @Override + public void onFailure() { + mReceiver.onFailure(); + } + } + + private static final class TextLinksCallback extends ITextLinksCallback.Stub { + + final ResponseReceiver<TextLinks> mReceiver = new ResponseReceiver<>(); + + @Override + public void onSuccess(TextLinks links) { + mReceiver.onSuccess(links); + } + + @Override + public void onFailure() { + mReceiver.onFailure(); + } + } + + private static final class ResponseReceiver<T> { + + private final CountDownLatch mLatch = new CountDownLatch(1); + + private T mResponse; + + public void onSuccess(T response) { + mResponse = response; + mLatch.countDown(); + } + + public void onFailure() { + Log.e(LOG_TAG, "Request failed.", null); + mLatch.countDown(); + } + + @Nullable + public T get() throws InterruptedException { + // If this is running on the main thread, do not block for a response. + // The response will unfortunately be null and the TextClassifier should depend on its + // fallback. + // NOTE that TextClassifier calls should preferably always be called on a worker thread. + if (Looper.myLooper() != Looper.getMainLooper()) { + mLatch.await(2, TimeUnit.SECONDS); + } + return mResponse; + } + } +} diff --git a/core/java/android/view/textclassifier/TextClassification.aidl b/core/java/android/view/textclassifier/TextClassification.aidl new file mode 100644 index 000000000000..9fefe5d4176a --- /dev/null +++ b/core/java/android/view/textclassifier/TextClassification.aidl @@ -0,0 +1,20 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.view.textclassifier; + +parcelable TextClassification; +parcelable TextClassification.Options;
\ No newline at end of file diff --git a/core/java/android/view/textclassifier/TextClassification.java b/core/java/android/view/textclassifier/TextClassification.java index 54e93d5afd88..a6a2a945e774 100644 --- a/core/java/android/view/textclassifier/TextClassification.java +++ b/core/java/android/view/textclassifier/TextClassification.java @@ -97,7 +97,7 @@ import java.util.Map; * }); * }</pre> */ -public final class TextClassification { +public final class TextClassification implements Parcelable { /** * @hide @@ -310,42 +310,6 @@ public final class TextClassification { mSignature); } - /** Helper for parceling via #ParcelableWrapper. */ - private void writeToParcel(Parcel dest, int flags) { - dest.writeString(mText); - final Bitmap primaryIconBitmap = drawableToBitmap(mPrimaryIcon, MAX_PRIMARY_ICON_SIZE); - dest.writeInt(primaryIconBitmap != null ? 1 : 0); - if (primaryIconBitmap != null) { - primaryIconBitmap.writeToParcel(dest, flags); - } - dest.writeString(mPrimaryLabel); - dest.writeInt(mPrimaryIntent != null ? 1 : 0); - if (mPrimaryIntent != null) { - mPrimaryIntent.writeToParcel(dest, flags); - } - // mPrimaryOnClickListener is not parcelable. - dest.writeTypedList(drawablesToBitmaps(mSecondaryIcons, MAX_SECONDARY_ICON_SIZE)); - dest.writeStringList(mSecondaryLabels); - dest.writeTypedList(mSecondaryIntents); - mEntityConfidence.writeToParcel(dest, flags); - dest.writeString(mSignature); - } - - /** Helper for unparceling via #ParcelableWrapper. */ - private TextClassification(Parcel in) { - mText = in.readString(); - mPrimaryIcon = in.readInt() == 0 - ? null : new BitmapDrawable(null, Bitmap.CREATOR.createFromParcel(in)); - mPrimaryLabel = in.readString(); - mPrimaryIntent = in.readInt() == 0 ? null : Intent.CREATOR.createFromParcel(in); - mPrimaryOnClickListener = null; // not parcelable - mSecondaryIcons = bitmapsToDrawables(in.createTypedArrayList(Bitmap.CREATOR)); - mSecondaryLabels = in.createStringArrayList(); - mSecondaryIntents = in.createTypedArrayList(Intent.CREATOR); - mEntityConfidence = EntityConfidence.CREATOR.createFromParcel(in); - mSignature = in.readString(); - } - /** * Creates an OnClickListener that starts an activity with the specified intent. * @@ -675,46 +639,56 @@ public final class TextClassification { } } - /** - * Parcelable wrapper for TextClassification objects. - * @hide - */ - public static final class ParcelableWrapper implements Parcelable { - - @NonNull private TextClassification mTextClassification; - - public ParcelableWrapper(@NonNull TextClassification textClassification) { - Preconditions.checkNotNull(textClassification); - mTextClassification = textClassification; - } - - @NonNull - public TextClassification getTextClassification() { - return mTextClassification; - } + @Override + public int describeContents() { + return 0; + } - @Override - public int describeContents() { - return 0; + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(mText); + final Bitmap primaryIconBitmap = drawableToBitmap(mPrimaryIcon, MAX_PRIMARY_ICON_SIZE); + dest.writeInt(primaryIconBitmap != null ? 1 : 0); + if (primaryIconBitmap != null) { + primaryIconBitmap.writeToParcel(dest, flags); } - - @Override - public void writeToParcel(Parcel dest, int flags) { - mTextClassification.writeToParcel(dest, flags); + dest.writeString(mPrimaryLabel); + dest.writeInt(mPrimaryIntent != null ? 1 : 0); + if (mPrimaryIntent != null) { + mPrimaryIntent.writeToParcel(dest, flags); } + // mPrimaryOnClickListener is not parcelable. + dest.writeTypedList(drawablesToBitmaps(mSecondaryIcons, MAX_SECONDARY_ICON_SIZE)); + dest.writeStringList(mSecondaryLabels); + dest.writeTypedList(mSecondaryIntents); + mEntityConfidence.writeToParcel(dest, flags); + dest.writeString(mSignature); + } - public static final Parcelable.Creator<ParcelableWrapper> CREATOR = - new Parcelable.Creator<ParcelableWrapper>() { - @Override - public ParcelableWrapper createFromParcel(Parcel in) { - return new ParcelableWrapper(new TextClassification(in)); - } + public static final Parcelable.Creator<TextClassification> CREATOR = + new Parcelable.Creator<TextClassification>() { + @Override + public TextClassification createFromParcel(Parcel in) { + return new TextClassification(in); + } - @Override - public ParcelableWrapper[] newArray(int size) { - return new ParcelableWrapper[size]; - } - }; + @Override + public TextClassification[] newArray(int size) { + return new TextClassification[size]; + } + }; + private TextClassification(Parcel in) { + mText = in.readString(); + mPrimaryIcon = in.readInt() == 0 + ? null : new BitmapDrawable(null, Bitmap.CREATOR.createFromParcel(in)); + mPrimaryLabel = in.readString(); + mPrimaryIntent = in.readInt() == 0 ? null : Intent.CREATOR.createFromParcel(in); + mPrimaryOnClickListener = null; // not parcelable + mSecondaryIcons = bitmapsToDrawables(in.createTypedArrayList(Bitmap.CREATOR)); + mSecondaryLabels = in.createStringArrayList(); + mSecondaryIntents = in.createTypedArrayList(Intent.CREATOR); + mEntityConfidence = EntityConfidence.CREATOR.createFromParcel(in); + mSignature = in.readString(); } } diff --git a/core/java/android/view/textclassifier/TextClassificationManager.java b/core/java/android/view/textclassifier/TextClassificationManager.java index d7b07761a653..300aef2d172e 100644 --- a/core/java/android/view/textclassifier/TextClassificationManager.java +++ b/core/java/android/view/textclassifier/TextClassificationManager.java @@ -19,6 +19,8 @@ package android.view.textclassifier; import android.annotation.Nullable; import android.annotation.SystemService; import android.content.Context; +import android.os.ServiceManager; +import android.service.textclassifier.TextClassifierService; import com.android.internal.util.Preconditions; @@ -28,10 +30,16 @@ import com.android.internal.util.Preconditions; @SystemService(Context.TEXT_CLASSIFICATION_SERVICE) public final class TextClassificationManager { - private final Object mTextClassifierLock = new Object(); + // TODO: Make this a configurable flag. + private static final boolean SYSTEM_TEXT_CLASSIFIER_ENABLED = true; + + private static final String LOG_TAG = "TextClassificationManager"; + + private final Object mLock = new Object(); private final Context mContext; private TextClassifier mTextClassifier; + private TextClassifier mSystemTextClassifier; /** @hide */ public TextClassificationManager(Context context) { @@ -39,12 +47,39 @@ public final class TextClassificationManager { } /** + * Returns the system's default TextClassifier. + * @hide + */ + // TODO: Unhide when this is ready. + public TextClassifier getSystemDefaultTextClassifier() { + synchronized (mLock) { + if (mSystemTextClassifier == null && isSystemTextClassifierEnabled()) { + try { + Log.d(LOG_TAG, "Initialized SystemTextClassifier"); + mSystemTextClassifier = new SystemTextClassifier(mContext); + } catch (ServiceManager.ServiceNotFoundException e) { + Log.e(LOG_TAG, "Could not initialize SystemTextClassifier", e); + } + } + if (mSystemTextClassifier == null) { + Log.d(LOG_TAG, "Using an in-process TextClassifier as the system default"); + mSystemTextClassifier = new TextClassifierImpl(mContext); + } + } + return mSystemTextClassifier; + } + + /** * Returns the text classifier. */ public TextClassifier getTextClassifier() { - synchronized (mTextClassifierLock) { + synchronized (mLock) { if (mTextClassifier == null) { - mTextClassifier = new TextClassifierImpl(mContext); + if (isSystemTextClassifierEnabled()) { + mTextClassifier = getSystemDefaultTextClassifier(); + } else { + mTextClassifier = new TextClassifierImpl(mContext); + } } return mTextClassifier; } @@ -56,8 +91,13 @@ public final class TextClassificationManager { * Set to {@link TextClassifier#NO_OP} to disable text classifier features. */ public void setTextClassifier(@Nullable TextClassifier textClassifier) { - synchronized (mTextClassifierLock) { + synchronized (mLock) { mTextClassifier = textClassifier; } } + + private boolean isSystemTextClassifierEnabled() { + return SYSTEM_TEXT_CLASSIFIER_ENABLED + && TextClassifierService.getServiceComponentName(mContext) != null; + } } diff --git a/core/java/android/view/textclassifier/TextClassifier.java b/core/java/android/view/textclassifier/TextClassifier.java index 04ab4474a40c..9f75c4a80ca2 100644 --- a/core/java/android/view/textclassifier/TextClassifier.java +++ b/core/java/android/view/textclassifier/TextClassifier.java @@ -23,9 +23,12 @@ import android.annotation.Nullable; import android.annotation.StringDef; import android.annotation.WorkerThread; import android.os.LocaleList; +import android.os.Looper; import android.os.Parcel; import android.os.Parcelable; import android.util.ArraySet; +import android.util.Slog; +import android.view.textclassifier.logging.Logger; import com.android.internal.util.Preconditions; @@ -39,8 +42,8 @@ import java.util.List; /** * Interface for providing text classification related features. * - * <p>Unless otherwise stated, methods of this interface are blocking operations. - * Avoid calling them on the UI thread. + * <p><strong>NOTE: </strong>Unless otherwise stated, methods of this interface are blocking + * operations. Call on a worker thread. */ public interface TextClassifier { @@ -107,6 +110,8 @@ public interface TextClassifier { * Returns suggested text selection start and end indices, recognized entity types, and their * associated confidence scores. The entity types are ordered from highest to lowest scoring. * + * <p><strong>NOTE: </strong>Call on a worker thread. + * * @param text text providing context for the selected text (which is specified * by the sub sequence starting at selectionStartIndex and ending at selectionEndIndex) * @param selectionStartIndex start index of the selected part of text @@ -125,7 +130,7 @@ public interface TextClassifier { @IntRange(from = 0) int selectionStartIndex, @IntRange(from = 0) int selectionEndIndex, @Nullable TextSelection.Options options) { - Utils.validateInput(text, selectionStartIndex, selectionEndIndex); + Utils.validate(text, selectionStartIndex, selectionEndIndex, false); return new TextSelection.Builder(selectionStartIndex, selectionEndIndex).build(); } @@ -137,6 +142,8 @@ public interface TextClassifier { * {@link #suggestSelection(CharSequence, int, int, TextSelection.Options)}. If that method * calls this method, a stack overflow error will happen. * + * <p><strong>NOTE: </strong>Call on a worker thread. + * * @param text text providing context for the selected text (which is specified * by the sub sequence starting at selectionStartIndex and ending at selectionEndIndex) * @param selectionStartIndex start index of the selected part of text @@ -161,6 +168,8 @@ public interface TextClassifier { * See {@link #suggestSelection(CharSequence, int, int)} or * {@link #suggestSelection(CharSequence, int, int, TextSelection.Options)}. * + * <p><strong>NOTE: </strong>Call on a worker thread. + * * <p><b>NOTE:</b> Do not implement. The default implementation of this method calls * {@link #suggestSelection(CharSequence, int, int, TextSelection.Options)}. If that method * calls this method, a stack overflow error will happen. @@ -182,6 +191,8 @@ public interface TextClassifier { * Classifies the specified text and returns a {@link TextClassification} object that can be * used to generate a widget for handling the classified text. * + * <p><strong>NOTE: </strong>Call on a worker thread. + * * @param text text providing context for the text to classify (which is specified * by the sub sequence starting at startIndex and ending at endIndex) * @param startIndex start index of the text to classify @@ -200,7 +211,7 @@ public interface TextClassifier { @IntRange(from = 0) int startIndex, @IntRange(from = 0) int endIndex, @Nullable TextClassification.Options options) { - Utils.validateInput(text, startIndex, endIndex); + Utils.validate(text, startIndex, endIndex, false); return TextClassification.EMPTY; } @@ -208,6 +219,8 @@ public interface TextClassifier { * Classifies the specified text and returns a {@link TextClassification} object that can be * used to generate a widget for handling the classified text. * + * <p><strong>NOTE: </strong>Call on a worker thread. + * * <p><b>NOTE:</b> Do not implement. The default implementation of this method calls * {@link #classifyText(CharSequence, int, int, TextClassification.Options)}. If that method * calls this method, a stack overflow error will happen. @@ -235,6 +248,8 @@ public interface TextClassifier { * See {@link #classifyText(CharSequence, int, int, TextClassification.Options)} or * {@link #classifyText(CharSequence, int, int)}. * + * <p><strong>NOTE: </strong>Call on a worker thread. + * * <p><b>NOTE:</b> Do not implement. The default implementation of this method calls * {@link #classifyText(CharSequence, int, int, TextClassification.Options)}. If that method * calls this method, a stack overflow error will happen. @@ -253,10 +268,10 @@ public interface TextClassifier { } /** - * Returns a {@link TextLinks} that may be applied to the text to annotate it with links - * information. + * Generates and returns a {@link TextLinks} that may be applied to the text to annotate it with + * links information. * - * If no options are supplied, default values will be used, determined by the TextClassifier. + * <p><strong>NOTE: </strong>Call on a worker thread. * * @param text the text to generate annotations for * @param options configuration for link generation @@ -268,13 +283,15 @@ public interface TextClassifier { @WorkerThread default TextLinks generateLinks( @NonNull CharSequence text, @Nullable TextLinks.Options options) { - Utils.validateInput(text); + Utils.validate(text, false); return new TextLinks.Builder(text.toString()).build(); } /** - * Returns a {@link TextLinks} that may be applied to the text to annotate it with links - * information. + * Generates and returns a {@link TextLinks} that may be applied to the text to annotate it with + * links information. + * + * <p><strong>NOTE: </strong>Call on a worker thread. * * <p><b>NOTE:</b> Do not implement. The default implementation of this method calls * {@link #generateLinks(CharSequence, TextLinks.Options)}. If that method calls this method, @@ -296,20 +313,22 @@ public interface TextClassifier { * * @see #ENTITY_PRESET_ALL * @see #ENTITY_PRESET_NONE + * @see #ENTITY_PRESET_BASE */ default Collection<String> getEntitiesForPreset(@EntityPreset int entityPreset) { return Collections.EMPTY_LIST; } /** - * Logs a TextClassifier event. + * Returns a helper for logging TextClassifier related events. * - * @param source the text classifier used to generate this event - * @param event the text classifier related event - * @hide + * @param config logger configuration */ @WorkerThread - default void logEvent(String source, String event) {} + default Logger getLogger(@NonNull Logger.Config config) { + Preconditions.checkNotNull(config); + return Logger.DISABLED; + } /** * Returns this TextClassifier's settings. @@ -424,19 +443,28 @@ public interface TextClassifier { * endIndex is greater than text.length() or is not greater than startIndex; * options is null */ - static void validateInput( - @NonNull CharSequence text, int startIndex, int endIndex) { + public static void validate( + @NonNull CharSequence text, int startIndex, int endIndex, + boolean allowInMainThread) { Preconditions.checkArgument(text != null); Preconditions.checkArgument(startIndex >= 0); Preconditions.checkArgument(endIndex <= text.length()); Preconditions.checkArgument(endIndex > startIndex); + checkMainThread(allowInMainThread); } /** * @throws IllegalArgumentException if text is null or options is null */ - static void validateInput(@NonNull CharSequence text) { + public static void validate(@NonNull CharSequence text, boolean allowInMainThread) { Preconditions.checkArgument(text != null); + checkMainThread(allowInMainThread); + } + + private static void checkMainThread(boolean allowInMainThread) { + if (!allowInMainThread && Looper.myLooper() == Looper.getMainLooper()) { + Slog.w(DEFAULT_LOG_TAG, "TextClassifier called on main thread"); + } } } } diff --git a/core/java/android/view/textclassifier/TextClassifierImpl.java b/core/java/android/view/textclassifier/TextClassifierImpl.java index 6a44fb38ee13..b03c70d19199 100644 --- a/core/java/android/view/textclassifier/TextClassifierImpl.java +++ b/core/java/android/view/textclassifier/TextClassifierImpl.java @@ -35,6 +35,8 @@ import android.provider.ContactsContract; import android.provider.Settings; import android.text.util.Linkify; import android.util.Patterns; +import android.view.textclassifier.logging.DefaultLogger; +import android.view.textclassifier.logging.Logger; import com.android.internal.annotations.GuardedBy; import com.android.internal.logging.MetricsLogger; @@ -43,6 +45,7 @@ import com.android.internal.util.Preconditions; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; +import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.Arrays; import java.util.Calendar; @@ -66,13 +69,13 @@ import java.util.regex.Pattern; * * @hide */ -final class TextClassifierImpl implements TextClassifier { +public final class TextClassifierImpl implements TextClassifier { private static final String LOG_TAG = DEFAULT_LOG_TAG; private static final String MODEL_DIR = "/etc/textclassifier/"; private static final String MODEL_FILE_REGEX = "textclassifier\\.smartselection\\.(.*)\\.model"; private static final String UPDATED_MODEL_FILE_PATH = - "/data/misc/textclassifier/textclassifier.smartselection.model"; + "/data/misc/textclassifier/textclassifier.model"; private static final List<String> ENTITY_TYPES_ALL = Collections.unmodifiableList(Arrays.asList( TextClassifier.TYPE_ADDRESS, @@ -90,30 +93,39 @@ final class TextClassifierImpl implements TextClassifier { TextClassifier.TYPE_URL)); private final Context mContext; + private final TextClassifier mFallback; private final MetricsLogger mMetricsLogger = new MetricsLogger(); - private final Object mSmartSelectionLock = new Object(); - @GuardedBy("mSmartSelectionLock") // Do not access outside this lock. + private final Object mLock = new Object(); + @GuardedBy("mLock") // Do not access outside this lock. private Map<Locale, String> mModelFilePaths; - @GuardedBy("mSmartSelectionLock") // Do not access outside this lock. + @GuardedBy("mLock") // Do not access outside this lock. private Locale mLocale; - @GuardedBy("mSmartSelectionLock") // Do not access outside this lock. + @GuardedBy("mLock") // Do not access outside this lock. private int mVersion; - @GuardedBy("mSmartSelectionLock") // Do not access outside this lock. + @GuardedBy("mLock") // Do not access outside this lock. private SmartSelection mSmartSelection; + private final Object mLoggerLock = new Object(); + @GuardedBy("mLoggerLock") // Do not access outside this lock. + private WeakReference<Logger.Config> mLoggerConfig = new WeakReference<>(null); + @GuardedBy("mLoggerLock") // Do not access outside this lock. + private Logger mLogger; // Should never be null if mLoggerConfig.get() is not null. + private TextClassifierConstants mSettings; - TextClassifierImpl(Context context) { + public TextClassifierImpl(Context context) { mContext = Preconditions.checkNotNull(context); + mFallback = TextClassifier.NO_OP; } + /** @inheritDoc */ @Override public TextSelection suggestSelection( @NonNull CharSequence text, int selectionStartIndex, int selectionEndIndex, @Nullable TextSelection.Options options) { - Utils.validateInput(text, selectionStartIndex, selectionEndIndex); + Utils.validate(text, selectionStartIndex, selectionEndIndex, false /* allowInMainThread */); try { if (text.length() > 0) { final LocaleList locales = (options == null) ? null : options.getDefaultLocales(); @@ -159,15 +171,16 @@ final class TextClassifierImpl implements TextClassifier { t); } // Getting here means something went wrong, return a NO_OP result. - return TextClassifier.NO_OP.suggestSelection( + return mFallback.suggestSelection( text, selectionStartIndex, selectionEndIndex, options); } + /** @inheritDoc */ @Override public TextClassification classifyText( @NonNull CharSequence text, int startIndex, int endIndex, @Nullable TextClassification.Options options) { - Utils.validateInput(text, startIndex, endIndex); + Utils.validate(text, startIndex, endIndex, false /* allowInMainThread */); try { if (text.length() > 0) { final String string = text.toString(); @@ -186,13 +199,14 @@ final class TextClassifierImpl implements TextClassifier { Log.e(LOG_TAG, "Error getting text classification info.", t); } // Getting here means something went wrong, return a NO_OP result. - return TextClassifier.NO_OP.classifyText(text, startIndex, endIndex, options); + return mFallback.classifyText(text, startIndex, endIndex, options); } + /** @inheritDoc */ @Override public TextLinks generateLinks( @NonNull CharSequence text, @Nullable TextLinks.Options options) { - Utils.validateInput(text); + Utils.validate(text, false /* allowInMainThread */); final String textString = text.toString(); final TextLinks.Builder builder = new TextLinks.Builder(textString); @@ -216,14 +230,13 @@ final class TextClassifierImpl implements TextClassifier { for (int i = 0; i < results.length; i++) { entityScores.put(results[i].mCollection, results[i].mScore); } - builder.addLink(new TextLinks.TextLink( - textString, span.getStartIndex(), span.getEndIndex(), entityScores)); + builder.addLink(span.getStartIndex(), span.getEndIndex(), entityScores); } } catch (Throwable t) { // Avoid throwing from this method. Log the error. Log.e(LOG_TAG, "Error getting links info.", t); } - return builder.build(); + return mFallback.generateLinks(text, options); } @Override @@ -241,12 +254,18 @@ final class TextClassifierImpl implements TextClassifier { } @Override - public void logEvent(String source, String event) { - if (LOG_TAG.equals(source)) { - mMetricsLogger.count(event, 1); + public Logger getLogger(@NonNull Logger.Config config) { + Preconditions.checkNotNull(config); + synchronized (mLoggerLock) { + if (mLoggerConfig.get() == null || !mLoggerConfig.get().equals(config)) { + mLoggerConfig = new WeakReference<>(config); + mLogger = new DefaultLogger(config); + } + return mLogger; } } + /** @hide */ @Override public TextClassifierConstants getSettings() { if (mSettings == null) { @@ -257,7 +276,7 @@ final class TextClassifierImpl implements TextClassifier { } private SmartSelection getSmartSelection(LocaleList localeList) throws FileNotFoundException { - synchronized (mSmartSelectionLock) { + synchronized (mLock) { localeList = localeList == null ? LocaleList.getEmptyLocaleList() : localeList; final Locale locale = findBestSupportedLocaleLocked(localeList); if (locale == null) { @@ -277,16 +296,12 @@ final class TextClassifierImpl implements TextClassifier { } private String getSignature(String text, int start, int end) { - synchronized (mSmartSelectionLock) { - final String versionInfo = (mLocale != null) - ? String.format(Locale.US, "%s_v%d", mLocale.toLanguageTag(), mVersion) - : ""; - final int hash = Objects.hash(text, start, end, mContext.getPackageName()); - return String.format(Locale.US, "%s|%s|%d", LOG_TAG, versionInfo, hash); + synchronized (mLock) { + return DefaultLogger.createSignature(text, start, end, mContext, mVersion, mLocale); } } - @GuardedBy("mSmartSelectionLock") // Do not call outside this lock. + @GuardedBy("mLock") // Do not call outside this lock. private ParcelFileDescriptor getFdLocked(Locale locale) throws FileNotFoundException { ParcelFileDescriptor updateFd; int updateVersion = -1; @@ -321,7 +336,7 @@ final class TextClassifierImpl implements TextClassifier { return factoryFd; } else { throw new FileNotFoundException( - String.format("No model file found for %s", locale)); + String.format(Locale.US, "No model file found for %s", locale)); } } @@ -335,7 +350,7 @@ final class TextClassifierImpl implements TextClassifier { } else { closeAndLogError(updateFd); throw new FileNotFoundException( - String.format("No model file found for %s", locale)); + String.format(Locale.US, "No model file found for %s", locale)); } } @@ -353,7 +368,7 @@ final class TextClassifierImpl implements TextClassifier { } } - @GuardedBy("mSmartSelectionLock") // Do not call outside this lock. + @GuardedBy("mLock") // Do not call outside this lock. private void destroySmartSelectionIfExistsLocked() { if (mSmartSelection != null) { mSmartSelection.close(); @@ -361,7 +376,7 @@ final class TextClassifierImpl implements TextClassifier { } } - @GuardedBy("mSmartSelectionLock") // Do not call outside this lock. + @GuardedBy("mLock") // Do not call outside this lock. @Nullable private Locale findBestSupportedLocaleLocked(LocaleList localeList) { // Specified localeList takes priority over the system default, so it is listed first. @@ -379,7 +394,7 @@ final class TextClassifierImpl implements TextClassifier { return Locale.lookup(languageRangeList, supportedLocales); } - @GuardedBy("mSmartSelectionLock") // Do not call outside this lock. + @GuardedBy("mLock") // Do not call outside this lock. private Map<Locale, String> getFactoryModelFilePathsLocked() { if (mModelFilePaths == null) { final Map<Locale, String> modelFilePaths = new HashMap<>(); diff --git a/core/java/android/view/textclassifier/TextLinks.aidl b/core/java/android/view/textclassifier/TextLinks.aidl new file mode 100644 index 000000000000..1bbb79845528 --- /dev/null +++ b/core/java/android/view/textclassifier/TextLinks.aidl @@ -0,0 +1,20 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.view.textclassifier; + +parcelable TextLinks; +parcelable TextLinks.Options;
\ No newline at end of file diff --git a/core/java/android/view/textclassifier/TextLinks.java b/core/java/android/view/textclassifier/TextLinks.java index ba854e040460..670efdd286e9 100644 --- a/core/java/android/view/textclassifier/TextLinks.java +++ b/core/java/android/view/textclassifier/TextLinks.java @@ -17,18 +17,24 @@ package android.view.textclassifier; import android.annotation.FloatRange; +import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.os.LocaleList; import android.os.Parcel; import android.os.Parcelable; -import android.text.SpannableString; +import android.widget.TextView; +import android.text.Spannable; import android.text.style.ClickableSpan; +import android.text.util.Linkify; +import android.text.util.Linkify.LinkifyMask; import android.view.View; -import android.widget.TextView; +import android.view.textclassifier.TextClassifier.EntityType; import com.android.internal.util.Preconditions; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -41,12 +47,51 @@ import java.util.function.Function; * address, url, etc) they may be. */ public final class TextLinks implements Parcelable { + + /** + * Return status of an attempt to apply TextLinks to text. + * @hide + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef({STATUS_LINKS_APPLIED, STATUS_NO_LINKS_FOUND, STATUS_NO_LINKS_APPLIED, + STATUS_DIFFERENT_TEXT}) + public @interface Status {} + + /** Links were successfully applied to the text. */ + public static final int STATUS_LINKS_APPLIED = 0; + + /** No links exist to apply to text. Links count is zero. */ + public static final int STATUS_NO_LINKS_FOUND = 1; + + /** No links applied to text. The links were filtered out. */ + public static final int STATUS_NO_LINKS_APPLIED = 2; + + /** The specified text does not match the text used to generate the links. */ + public static final int STATUS_DIFFERENT_TEXT = 3; + + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef({APPLY_STRATEGY_IGNORE, APPLY_STRATEGY_REPLACE}) + public @interface ApplyStrategy {} + + /** + * Do not replace {@link ClickableSpan}s that exist where the {@link TextLinkSpan} needs to + * be applied to. Do not apply the TextLinkSpan. + */ + public static final int APPLY_STRATEGY_IGNORE = 0; + + /** + * Replace any {@link ClickableSpan}s that exist where the {@link TextLinkSpan} needs to be + * applied to. + */ + public static final int APPLY_STRATEGY_REPLACE = 1; + private final String mFullText; private final List<TextLink> mLinks; - private TextLinks(String fullText, Collection<TextLink> links) { + private TextLinks(String fullText, ArrayList<TextLink> links) { mFullText = fullText; - mLinks = Collections.unmodifiableList(new ArrayList<>(links)); + mLinks = Collections.unmodifiableList(links); } /** @@ -60,29 +105,57 @@ public final class TextLinks implements Parcelable { * Annotates the given text with the generated links. It will fail if the provided text doesn't * match the original text used to crete the TextLinks. * - * @param text the text to apply the links to. Must match the original text. - * @param spanFactory a factory to generate spans from TextLinks. Will use a default if null. + * @param text the text to apply the links to. Must match the original text + * @param applyStrategy strategy for resolving link conflicts + * @param spanFactory a factory to generate spans from TextLinks. Will use a default if null + * + * @return a status code indicating whether or not the links were successfully applied * - * @return Success or failure. + * @hide */ - public boolean apply( - @NonNull SpannableString text, - @Nullable Function<TextLink, ClickableSpan> spanFactory) { + @Status + public int apply( + @NonNull Spannable text, + @ApplyStrategy int applyStrategy, + @Nullable Function<TextLink, TextLinkSpan> spanFactory) { Preconditions.checkNotNull(text); + checkValidApplyStrategy(applyStrategy); if (!mFullText.equals(text.toString())) { - return false; + return STATUS_DIFFERENT_TEXT; + } + if (mLinks.isEmpty()) { + return STATUS_NO_LINKS_FOUND; } if (spanFactory == null) { spanFactory = DEFAULT_SPAN_FACTORY; } + int applyCount = 0; for (TextLink link : mLinks) { - final ClickableSpan span = spanFactory.apply(link); + final TextLinkSpan span = spanFactory.apply(link); if (span != null) { - text.setSpan(span, link.getStart(), link.getEnd(), 0); + final ClickableSpan[] existingSpans = text.getSpans( + link.getStart(), link.getEnd(), ClickableSpan.class); + if (existingSpans.length > 0) { + if (applyStrategy == APPLY_STRATEGY_REPLACE) { + for (ClickableSpan existingSpan : existingSpans) { + text.removeSpan(existingSpan); + } + text.setSpan(span, link.getStart(), link.getEnd(), + Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + applyCount++; + } + } else { + text.setSpan(span, link.getStart(), link.getEnd(), + Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + applyCount++; + } } } - return true; + if (applyCount == 0) { + return STATUS_NO_LINKS_APPLIED; + } + return STATUS_LINKS_APPLIED; } @Override @@ -119,7 +192,6 @@ public final class TextLinks implements Parcelable { */ public static final class TextLink implements Parcelable { private final EntityConfidence mEntityScores; - private final String mOriginalText; private final int mStart; private final int mEnd; @@ -128,12 +200,10 @@ public final class TextLinks implements Parcelable { * * @throws IllegalArgumentException if entityScores is null or empty. */ - public TextLink(String originalText, int start, int end, Map<String, Float> entityScores) { - Preconditions.checkNotNull(originalText); + TextLink(int start, int end, Map<String, Float> entityScores) { Preconditions.checkNotNull(entityScores); Preconditions.checkArgument(!entityScores.isEmpty()); Preconditions.checkArgument(start <= end); - mOriginalText = originalText; mStart = start; mEnd = end; mEntityScores = new EntityConfidence(entityScores); @@ -171,7 +241,7 @@ public final class TextLinks implements Parcelable { * * @return the entity type at the provided index. */ - @NonNull public @TextClassifier.EntityType String getEntity(int index) { + @NonNull public @EntityType String getEntity(int index) { return mEntityScores.getEntities().get(index); } @@ -181,7 +251,7 @@ public final class TextLinks implements Parcelable { * @param entityType the entity type. */ public @FloatRange(from = 0.0, to = 1.0) float getConfidenceScore( - @TextClassifier.EntityType String entityType) { + @EntityType String entityType) { return mEntityScores.getConfidenceScore(entityType); } @@ -193,7 +263,6 @@ public final class TextLinks implements Parcelable { @Override public void writeToParcel(Parcel dest, int flags) { mEntityScores.writeToParcel(dest, flags); - dest.writeString(mOriginalText); dest.writeInt(mStart); dest.writeInt(mEnd); } @@ -213,7 +282,6 @@ public final class TextLinks implements Parcelable { private TextLink(Parcel in) { mEntityScores = EntityConfidence.CREATOR.createFromParcel(in); - mOriginalText = in.readString(); mStart = in.readInt(); mEnd = in.readInt(); } @@ -227,6 +295,32 @@ public final class TextLinks implements Parcelable { private LocaleList mDefaultLocales; private TextClassifier.EntityConfig mEntityConfig; + private @ApplyStrategy int mApplyStrategy; + private Function<TextLink, TextLinkSpan> mSpanFactory; + + /** + * Returns a new options object based on the specified link mask. + */ + public static Options fromLinkMask(@LinkifyMask int mask) { + final TextClassifier.EntityConfig entityConfig = + new TextClassifier.EntityConfig(TextClassifier.ENTITY_PRESET_NONE); + + if ((mask & Linkify.WEB_URLS) != 0) { + entityConfig.includeEntities(TextClassifier.TYPE_URL); + } + if ((mask & Linkify.EMAIL_ADDRESSES) != 0) { + entityConfig.includeEntities(TextClassifier.TYPE_EMAIL); + } + if ((mask & Linkify.PHONE_NUMBERS) != 0) { + entityConfig.includeEntities(TextClassifier.TYPE_PHONE); + } + if ((mask & Linkify.MAP_ADDRESSES) != 0) { + entityConfig.includeEntities(TextClassifier.TYPE_ADDRESS); + } + + return new Options().setEntityConfig(entityConfig); + } + public Options() {} /** @@ -251,6 +345,31 @@ public final class TextLinks implements Parcelable { } /** + * Sets a strategy for resolving conflicts when applying generated links to text that + * already have links. + * + * @throws IllegalArgumentException if applyStrategy is not valid + * + * @see #APPLY_STRATEGY_IGNORE + * @see #APPLY_STRAGETY_REPLACE + */ + public Options setApplyStrategy(@ApplyStrategy int applyStrategy) { + checkValidApplyStrategy(applyStrategy); + mApplyStrategy = applyStrategy; + return this; + } + + /** + * Sets a factory for converting a TextLink to a TextLinkSpan. + * + * <p><strong>Note: </strong>This is not parceled over IPC. + */ + public Options setSpanFactory(@Nullable Function<TextLink, TextLinkSpan> spanFactory) { + mSpanFactory = spanFactory; + return this; + } + + /** * @return ordered list of locale preferences that can be used to disambiguate * the provided text. */ @@ -260,7 +379,7 @@ public final class TextLinks implements Parcelable { } /** - * @return The config representing the set of entities to look for. + * @return The config representing the set of entities to look for * @see #setEntityConfig(TextClassifier.EntityConfig) */ @Nullable @@ -268,6 +387,29 @@ public final class TextLinks implements Parcelable { return mEntityConfig; } + /** + * @return the strategy for resolving conflictswhen applying generated links to text that + * already have links. + * + * @see #APPLY_STATEGY_IGNORE + * @see #APPLY_STRAGETY_REPLACE + */ + @ApplyStrategy + public int getApplyStrategy() { + return mApplyStrategy; + } + + /** + * Returns a factory for converting a TextLink to a TextLinkSpan. + * + * <p><strong>Note: </strong>This is not parcelable and will always return null if read + * from a parcel + */ + @Nullable + public Function<TextLink, TextLinkSpan> getSpanFactory() { + return mSpanFactory; + } + @Override public int describeContents() { return 0; @@ -283,6 +425,7 @@ public final class TextLinks implements Parcelable { if (mEntityConfig != null) { mEntityConfig.writeToParcel(dest, flags); } + dest.writeInt(mApplyStrategy); } public static final Parcelable.Creator<Options> CREATOR = @@ -305,39 +448,53 @@ public final class TextLinks implements Parcelable { if (in.readInt() > 0) { mEntityConfig = TextClassifier.EntityConfig.CREATOR.createFromParcel(in); } + mApplyStrategy = in.readInt(); } } /** * A function to create spans from TextLinks. + */ + private static final Function<TextLink, TextLinkSpan> DEFAULT_SPAN_FACTORY = + textLink -> new TextLinkSpan(textLink); + + /** + * A ClickableSpan for a TextLink. * - * Applies only to TextViews. - * We can hide this until we are convinced we want it to be part of the public API. - * - * @hide + * <p>Applies only to TextViews. */ - public static final Function<TextLink, ClickableSpan> DEFAULT_SPAN_FACTORY = - textLink -> new ClickableSpan() { - @Override - public void onClick(View widget) { - if (widget instanceof TextView) { - final TextView textView = (TextView) widget; - textView.requestActionMode(textLink); - } - } - }; + public static class TextLinkSpan extends ClickableSpan { + + private final TextLink mTextLink; + + public TextLinkSpan(@Nullable TextLink textLink) { + mTextLink = textLink; + } + + @Override + public void onClick(View widget) { + if (widget instanceof TextView) { + final TextView textView = (TextView) widget; + textView.requestActionMode(mTextLink); + } + } + + public final TextLink getTextLink() { + return mTextLink; + } + } /** * A builder to construct a TextLinks instance. */ public static final class Builder { private final String mFullText; - private final Collection<TextLink> mLinks; + private final ArrayList<TextLink> mLinks; /** * Create a new TextLinks.Builder. * - * @param fullText The full text that links will be added to. + * @param fullText The full text to annotate with links. */ public Builder(@NonNull String fullText) { mFullText = Preconditions.checkNotNull(fullText); @@ -348,10 +505,19 @@ public final class TextLinks implements Parcelable { * Adds a TextLink. * * @return this instance. + * + * @throws IllegalArgumentException if entityScores is null or empty. + */ + public Builder addLink(int start, int end, Map<String, Float> entityScores) { + mLinks.add(new TextLink(start, end, entityScores)); + return this; + } + + /** + * Removes all {@link TextLink}s. */ - public Builder addLink(TextLink link) { - Preconditions.checkNotNull(link); - mLinks.add(link); + public Builder clearTextLinks() { + mLinks.clear(); return this; } @@ -364,4 +530,14 @@ public final class TextLinks implements Parcelable { return new TextLinks(mFullText, mLinks); } } + + /** + * @throws IllegalArgumentException if the value is invalid + */ + private static void checkValidApplyStrategy(int applyStrategy) { + if (applyStrategy != APPLY_STRATEGY_IGNORE && applyStrategy != APPLY_STRATEGY_REPLACE) { + throw new IllegalArgumentException( + "Invalid apply strategy. See TextLinks.ApplyStrategy for options."); + } + } } diff --git a/core/java/android/view/textclassifier/TextSelection.aidl b/core/java/android/view/textclassifier/TextSelection.aidl new file mode 100644 index 000000000000..dab1aefa3532 --- /dev/null +++ b/core/java/android/view/textclassifier/TextSelection.aidl @@ -0,0 +1,20 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.view.textclassifier; + +parcelable TextSelection; +parcelable TextSelection.Options;
\ No newline at end of file diff --git a/core/java/android/view/textclassifier/TextSelection.java b/core/java/android/view/textclassifier/TextSelection.java index 774d42db67a0..1c93be75a3a1 100644 --- a/core/java/android/view/textclassifier/TextSelection.java +++ b/core/java/android/view/textclassifier/TextSelection.java @@ -34,7 +34,7 @@ import java.util.Map; /** * Information about where text selection should be. */ -public final class TextSelection { +public final class TextSelection implements Parcelable { private final int mStartIndex; private final int mEndIndex; @@ -112,22 +112,6 @@ public final class TextSelection { mStartIndex, mEndIndex, mEntityConfidence, mSignature); } - /** Helper for parceling via #ParcelableWrapper. */ - private void writeToParcel(Parcel dest, int flags) { - dest.writeInt(mStartIndex); - dest.writeInt(mEndIndex); - mEntityConfidence.writeToParcel(dest, flags); - dest.writeString(mSignature); - } - - /** Helper for unparceling via #ParcelableWrapper. */ - private TextSelection(Parcel in) { - mStartIndex = in.readInt(); - mEndIndex = in.readInt(); - mEntityConfidence = EntityConfidence.CREATOR.createFromParcel(in); - mSignature = in.readString(); - } - /** * Builder used to build {@link TextSelection} objects. */ @@ -272,46 +256,36 @@ public final class TextSelection { } } - /** - * Parcelable wrapper for TextSelection objects. - * @hide - */ - public static final class ParcelableWrapper implements Parcelable { - - @NonNull private TextSelection mTextSelection; - - public ParcelableWrapper(@NonNull TextSelection textSelection) { - Preconditions.checkNotNull(textSelection); - mTextSelection = textSelection; - } - - @NonNull - public TextSelection getTextSelection() { - return mTextSelection; - } - - @Override - public int describeContents() { - return 0; - } + @Override + public int describeContents() { + return 0; + } - @Override - public void writeToParcel(Parcel dest, int flags) { - mTextSelection.writeToParcel(dest, flags); - } + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(mStartIndex); + dest.writeInt(mEndIndex); + mEntityConfidence.writeToParcel(dest, flags); + dest.writeString(mSignature); + } - public static final Parcelable.Creator<ParcelableWrapper> CREATOR = - new Parcelable.Creator<ParcelableWrapper>() { - @Override - public ParcelableWrapper createFromParcel(Parcel in) { - return new ParcelableWrapper(new TextSelection(in)); - } + public static final Parcelable.Creator<TextSelection> CREATOR = + new Parcelable.Creator<TextSelection>() { + @Override + public TextSelection createFromParcel(Parcel in) { + return new TextSelection(in); + } - @Override - public ParcelableWrapper[] newArray(int size) { - return new ParcelableWrapper[size]; - } - }; + @Override + public TextSelection[] newArray(int size) { + return new TextSelection[size]; + } + }; + private TextSelection(Parcel in) { + mStartIndex = in.readInt(); + mEndIndex = in.readInt(); + mEntityConfidence = EntityConfidence.CREATOR.createFromParcel(in); + mSignature = in.readString(); } } diff --git a/core/java/android/view/textclassifier/logging/DefaultLogger.java b/core/java/android/view/textclassifier/logging/DefaultLogger.java new file mode 100644 index 000000000000..6b848351cbf6 --- /dev/null +++ b/core/java/android/view/textclassifier/logging/DefaultLogger.java @@ -0,0 +1,263 @@ +/* + * 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.view.textclassifier.logging; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.Context; +import android.metrics.LogMaker; +import android.util.Log; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.logging.MetricsLogger; +import com.android.internal.logging.nano.MetricsProto.MetricsEvent; +import com.android.internal.util.Preconditions; + +import java.util.Locale; +import java.util.Objects; + +/** + * Default Logger. + * Used internally by TextClassifierImpl. + * @hide + */ +public final class DefaultLogger extends Logger { + + private static final String LOG_TAG = "DefaultLogger"; + private static final String CLASSIFIER_ID = "androidtc"; + + private static final int START_EVENT_DELTA = MetricsEvent.FIELD_SELECTION_SINCE_START; + private static final int PREV_EVENT_DELTA = MetricsEvent.FIELD_SELECTION_SINCE_PREVIOUS; + private static final int INDEX = MetricsEvent.FIELD_SELECTION_SESSION_INDEX; + private static final int WIDGET_TYPE = MetricsEvent.FIELD_SELECTION_WIDGET_TYPE; + private static final int WIDGET_VERSION = MetricsEvent.FIELD_SELECTION_WIDGET_VERSION; + private static final int MODEL_NAME = MetricsEvent.FIELD_TEXTCLASSIFIER_MODEL; + private static final int ENTITY_TYPE = MetricsEvent.FIELD_SELECTION_ENTITY_TYPE; + private static final int SMART_START = MetricsEvent.FIELD_SELECTION_SMART_RANGE_START; + private static final int SMART_END = MetricsEvent.FIELD_SELECTION_SMART_RANGE_END; + private static final int EVENT_START = MetricsEvent.FIELD_SELECTION_RANGE_START; + private static final int EVENT_END = MetricsEvent.FIELD_SELECTION_RANGE_END; + private static final int SESSION_ID = MetricsEvent.FIELD_SELECTION_SESSION_ID; + + private static final String ZERO = "0"; + private static final String UNKNOWN = "unknown"; + + private final MetricsLogger mMetricsLogger; + + public DefaultLogger(@NonNull Config config) { + super(config); + mMetricsLogger = new MetricsLogger(); + } + + @VisibleForTesting + public DefaultLogger(@NonNull Config config, @NonNull MetricsLogger metricsLogger) { + super(config); + mMetricsLogger = Preconditions.checkNotNull(metricsLogger); + } + + @Override + public boolean isSmartSelection(@NonNull String signature) { + return CLASSIFIER_ID.equals(SignatureParser.getClassifierId(signature)); + } + + @Override + public void writeEvent(@NonNull SelectionEvent event) { + Preconditions.checkNotNull(event); + final LogMaker log = new LogMaker(MetricsEvent.TEXT_SELECTION_SESSION) + .setType(getLogType(event)) + .setSubtype(MetricsEvent.TEXT_SELECTION_INVOCATION_MANUAL) + .setPackageName(event.getPackageName()) + .addTaggedData(START_EVENT_DELTA, event.getDurationSinceSessionStart()) + .addTaggedData(PREV_EVENT_DELTA, event.getDurationSincePreviousEvent()) + .addTaggedData(INDEX, event.getEventIndex()) + .addTaggedData(WIDGET_TYPE, event.getWidgetType()) + .addTaggedData(WIDGET_VERSION, event.getWidgetVersion()) + .addTaggedData(MODEL_NAME, SignatureParser.getModelName(event.getSignature())) + .addTaggedData(ENTITY_TYPE, event.getEntityType()) + .addTaggedData(SMART_START, event.getSmartStart()) + .addTaggedData(SMART_END, event.getSmartEnd()) + .addTaggedData(EVENT_START, event.getStart()) + .addTaggedData(EVENT_END, event.getEnd()) + .addTaggedData(SESSION_ID, event.getSessionId()); + mMetricsLogger.write(log); + debugLog(log); + } + + private static int getLogType(SelectionEvent event) { + switch (event.getEventType()) { + case SelectionEvent.ACTION_OVERTYPE: + return MetricsEvent.ACTION_TEXT_SELECTION_OVERTYPE; + case SelectionEvent.ACTION_COPY: + return MetricsEvent.ACTION_TEXT_SELECTION_COPY; + case SelectionEvent.ACTION_PASTE: + return MetricsEvent.ACTION_TEXT_SELECTION_PASTE; + case SelectionEvent.ACTION_CUT: + return MetricsEvent.ACTION_TEXT_SELECTION_CUT; + case SelectionEvent.ACTION_SHARE: + return MetricsEvent.ACTION_TEXT_SELECTION_SHARE; + case SelectionEvent.ACTION_SMART_SHARE: + return MetricsEvent.ACTION_TEXT_SELECTION_SMART_SHARE; + case SelectionEvent.ACTION_DRAG: + return MetricsEvent.ACTION_TEXT_SELECTION_DRAG; + case SelectionEvent.ACTION_ABANDON: + return MetricsEvent.ACTION_TEXT_SELECTION_ABANDON; + case SelectionEvent.ACTION_OTHER: + return MetricsEvent.ACTION_TEXT_SELECTION_OTHER; + case SelectionEvent.ACTION_SELECT_ALL: + return MetricsEvent.ACTION_TEXT_SELECTION_SELECT_ALL; + case SelectionEvent.ACTION_RESET: + return MetricsEvent.ACTION_TEXT_SELECTION_RESET; + case SelectionEvent.EVENT_SELECTION_STARTED: + return MetricsEvent.ACTION_TEXT_SELECTION_START; + case SelectionEvent.EVENT_SELECTION_MODIFIED: + return MetricsEvent.ACTION_TEXT_SELECTION_MODIFY; + case SelectionEvent.EVENT_SMART_SELECTION_SINGLE: + return MetricsEvent.ACTION_TEXT_SELECTION_SMART_SINGLE; + case SelectionEvent.EVENT_SMART_SELECTION_MULTI: + return MetricsEvent.ACTION_TEXT_SELECTION_SMART_MULTI; + case SelectionEvent.EVENT_AUTO_SELECTION: + return MetricsEvent.ACTION_TEXT_SELECTION_AUTO; + default: + return MetricsEvent.VIEW_UNKNOWN; + } + } + + private static String getLogTypeString(int logType) { + switch (logType) { + case MetricsEvent.ACTION_TEXT_SELECTION_OVERTYPE: + return "OVERTYPE"; + case MetricsEvent.ACTION_TEXT_SELECTION_COPY: + return "COPY"; + case MetricsEvent.ACTION_TEXT_SELECTION_PASTE: + return "PASTE"; + case MetricsEvent.ACTION_TEXT_SELECTION_CUT: + return "CUT"; + case MetricsEvent.ACTION_TEXT_SELECTION_SHARE: + return "SHARE"; + case MetricsEvent.ACTION_TEXT_SELECTION_SMART_SHARE: + return "SMART_SHARE"; + case MetricsEvent.ACTION_TEXT_SELECTION_DRAG: + return "DRAG"; + case MetricsEvent.ACTION_TEXT_SELECTION_ABANDON: + return "ABANDON"; + case MetricsEvent.ACTION_TEXT_SELECTION_OTHER: + return "OTHER"; + case MetricsEvent.ACTION_TEXT_SELECTION_SELECT_ALL: + return "SELECT_ALL"; + case MetricsEvent.ACTION_TEXT_SELECTION_RESET: + return "RESET"; + case MetricsEvent.ACTION_TEXT_SELECTION_START: + return "SELECTION_STARTED"; + case MetricsEvent.ACTION_TEXT_SELECTION_MODIFY: + return "SELECTION_MODIFIED"; + case MetricsEvent.ACTION_TEXT_SELECTION_SMART_SINGLE: + return "SMART_SELECTION_SINGLE"; + case MetricsEvent.ACTION_TEXT_SELECTION_SMART_MULTI: + return "SMART_SELECTION_MULTI"; + case MetricsEvent.ACTION_TEXT_SELECTION_AUTO: + return "AUTO_SELECTION"; + default: + return UNKNOWN; + } + } + + private static void debugLog(LogMaker log) { + if (!DEBUG_LOG_ENABLED) return; + + final String widgetType = Objects.toString(log.getTaggedData(WIDGET_TYPE), UNKNOWN); + final String widgetVersion = Objects.toString(log.getTaggedData(WIDGET_VERSION), ""); + final String widget = widgetVersion.isEmpty() + ? widgetType : widgetType + "-" + widgetVersion; + final int index = Integer.parseInt(Objects.toString(log.getTaggedData(INDEX), ZERO)); + if (log.getType() == MetricsEvent.ACTION_TEXT_SELECTION_START) { + String sessionId = Objects.toString(log.getTaggedData(SESSION_ID), ""); + sessionId = sessionId.substring(sessionId.lastIndexOf("-") + 1); + Log.d(LOG_TAG, String.format("New selection session: %s (%s)", widget, sessionId)); + } + + final String model = Objects.toString(log.getTaggedData(MODEL_NAME), UNKNOWN); + final String entity = Objects.toString(log.getTaggedData(ENTITY_TYPE), UNKNOWN); + final String type = getLogTypeString(log.getType()); + final int smartStart = Integer.parseInt( + Objects.toString(log.getTaggedData(SMART_START), ZERO)); + final int smartEnd = Integer.parseInt( + Objects.toString(log.getTaggedData(SMART_END), ZERO)); + final int eventStart = Integer.parseInt( + Objects.toString(log.getTaggedData(EVENT_START), ZERO)); + final int eventEnd = Integer.parseInt( + Objects.toString(log.getTaggedData(EVENT_END), ZERO)); + + Log.d(LOG_TAG, String.format("%2d: %s/%s, range=%d,%d - smart_range=%d,%d (%s/%s)", + index, type, entity, eventStart, eventEnd, smartStart, smartEnd, widget, model)); + } + + /** + * Creates a signature string that may be used to tag TextClassifier results. + */ + public static String createSignature( + String text, int start, int end, Context context, int modelVersion, + @Nullable Locale locale) { + Preconditions.checkNotNull(text); + Preconditions.checkNotNull(context); + final String modelName = (locale != null) + ? String.format(Locale.US, "%s_v%d", locale.toLanguageTag(), modelVersion) + : ""; + final int hash = Objects.hash(text, start, end, context.getPackageName()); + return SignatureParser.createSignature(CLASSIFIER_ID, modelName, hash); + } + + /** + * Helper for creating and parsing signature strings for + * {@link android.view.textclassifier.TextClassifierImpl}. + */ + @VisibleForTesting + public static final class SignatureParser { + + static String createSignature(String classifierId, String modelName, int hash) { + return String.format(Locale.US, "%s|%s|%d", classifierId, modelName, hash); + } + + static String getClassifierId(String signature) { + Preconditions.checkNotNull(signature); + final int end = signature.indexOf("|"); + if (end >= 0) { + return signature.substring(0, end); + } + return ""; + } + + static String getModelName(String signature) { + Preconditions.checkNotNull(signature); + final int start = signature.indexOf("|"); + final int end = signature.indexOf("|", start); + if (start >= 0 && end >= start) { + return signature.substring(start, end); + } + return ""; + } + + static int getHash(String signature) { + Preconditions.checkNotNull(signature); + final int index1 = signature.indexOf("|"); + final int index2 = signature.indexOf("|", index1); + if (index2 > 0) { + return Integer.parseInt(signature.substring(index2)); + } + return 0; + } + } +} diff --git a/core/java/android/view/textclassifier/logging/Logger.java b/core/java/android/view/textclassifier/logging/Logger.java new file mode 100644 index 000000000000..40e4d8ce1a77 --- /dev/null +++ b/core/java/android/view/textclassifier/logging/Logger.java @@ -0,0 +1,429 @@ +/* + * 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.view.textclassifier.logging; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.StringDef; +import android.content.Context; +import android.util.Log; +import android.view.textclassifier.TextClassification; +import android.view.textclassifier.TextClassifier; +import android.view.textclassifier.TextSelection; + +import com.android.internal.util.Preconditions; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.text.BreakIterator; +import java.util.Locale; +import java.util.Objects; +import java.util.UUID; + +/** + * A helper for logging TextClassifier related events. + */ +public abstract class Logger { + + /** + * Use this to specify an indeterminate positive index. + */ + public static final int OUT_OF_BOUNDS = Integer.MAX_VALUE; + + /** + * Use this to specify an indeterminate negative index. + */ + public static final int OUT_OF_BOUNDS_NEGATIVE = Integer.MIN_VALUE; + + private static final String LOG_TAG = "Logger"; + /* package */ static final boolean DEBUG_LOG_ENABLED = true; + + private static final String NO_SIGNATURE = ""; + + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @StringDef({WIDGET_TEXTVIEW, WIDGET_WEBVIEW, WIDGET_EDITTEXT, + WIDGET_EDIT_WEBVIEW, WIDGET_CUSTOM_TEXTVIEW, WIDGET_CUSTOM_EDITTEXT, + WIDGET_CUSTOM_UNSELECTABLE_TEXTVIEW, WIDGET_UNKNOWN}) + public @interface WidgetType {} + + public static final String WIDGET_TEXTVIEW = "textview"; + public static final String WIDGET_EDITTEXT = "edittext"; + public static final String WIDGET_UNSELECTABLE_TEXTVIEW = "nosel-textview"; + public static final String WIDGET_WEBVIEW = "webview"; + public static final String WIDGET_EDIT_WEBVIEW = "edit-webview"; + public static final String WIDGET_CUSTOM_TEXTVIEW = "customview"; + public static final String WIDGET_CUSTOM_EDITTEXT = "customedit"; + public static final String WIDGET_CUSTOM_UNSELECTABLE_TEXTVIEW = "nosel-customview"; + public static final String WIDGET_UNKNOWN = "unknown"; + + private SelectionEvent mPrevEvent; + private SelectionEvent mSmartEvent; + private SelectionEvent mStartEvent; + + /** + * Logger that does not log anything. + * @hide + */ + public static final Logger DISABLED = new Logger() { + @Override + public void writeEvent(SelectionEvent event) {} + }; + + @Nullable + private final Config mConfig; + + public Logger(Config config) { + mConfig = Preconditions.checkNotNull(config); + } + + private Logger() { + mConfig = null; + } + + /** + * Writes the selection event. + * + * <p><strong>NOTE: </strong>This method is designed for subclasses. + * Apps should not call it directly. + */ + public abstract void writeEvent(@NonNull SelectionEvent event); + + /** + * Returns true if the signature matches that of a smart selection event (i.e. + * {@link SelectionEvent#EVENT_SMART_SELECTION_SINGLE} or + * {@link SelectionEvent#EVENT_SMART_SELECTION_MULTI}). + * Returns false otherwise. + */ + public boolean isSmartSelection(@NonNull String signature) { + return false; + } + + + /** + * Returns a token iterator for tokenizing text for logging purposes. + */ + public BreakIterator getTokenIterator(@NonNull Locale locale) { + return BreakIterator.getWordInstance(Preconditions.checkNotNull(locale)); + } + + /** + * Logs a "selection started" event. + * + * @param start the token index of the selected token + */ + public final void logSelectionStartedEvent(int start) { + if (mConfig == null) { + return; + } + + logEvent(new SelectionEvent( + start, start + 1, SelectionEvent.EVENT_SELECTION_STARTED, + TextClassifier.TYPE_UNKNOWN, NO_SIGNATURE, mConfig)); + } + + /** + * Logs a "selection modified" event. + * Use when the user modifies the selection. + * + * @param start the start token (inclusive) index of the selection + * @param end the end token (exclusive) index of the selection + */ + public final void logSelectionModifiedEvent(int start, int end) { + Preconditions.checkArgument(end >= start, "end cannot be less than start"); + + if (mConfig == null) { + return; + } + + logEvent(new SelectionEvent( + start, end, SelectionEvent.EVENT_SELECTION_MODIFIED, + TextClassifier.TYPE_UNKNOWN, NO_SIGNATURE, mConfig)); + } + + /** + * Logs a "selection modified" event. + * Use when the user modifies the selection and the selection's entity type is known. + * + * @param start the start token (inclusive) index of the selection + * @param end the end token (exclusive) index of the selection + * @param classification the TextClassification object returned by the TextClassifier that + * classified the selected text + */ + public final void logSelectionModifiedEvent( + int start, int end, @NonNull TextClassification classification) { + Preconditions.checkArgument(end >= start, "end cannot be less than start"); + Preconditions.checkNotNull(classification); + + if (mConfig == null) { + return; + } + + final String entityType = classification.getEntityCount() > 0 + ? classification.getEntity(0) + : TextClassifier.TYPE_UNKNOWN; + final String signature = classification.getSignature(); + logEvent(new SelectionEvent( + start, end, SelectionEvent.EVENT_SELECTION_MODIFIED, + entityType, signature, mConfig)); + } + + /** + * Logs a "selection modified" event. + * Use when a TextClassifier modifies the selection. + * + * @param start the start token (inclusive) index of the selection + * @param end the end token (exclusive) index of the selection + * @param selection the TextSelection object returned by the TextClassifier for the + * specified selection + */ + public final void logSelectionModifiedEvent( + int start, int end, @NonNull TextSelection selection) { + Preconditions.checkArgument(end >= start, "end cannot be less than start"); + Preconditions.checkNotNull(selection); + + if (mConfig == null) { + return; + } + + final int eventType; + if (isSmartSelection(selection.getSignature())) { + eventType = end - start > 1 + ? SelectionEvent.EVENT_SMART_SELECTION_MULTI + : SelectionEvent.EVENT_SMART_SELECTION_SINGLE; + + } else { + eventType = SelectionEvent.EVENT_AUTO_SELECTION; + } + final String entityType = selection.getEntityCount() > 0 + ? selection.getEntity(0) + : TextClassifier.TYPE_UNKNOWN; + final String signature = selection.getSignature(); + logEvent(new SelectionEvent(start, end, eventType, entityType, signature, mConfig)); + } + + /** + * Logs an event specifying an action taken on a selection. + * Use when the user clicks on an action to act on the selected text. + * + * @param start the start token (inclusive) index of the selection + * @param end the end token (exclusive) index of the selection + * @param actionType the action that was performed on the selection + */ + public final void logSelectionActionEvent( + int start, int end, @SelectionEvent.ActionType int actionType) { + Preconditions.checkArgument(end >= start, "end cannot be less than start"); + checkActionType(actionType); + + if (mConfig == null) { + return; + } + + logEvent(new SelectionEvent( + start, end, actionType, TextClassifier.TYPE_UNKNOWN, NO_SIGNATURE, mConfig)); + } + + /** + * Logs an event specifying an action taken on a selection. + * Use when the user clicks on an action to act on the selected text and the selection's + * entity type is known. + * + * @param start the start token (inclusive) index of the selection + * @param end the end token (exclusive) index of the selection + * @param actionType the action that was performed on the selection + * @param classification the TextClassification object returned by the TextClassifier that + * classified the selected text + * + * @throws IllegalArgumentException If actionType is not a valid SelectionEvent actionType + */ + public final void logSelectionActionEvent( + int start, int end, @SelectionEvent.ActionType int actionType, + @NonNull TextClassification classification) { + Preconditions.checkArgument(end >= start, "end cannot be less than start"); + Preconditions.checkNotNull(classification); + checkActionType(actionType); + + if (mConfig == null) { + return; + } + + final String entityType = classification.getEntityCount() > 0 + ? classification.getEntity(0) + : TextClassifier.TYPE_UNKNOWN; + final String signature = classification.getSignature(); + logEvent(new SelectionEvent(start, end, actionType, entityType, signature, mConfig)); + } + + private void logEvent(@NonNull SelectionEvent event) { + Preconditions.checkNotNull(event); + + if (event.getEventType() != SelectionEvent.EVENT_SELECTION_STARTED + && mStartEvent == null) { + if (DEBUG_LOG_ENABLED) { + Log.d(LOG_TAG, "Selection session not yet started. Ignoring event"); + } + return; + } + + final long now = System.currentTimeMillis(); + switch (event.getEventType()) { + case SelectionEvent.EVENT_SELECTION_STARTED: + Preconditions.checkArgument(event.getAbsoluteEnd() == event.getAbsoluteStart() + 1); + event.setSessionId(startNewSession()); + mStartEvent = event; + break; + case SelectionEvent.EVENT_SMART_SELECTION_SINGLE: // fall through + case SelectionEvent.EVENT_SMART_SELECTION_MULTI: + mSmartEvent = event; + break; + case SelectionEvent.EVENT_SELECTION_MODIFIED: // fall through + case SelectionEvent.EVENT_AUTO_SELECTION: + if (mPrevEvent != null + && mPrevEvent.getAbsoluteStart() == event.getAbsoluteStart() + && mPrevEvent.getAbsoluteEnd() == event.getAbsoluteEnd()) { + // Selection did not change. Ignore event. + return; + } + } + + event.setEventTime(now); + if (mStartEvent != null) { + event.setSessionId(mStartEvent.getSessionId()) + .setDurationSinceSessionStart(now - mStartEvent.getEventTime()) + .setStart(event.getAbsoluteStart() - mStartEvent.getAbsoluteStart()) + .setEnd(event.getAbsoluteEnd() - mStartEvent.getAbsoluteStart()); + } + if (mSmartEvent != null) { + event.setSignature(mSmartEvent.getSignature()) + .setSmartStart(mSmartEvent.getAbsoluteStart() - mStartEvent.getAbsoluteStart()) + .setSmartEnd(mSmartEvent.getAbsoluteEnd() - mStartEvent.getAbsoluteStart()); + } + if (mPrevEvent != null) { + event.setDurationSincePreviousEvent(now - mPrevEvent.getEventTime()) + .setEventIndex(mPrevEvent.getEventIndex() + 1); + } + writeEvent(event); + mPrevEvent = event; + + if (event.isTerminal()) { + endSession(); + } + } + + private String startNewSession() { + endSession(); + return UUID.randomUUID().toString(); + } + + private void endSession() { + mPrevEvent = null; + mSmartEvent = null; + mStartEvent = null; + } + + /** + * @throws IllegalArgumentException If eventType is not an {@link SelectionEvent.ActionType} + */ + private static void checkActionType(@SelectionEvent.EventType int eventType) + throws IllegalArgumentException { + switch (eventType) { + case SelectionEvent.ACTION_OVERTYPE: // fall through + case SelectionEvent.ACTION_COPY: // fall through + case SelectionEvent.ACTION_PASTE: // fall through + case SelectionEvent.ACTION_CUT: // fall through + case SelectionEvent.ACTION_SHARE: // fall through + case SelectionEvent.ACTION_SMART_SHARE: // fall through + case SelectionEvent.ACTION_DRAG: // fall through + case SelectionEvent.ACTION_ABANDON: // fall through + case SelectionEvent.ACTION_SELECT_ALL: // fall through + case SelectionEvent.ACTION_RESET: // fall through + return; + default: + throw new IllegalArgumentException( + String.format(Locale.US, "%d is not an eventType", eventType)); + } + } + + + /** + * A Logger config. + */ + public static final class Config { + + private final String mPackageName; + private final String mWidgetType; + @Nullable private final String mWidgetVersion; + + /** + * @param context Context of the widget the logger logs for + * @param widgetType a name for the widget being logged for. e.g. + * {@link #WIDGET_TEXTVIEW} + * @param widgetVersion a string version info for the widget the logger logs for + */ + public Config( + @NonNull Context context, + @WidgetType String widgetType, + @Nullable String widgetVersion) { + mPackageName = Preconditions.checkNotNull(context).getPackageName(); + mWidgetType = widgetType; + mWidgetVersion = widgetVersion; + } + + /** + * Returns the package name of the application the logger logs for. + */ + public String getPackageName() { + return mPackageName; + } + + /** + * Returns the name for the widget being logged for. e.g. {@link #WIDGET_TEXTVIEW}. + */ + public String getWidgetType() { + return mWidgetType; + } + + /** + * Returns string version info for the logger. This is specific to the text classifier. + */ + @Nullable + public String getWidgetVersion() { + return mWidgetVersion; + } + + @Override + public int hashCode() { + return Objects.hash(mPackageName, mWidgetType, mWidgetVersion); + } + + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + + if (!(obj instanceof Config)) { + return false; + } + + final Config other = (Config) obj; + return Objects.equals(mPackageName, other.mPackageName) + && Objects.equals(mWidgetType, other.mWidgetType) + && Objects.equals(mWidgetVersion, other.mWidgetType); + } + } +} diff --git a/core/java/android/view/textclassifier/logging/SelectionEvent.java b/core/java/android/view/textclassifier/logging/SelectionEvent.java new file mode 100644 index 000000000000..f40b65571142 --- /dev/null +++ b/core/java/android/view/textclassifier/logging/SelectionEvent.java @@ -0,0 +1,337 @@ +/* + * 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.view.textclassifier.logging; + +import android.annotation.IntDef; +import android.annotation.Nullable; +import android.view.textclassifier.TextClassifier.EntityType; + +import com.android.internal.util.Preconditions; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.Locale; + +/** + * A selection event. + * Specify index parameters as word token indices. + */ +public final class SelectionEvent { + + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef({ACTION_OVERTYPE, ACTION_COPY, ACTION_PASTE, ACTION_CUT, + ACTION_SHARE, ACTION_SMART_SHARE, ACTION_DRAG, ACTION_ABANDON, + ACTION_OTHER, ACTION_SELECT_ALL, ACTION_RESET}) + // NOTE: ActionType values should not be lower than 100 to avoid colliding with the other + // EventTypes declared below. + public @interface ActionType { + /* + * Terminal event types range: [100,200). + * Non-terminal event types range: [200,300). + */ + } + + /** User typed over the selection. */ + public static final int ACTION_OVERTYPE = 100; + /** User copied the selection. */ + public static final int ACTION_COPY = 101; + /** User pasted over the selection. */ + public static final int ACTION_PASTE = 102; + /** User cut the selection. */ + public static final int ACTION_CUT = 103; + /** User shared the selection. */ + public static final int ACTION_SHARE = 104; + /** User clicked the textAssist menu item. */ + public static final int ACTION_SMART_SHARE = 105; + /** User dragged+dropped the selection. */ + public static final int ACTION_DRAG = 106; + /** User abandoned the selection. */ + public static final int ACTION_ABANDON = 107; + /** User performed an action on the selection. */ + public static final int ACTION_OTHER = 108; + + // Non-terminal actions. + /** User activated Select All */ + public static final int ACTION_SELECT_ALL = 200; + /** User reset the smart selection. */ + public static final int ACTION_RESET = 201; + + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef({ACTION_OVERTYPE, ACTION_COPY, ACTION_PASTE, ACTION_CUT, + ACTION_SHARE, ACTION_SMART_SHARE, ACTION_DRAG, ACTION_ABANDON, + ACTION_OTHER, ACTION_SELECT_ALL, ACTION_RESET, + EVENT_SELECTION_STARTED, EVENT_SELECTION_MODIFIED, + EVENT_SMART_SELECTION_SINGLE, EVENT_SMART_SELECTION_MULTI, + EVENT_AUTO_SELECTION}) + // NOTE: EventTypes declared here must be less than 100 to avoid colliding with the + // ActionTypes declared above. + public @interface EventType { + /* + * Range: 1 -> 99. + */ + } + + /** User started a new selection. */ + public static final int EVENT_SELECTION_STARTED = 1; + /** User modified an existing selection. */ + public static final int EVENT_SELECTION_MODIFIED = 2; + /** Smart selection triggered for a single token (word). */ + public static final int EVENT_SMART_SELECTION_SINGLE = 3; + /** Smart selection triggered spanning multiple tokens (words). */ + public static final int EVENT_SMART_SELECTION_MULTI = 4; + /** Something else other than User or the default TextClassifier triggered a selection. */ + public static final int EVENT_AUTO_SELECTION = 5; + + private final int mAbsoluteStart; + private final int mAbsoluteEnd; + private final @EventType int mEventType; + private final @EntityType String mEntityType; + @Nullable private final String mWidgetVersion; + private final String mPackageName; + private final String mWidgetType; + + // These fields should only be set by creator of a SelectionEvent. + private String mSignature; + private long mEventTime; + private long mDurationSinceSessionStart; + private long mDurationSinceLastEvent; + private int mEventIndex; + private String mSessionId; + private int mStart; + private int mEnd; + private int mSmartStart; + private int mSmartEnd; + + SelectionEvent( + int start, int end, + @EventType int eventType, @EntityType String entityType, + String signature, Logger.Config config) { + Preconditions.checkArgument(end >= start, "end cannot be less than start"); + mAbsoluteStart = start; + mAbsoluteEnd = end; + mEventType = eventType; + mEntityType = Preconditions.checkNotNull(entityType); + mSignature = Preconditions.checkNotNull(signature); + Preconditions.checkNotNull(config); + mWidgetVersion = config.getWidgetVersion(); + mPackageName = Preconditions.checkNotNull(config.getPackageName()); + mWidgetType = Preconditions.checkNotNull(config.getWidgetType()); + } + + int getAbsoluteStart() { + return mAbsoluteStart; + } + + int getAbsoluteEnd() { + return mAbsoluteEnd; + } + + /** + * Returns the type of event that was triggered. e.g. {@link #ACTION_COPY}. + */ + public int getEventType() { + return mEventType; + } + + /** + * Returns the type of entity that is associated with this event. e.g. + * {@link android.view.textclassifier.TextClassifier#TYPE_EMAIL}. + */ + @EntityType + public String getEntityType() { + return mEntityType; + } + + /** + * Returns the package name of the app that this event originated in. + */ + public String getPackageName() { + return mPackageName; + } + + /** + * Returns the type of widget that was involved in triggering this event. + */ + public String getWidgetType() { + return mWidgetType; + } + + /** + * Returns a string version info for the widget this event was triggered in. + */ + public String getWidgetVersion() { + return mWidgetVersion; + } + + /** + * Returns the signature of the text classifier result associated with this event. + */ + public String getSignature() { + return mSignature; + } + + SelectionEvent setSignature(String signature) { + mSignature = Preconditions.checkNotNull(signature); + return this; + } + + /** + * Returns the time this event was triggered. + */ + public long getEventTime() { + return mEventTime; + } + + SelectionEvent setEventTime(long timeMs) { + mEventTime = timeMs; + return this; + } + + /** + * Returns the duration in ms between when this event was triggered and when the first event in + * the selection session was triggered. + */ + public long getDurationSinceSessionStart() { + return mDurationSinceSessionStart; + } + + SelectionEvent setDurationSinceSessionStart(long durationMs) { + mDurationSinceSessionStart = durationMs; + return this; + } + + /** + * Returns the duration in ms between when this event was triggered and when the previous event + * in the selection session was triggered. + */ + public long getDurationSincePreviousEvent() { + return mDurationSinceLastEvent; + } + + SelectionEvent setDurationSincePreviousEvent(long durationMs) { + this.mDurationSinceLastEvent = durationMs; + return this; + } + + /** + * Returns the index (e.g. 1st event, 2nd event, etc.) of this event in the selection session. + */ + public int getEventIndex() { + return mEventIndex; + } + + SelectionEvent setEventIndex(int index) { + mEventIndex = index; + return this; + } + + /** + * Returns the selection session id. + */ + public String getSessionId() { + return mSessionId; + } + + SelectionEvent setSessionId(String id) { + mSessionId = id; + return this; + } + + /** + * Returns the start index of this events token relative to the index of the start selection + * event in the selection session. + */ + public int getStart() { + return mStart; + } + + SelectionEvent setStart(int start) { + mStart = start; + return this; + } + + /** + * Returns the end index of this events token relative to the index of the start selection + * event in the selection session. + */ + public int getEnd() { + return mEnd; + } + + SelectionEvent setEnd(int end) { + mEnd = end; + return this; + } + + /** + * Returns the start index of this events token relative to the index of the smart selection + * event in the selection session. + */ + public int getSmartStart() { + return mSmartStart; + } + + SelectionEvent setSmartStart(int start) { + this.mSmartStart = start; + return this; + } + + /** + * Returns the end index of this events token relative to the index of the smart selection + * event in the selection session. + */ + public int getSmartEnd() { + return mSmartEnd; + } + + SelectionEvent setSmartEnd(int end) { + mSmartEnd = end; + return this; + } + + boolean isTerminal() { + switch (mEventType) { + case ACTION_OVERTYPE: // fall through + case ACTION_COPY: // fall through + case ACTION_PASTE: // fall through + case ACTION_CUT: // fall through + case ACTION_SHARE: // fall through + case ACTION_SMART_SHARE: // fall through + case ACTION_DRAG: // fall through + case ACTION_ABANDON: // fall through + case ACTION_OTHER: // fall through + return true; + default: + return false; + } + } + + @Override + public String toString() { + return String.format(Locale.US, + "SelectionEvent {absoluteStart=%d, absoluteEnd=%d, eventType=%d, entityType=%s, " + + "widgetVersion=%s, packageName=%s, widgetType=%s, signature=%s, " + + "eventTime=%d, durationSinceSessionStart=%d, durationSinceLastEvent=%d, " + + "eventIndex=%d, sessionId=%s, start=%d, end=%d, smartStart=%d, smartEnd=%d}", + mAbsoluteStart, mAbsoluteEnd, mEventType, mEntityType, + mWidgetVersion, mPackageName, mWidgetType, mSignature, + mEventTime, mDurationSinceSessionStart, mDurationSinceLastEvent, + mEventIndex, mSessionId, mStart, mEnd, mSmartStart, mSmartEnd); + } +} diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java index d2cb70e027c1..65deb3b85572 100644 --- a/core/java/android/webkit/WebView.java +++ b/core/java/android/webkit/WebView.java @@ -2106,6 +2106,18 @@ public class WebView extends AbsoluteLayout * the default directory, and other processes must call this API to define * a unique suffix. * <p> + * This means that different processes in the same application cannot directly + * share WebView-related data, since the data directories must be distinct. + * Applications that use this API may have to explicitly pass data between + * processes. For example, login cookies may have to be copied from one + * process's cookie jar to the other using {@link CookieManager} if both + * processes' WebViews are intended to be logged in. + * <p> + * Most applications should simply ensure that all components of the app + * that rely on WebView are in the same process, to avoid needing multiple + * data directories. The {@link #disableWebView} method can be used to ensure + * that the other processes do not use WebView by accident in this case. + * <p> * This API must be called before any instances of WebView are created in * this process and before any other methods in the android.webkit package * are called by this process. @@ -2126,10 +2138,14 @@ public class WebView extends AbsoluteLayout * methods in the android.webkit package are used. * <p> * Applications with multiple processes may wish to call this in processes - * which are not intended to use WebView to prevent potential data directory - * conflicts (see {@link #setDataDirectorySuffix}) and to avoid accidentally - * incurring the memory usage of initializing WebView in long-lived - * processes which have no need for it. + * that are not intended to use WebView to avoid accidentally incurring + * the memory usage of initializing WebView in long-lived processes that + * have no need for it, and to prevent potential data directory conflicts + * (see {@link #setDataDirectorySuffix}). + * <p> + * For example, an audio player application with one process for its + * activities and another process for its playback service may wish to call + * this method in the playback service's {@link android.app.Service#onCreate}. * * @throws IllegalStateException if WebView has already been initialized * in the current process. diff --git a/core/java/android/widget/Magnifier.java b/core/java/android/widget/Magnifier.java index 30dddd0d9c24..7a4c800ba15e 100644 --- a/core/java/android/widget/Magnifier.java +++ b/core/java/android/widget/Magnifier.java @@ -18,7 +18,6 @@ package android.widget; import android.annotation.FloatRange; import android.annotation.NonNull; -import android.annotation.Nullable; import android.annotation.UiThread; import android.content.Context; import android.graphics.Bitmap; @@ -30,9 +29,11 @@ import android.view.Gravity; import android.view.LayoutInflater; import android.view.PixelCopy; import android.view.Surface; +import android.view.SurfaceHolder; import android.view.SurfaceView; import android.view.View; import android.view.ViewParent; +import android.view.ViewRootImpl; import com.android.internal.util.Preconditions; @@ -124,7 +125,8 @@ public final class Magnifier { configureCoordinates(xPosInView, yPosInView); - // Clamp startX value to avoid distorting the rendering of the magnifier content. + // Clamp the startX value to avoid magnifying content which does not belong to the magnified + // view. This will not take into account overlapping views. // For this, we compute: // - zeroScrollXInSurface: this is the start x of mView, where this is not masked by a // potential scrolling container. For example, if mView is a @@ -180,8 +182,6 @@ public final class Magnifier { /** * Forces the magnifier to update its content. It uses the previous coordinates passed to * {@link #show(float, float)}. This only happens if the magnifier is currently showing. - * - * @hide */ public void update() { if (mWindow.isShowing()) { @@ -221,33 +221,48 @@ public final class Magnifier { } private void performPixelCopy(final int startXInSurface, final int startYInSurface) { - final Surface surface = getValidViewSurface(); - if (surface != null) { - mPixelCopyRequestRect.set(startXInSurface, startYInSurface, - startXInSurface + mBitmap.getWidth(), startYInSurface + mBitmap.getHeight()); - - PixelCopy.request(surface, mPixelCopyRequestRect, mBitmap, - result -> { - getImageView().invalidate(); - mPrevStartCoordsInSurface.x = startXInSurface; - mPrevStartCoordsInSurface.y = startYInSurface; - }, - mPixelCopyHandler); - } - } - - @Nullable - private Surface getValidViewSurface() { + // Get the view surface where the content will be copied from. final Surface surface; + final int surfaceWidth; + final int surfaceHeight; if (mView instanceof SurfaceView) { - surface = ((SurfaceView) mView).getHolder().getSurface(); + final SurfaceHolder surfaceHolder = ((SurfaceView) mView).getHolder(); + surface = surfaceHolder.getSurface(); + surfaceWidth = surfaceHolder.getSurfaceFrame().right; + surfaceHeight = surfaceHolder.getSurfaceFrame().bottom; } else if (mView.getViewRootImpl() != null) { - surface = mView.getViewRootImpl().mSurface; + final ViewRootImpl viewRootImpl = mView.getViewRootImpl(); + surface = viewRootImpl.mSurface; + surfaceWidth = viewRootImpl.getWidth(); + surfaceHeight = viewRootImpl.getHeight(); } else { surface = null; + surfaceWidth = NONEXISTENT_PREVIOUS_CONFIG_VALUE; + surfaceHeight = NONEXISTENT_PREVIOUS_CONFIG_VALUE; + } + + if (surface == null || !surface.isValid()) { + return; } - return (surface != null && surface.isValid()) ? surface : null; + // Clamp copy coordinates inside the surface to avoid displaying distorted content. + final int clampedStartXInSurface = Math.max(0, + Math.min(startXInSurface, surfaceWidth - mWindowWidth)); + final int clampedStartYInSurface = Math.max(0, + Math.min(startYInSurface, surfaceHeight - mWindowHeight)); + + // Perform the pixel copy. + mPixelCopyRequestRect.set(clampedStartXInSurface, + clampedStartYInSurface, + clampedStartXInSurface + mBitmap.getWidth(), + clampedStartYInSurface + mBitmap.getHeight()); + PixelCopy.request(surface, mPixelCopyRequestRect, mBitmap, + result -> { + getImageView().invalidate(); + mPrevStartCoordsInSurface.x = startXInSurface; + mPrevStartCoordsInSurface.y = startYInSurface; + }, + mPixelCopyHandler); } private ImageView getImageView() { diff --git a/core/java/android/widget/MediaControlView2.java b/core/java/android/widget/MediaControlView2.java index 84d18509f14a..2e4cccfc230f 100644 --- a/core/java/android/widget/MediaControlView2.java +++ b/core/java/android/widget/MediaControlView2.java @@ -19,17 +19,17 @@ package android.widget; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.SystemApi; import android.content.Context; import android.media.session.MediaController; import android.media.update.ApiLoader; +import android.media.update.FrameLayoutHelper; import android.media.update.MediaControlView2Provider; -import android.media.update.ViewProvider; import android.util.AttributeSet; -import android.view.MotionEvent; -import android.view.View; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; + /** * A View that contains the controls for MediaPlayer2. * It provides a wide range of UI including buttons such as "Play/Pause", "Rewind", "Fast Forward", @@ -41,15 +41,28 @@ import java.lang.annotation.RetentionPolicy; * adds it to the view. * 2) Initialize MediaControlView2 programmatically and add it to a ViewGroup instance. * - * In the first option, VideoView2 automatically connects MediaControlView2 to MediaController2, + * In the first option, VideoView2 automatically connects MediaControlView2 to MediaController, * which is necessary to communicate with MediaSession2. In the second option, however, the - * developer needs to manually retrieve a MediaController2 instance and set it to MediaControlView2 - * by calling setController(MediaController2 controller). + * developer needs to manually retrieve a MediaController instance and set it to MediaControlView2 + * by calling setController(MediaController controller). + * + * <p> + * There is no separate method that handles the show/hide behavior for MediaControlView2. Instead, + * one can directly change the visibility of this view by calling View.setVisibility(int). The + * values supported are View.VISIBLE and View.GONE. + * In addition, the following customizations are supported: + * 1. Modify default timeout value of 2 seconds by calling setTimeout(long). + * 2. Set focus to the play/pause button by calling requestPlayButtonFocus(). + * + * <p> + * It is also possible to add custom buttons with custom icons and actions inside MediaControlView2. + * Those buttons will be shown when the overflow button is clicked. + * See {@link VideoView2#setCustomActions} for more details on how to add. * * TODO PUBLIC API * @hide */ -public class MediaControlView2 extends FrameLayout { +public class MediaControlView2 extends FrameLayoutHelper<MediaControlView2Provider> { /** @hide */ @IntDef({ BUTTON_PLAY_PAUSE, @@ -67,19 +80,66 @@ public class MediaControlView2 extends FrameLayout { @Retention(RetentionPolicy.SOURCE) public @interface Button {} + /** + * MediaControlView2 button value for playing and pausing media. + */ public static final int BUTTON_PLAY_PAUSE = 1; + /** + * MediaControlView2 button value for jumping 30 seconds forward. + */ public static final int BUTTON_FFWD = 2; + /** + * MediaControlView2 button value for jumping 10 seconds backward. + */ public static final int BUTTON_REW = 3; + /** + * MediaControlView2 button value for jumping to next media. + */ public static final int BUTTON_NEXT = 4; + /** + * MediaControlView2 button value for jumping to previous media. + */ public static final int BUTTON_PREV = 5; + /** + * MediaControlView2 button value for showing/hiding subtitle track. + */ public static final int BUTTON_SUBTITLE = 6; + /** + * MediaControlView2 button value for toggling full screen. + */ public static final int BUTTON_FULL_SCREEN = 7; + /** + * MediaControlView2 button value for showing/hiding overflow buttons. + */ public static final int BUTTON_OVERFLOW = 8; + /** + * MediaControlView2 button value for muting audio. + */ public static final int BUTTON_MUTE = 9; + /** + * MediaControlView2 button value for adjusting aspect ratio of view. + */ public static final int BUTTON_ASPECT_RATIO = 10; + /** + * MediaControlView2 button value for showing/hiding settings page. + */ public static final int BUTTON_SETTINGS = 11; - private final MediaControlView2Provider mProvider; + /** + * String for receiving command to show subtitle from MediaSession. Can be checked by + * implementing {@link android.media.session.MediaSession.Callback#onCommand} + */ + public static final String COMMAND_SHOW_SUBTITLE = "showSubtitle"; + /** + * String for receiving command to hide subtitle from MediaSession. Can be checked by + * implementing {@link android.media.session.MediaSession.Callback#onCommand} + */ + public static final String COMMAND_HIDE_SUBTITLE = "hideSubtitle"; + /** + * String for receiving command to set fullscreen from MediaSession. Can be checked by + * implementing {@link android.media.session.MediaSession.Callback#onCommand} + */ + public static final String COMMAND_SET_FULLSCREEN = "setFullscreen"; public MediaControlView2(@NonNull Context context) { this(context, null); @@ -90,49 +150,34 @@ public class MediaControlView2 extends FrameLayout { } public MediaControlView2(@NonNull Context context, @Nullable AttributeSet attrs, - int defStyleAttr) { + int defStyleAttr) { this(context, attrs, defStyleAttr, 0); } public MediaControlView2(@NonNull Context context, @Nullable AttributeSet attrs, - int defStyleAttr, int defStyleRes) { - super(context, attrs, defStyleAttr, defStyleRes); - - mProvider = ApiLoader.getProvider(context) - .createMediaControlView2(this, new SuperProvider()); + int defStyleAttr, int defStyleRes) { + super((instance, superProvider) -> + ApiLoader.getProvider(context).createMediaControlView2( + (MediaControlView2) instance, superProvider), + context, attrs, defStyleAttr, defStyleRes); } /** * @hide */ + @SystemApi public MediaControlView2Provider getProvider() { return mProvider; } /** - * Sets MediaController2 instance to control corresponding MediaSession2. + * Sets MediaController instance to control corresponding MediaSession. */ public void setController(MediaController controller) { mProvider.setController_impl(controller); } /** - * Shows the control view on screen. It will disappear automatically after 3 seconds of - * inactivity. - */ - public void show() { - mProvider.show_impl(); - } - - /** - * Shows the control view on screen. It will disappear automatically after {@code timeout} - * milliseconds of inactivity. - */ - public void show(int timeout) { - mProvider.show_impl(timeout); - } - - /** * Returns whether the control view is currently shown or hidden. */ public boolean isShowing() { @@ -140,119 +185,56 @@ public class MediaControlView2 extends FrameLayout { } /** - * Hide the control view from the screen. + * Changes the visibility state of an individual button. Default value is View.Visible. + * + * @param button the {@code Button} assigned to individual buttons + * <ul> + * <li>{@link #BUTTON_PLAY_PAUSE} + * <li>{@link #BUTTON_FFWD} + * <li>{@link #BUTTON_REW} + * <li>{@link #BUTTON_NEXT} + * <li>{@link #BUTTON_PREV} + * <li>{@link #BUTTON_SUBTITLE} + * <li>{@link #BUTTON_FULL_SCREEN} + * <li>{@link #BUTTON_MUTE} + * <li>{@link #BUTTON_OVERFLOW} + * <li>{@link #BUTTON_ASPECT_RATIO} + * <li>{@link #BUTTON_SETTINGS} + * </ul> + * @param visibility One of {@link #VISIBLE}, {@link #INVISIBLE}, or {@link #GONE}. */ - public void hide() { - mProvider.hide_impl(); + public void setButtonVisibility(@Button int button, @Visibility int visibility) { + mProvider.setButtonVisibility_impl(button, visibility); } /** - * If the media selected has a subtitle track, calling this method will display the subtitle at - * the bottom of the view. If a media has multiple subtitle tracks, this method will select the - * first one of them. + * Requests focus for the play/pause button. */ - public void showSubtitle() { - mProvider.showSubtitle_impl(); + public void requestPlayButtonFocus() { + mProvider.requestPlayButtonFocus_impl(); } /** - * Hides the currently displayed subtitle. + * Sets a new timeout value (in milliseconds) for showing MediaControlView2. The default value + * is set as 2 seconds. + * @param timeout the */ - public void hideSubtitle() { - mProvider.hideSubtitle_impl(); + public void setTimeout(long timeout) { + mProvider.setTimeout_impl(timeout); } /** - * Set listeners for previous and next buttons to customize the behavior of clicking them. - * The UI for these buttons are provided as default and will be automatically displayed when - * this method is called. - * - * @param next Listener for clicking next button - * @param prev Listener for clicking previous button + * Retrieves current timeout value (in milliseconds) for showing MediaControlView2. The default + * value is set as 2 seconds. */ - public void setPrevNextListeners(View.OnClickListener next, View.OnClickListener prev) { - mProvider.setPrevNextListeners_impl(next, prev); - } - - /** - * Hides the specified button from view. - * - * @param button the constant integer assigned to individual buttons - * @param visible whether the button should be visible or not - */ - public void setButtonVisibility(int button, boolean visible) { - mProvider.setButtonVisibility_impl(button, visible); - } - - @Override - protected void onAttachedToWindow() { - mProvider.onAttachedToWindow_impl(); + public long getTimeout() { + return mProvider.getTimeout_impl(); } @Override - protected void onDetachedFromWindow() { - mProvider.onDetachedFromWindow_impl(); - } - - @Override - public CharSequence getAccessibilityClassName() { - return mProvider.getAccessibilityClassName_impl(); - } - - @Override - public boolean onTouchEvent(MotionEvent ev) { - return mProvider.onTouchEvent_impl(ev); - } - - @Override - public boolean onTrackballEvent(MotionEvent ev) { - return mProvider.onTrackballEvent_impl(ev); - } - - @Override - public void onFinishInflate() { - mProvider.onFinishInflate_impl(); - } - - @Override - public void setEnabled(boolean enabled) { - mProvider.setEnabled_impl(enabled); - } - - private class SuperProvider implements ViewProvider { - @Override - public void onAttachedToWindow_impl() { - MediaControlView2.super.onAttachedToWindow(); - } - - @Override - public void onDetachedFromWindow_impl() { - MediaControlView2.super.onDetachedFromWindow(); - } - - @Override - public CharSequence getAccessibilityClassName_impl() { - return MediaControlView2.super.getAccessibilityClassName(); - } - - @Override - public boolean onTouchEvent_impl(MotionEvent ev) { - return MediaControlView2.super.onTouchEvent(ev); - } - - @Override - public boolean onTrackballEvent_impl(MotionEvent ev) { - return MediaControlView2.super.onTrackballEvent(ev); - } - - @Override - public void onFinishInflate_impl() { - MediaControlView2.super.onFinishInflate(); - } + // TODO Move this method to ViewProvider + public void onVisibilityAggregated(boolean isVisible) { - @Override - public void setEnabled_impl(boolean enabled) { - MediaControlView2.super.setEnabled(enabled); - } + mProvider.onVisibilityAggregated_impl(isVisible); } } diff --git a/core/java/android/widget/SelectionActionModeHelper.java b/core/java/android/widget/SelectionActionModeHelper.java index 3bfa520cd942..2e354c1eee1f 100644 --- a/core/java/android/widget/SelectionActionModeHelper.java +++ b/core/java/android/widget/SelectionActionModeHelper.java @@ -37,8 +37,8 @@ import android.view.textclassifier.TextClassification; import android.view.textclassifier.TextClassifier; import android.view.textclassifier.TextLinks; import android.view.textclassifier.TextSelection; -import android.view.textclassifier.logging.SmartSelectionEventTracker; -import android.view.textclassifier.logging.SmartSelectionEventTracker.SelectionEvent; +import android.view.textclassifier.logging.Logger; +import android.view.textclassifier.logging.SelectionEvent; import android.widget.Editor.SelectionModifierCursorController; import com.android.internal.annotations.VisibleForTesting; @@ -65,6 +65,7 @@ public final class SelectionActionModeHelper { private static final String LOG_TAG = "SelectActionModeHelper"; + // TODO: Make this a configurable flag. private static final boolean SMART_SELECT_ANIMATION_ENABLED = true; private final Editor mEditor; @@ -172,7 +173,7 @@ public final class SelectionActionModeHelper { public void onSelectionDrag() { mSelectionTracker.onSelectionAction( mTextView.getSelectionStart(), mTextView.getSelectionEnd(), - SelectionEvent.ActionType.DRAG, mTextClassification); + SelectionEvent.ACTION_DRAG, mTextClassification); } public void onTextChanged(int start, int end) { @@ -574,7 +575,7 @@ public final class SelectionActionModeHelper { mSelectionEnd = editor.getTextView().getSelectionEnd(); mLogger.logSelectionAction( textView.getSelectionStart(), textView.getSelectionEnd(), - SelectionEvent.ActionType.RESET, null /* classification */); + SelectionEvent.ACTION_RESET, null /* classification */); } return selected; } @@ -583,7 +584,7 @@ public final class SelectionActionModeHelper { public void onTextChanged(int start, int end, TextClassification classification) { if (isSelectionStarted() && start == mSelectionStart && end == mSelectionEnd) { - onSelectionAction(start, end, SelectionEvent.ActionType.OVERTYPE, classification); + onSelectionAction(start, end, SelectionEvent.ACTION_OVERTYPE, classification); } } @@ -622,7 +623,7 @@ public final class SelectionActionModeHelper { if (mIsPending) { mLogger.logSelectionAction( mSelectionStart, mSelectionEnd, - SelectionEvent.ActionType.ABANDON, null /* classification */); + SelectionEvent.ACTION_ABANDON, null /* classification */); mSelectionStart = mSelectionEnd = -1; mIsPending = false; } @@ -649,22 +650,29 @@ public final class SelectionActionModeHelper { private static final String LOG_TAG = "SelectionMetricsLogger"; private static final Pattern PATTERN_WHITESPACE = Pattern.compile("\\s+"); - private final SmartSelectionEventTracker mDelegate; + private final Logger mLogger; private final boolean mEditTextLogger; - private final BreakIterator mWordIterator; + private final BreakIterator mTokenIterator; private int mStartIndex; private String mText; SelectionMetricsLogger(TextView textView) { Preconditions.checkNotNull(textView); - final @SmartSelectionEventTracker.WidgetType int widgetType = textView.isTextEditable() - ? SmartSelectionEventTracker.WidgetType.EDITTEXT - : (textView.isTextSelectable() - ? SmartSelectionEventTracker.WidgetType.TEXTVIEW - : SmartSelectionEventTracker.WidgetType.UNSELECTABLE_TEXTVIEW); - mDelegate = new SmartSelectionEventTracker(textView.getContext(), widgetType); + mLogger = textView.getTextClassifier().getLogger( + new Logger.Config(textView.getContext(), getWidetType(textView), null)); mEditTextLogger = textView.isTextEditable(); - mWordIterator = BreakIterator.getWordInstance(textView.getTextLocale()); + mTokenIterator = mLogger.getTokenIterator(textView.getTextLocale()); + } + + @Logger.WidgetType + private static String getWidetType(TextView textView) { + if (textView.isTextEditable()) { + return Logger.WIDGET_EDITTEXT; + } + if (textView.isTextSelectable()) { + return Logger.WIDGET_TEXTVIEW; + } + return Logger.WIDGET_UNSELECTABLE_TEXTVIEW; } public void logSelectionStarted(CharSequence text, int index) { @@ -674,9 +682,9 @@ public final class SelectionActionModeHelper { if (mText == null || !mText.contentEquals(text)) { mText = text.toString(); } - mWordIterator.setText(mText); + mTokenIterator.setText(mText); mStartIndex = index; - mDelegate.logEvent(SelectionEvent.selectionStarted(0)); + mLogger.logSelectionStartedEvent(0); } catch (Exception e) { // Avoid crashes due to logging. Log.d(LOG_TAG, e.getMessage()); @@ -690,14 +698,14 @@ public final class SelectionActionModeHelper { Preconditions.checkArgumentInRange(end, start, mText.length(), "end"); int[] wordIndices = getWordDelta(start, end); if (selection != null) { - mDelegate.logEvent(SelectionEvent.selectionModified( - wordIndices[0], wordIndices[1], selection)); + mLogger.logSelectionModifiedEvent( + wordIndices[0], wordIndices[1], selection); } else if (classification != null) { - mDelegate.logEvent(SelectionEvent.selectionModified( - wordIndices[0], wordIndices[1], classification)); + mLogger.logSelectionModifiedEvent( + wordIndices[0], wordIndices[1], classification); } else { - mDelegate.logEvent(SelectionEvent.selectionModified( - wordIndices[0], wordIndices[1])); + mLogger.logSelectionModifiedEvent( + wordIndices[0], wordIndices[1]); } } catch (Exception e) { // Avoid crashes due to logging. @@ -714,11 +722,11 @@ public final class SelectionActionModeHelper { Preconditions.checkArgumentInRange(end, start, mText.length(), "end"); int[] wordIndices = getWordDelta(start, end); if (classification != null) { - mDelegate.logEvent(SelectionEvent.selectionAction( - wordIndices[0], wordIndices[1], action, classification)); + mLogger.logSelectionActionEvent( + wordIndices[0], wordIndices[1], action, classification); } else { - mDelegate.logEvent(SelectionEvent.selectionAction( - wordIndices[0], wordIndices[1], action)); + mLogger.logSelectionActionEvent( + wordIndices[0], wordIndices[1], action); } } catch (Exception e) { // Avoid crashes due to logging. @@ -741,10 +749,10 @@ public final class SelectionActionModeHelper { wordIndices[0] = countWordsBackward(start); // For the selection start index, avoid counting a partial word backwards. - if (!mWordIterator.isBoundary(start) + if (!mTokenIterator.isBoundary(start) && !isWhitespace( - mWordIterator.preceding(start), - mWordIterator.following(start))) { + mTokenIterator.preceding(start), + mTokenIterator.following(start))) { // We counted a partial word. Remove it. wordIndices[0]--; } @@ -766,7 +774,7 @@ public final class SelectionActionModeHelper { int wordCount = 0; int offset = from; while (offset > mStartIndex) { - int start = mWordIterator.preceding(offset); + int start = mTokenIterator.preceding(offset); if (!isWhitespace(start, offset)) { wordCount++; } @@ -780,7 +788,7 @@ public final class SelectionActionModeHelper { int wordCount = 0; int offset = from; while (offset < mStartIndex) { - int end = mWordIterator.following(offset); + int end = mTokenIterator.following(offset); if (!isWhitespace(offset, end)) { wordCount++; } @@ -1021,20 +1029,20 @@ public final class SelectionActionModeHelper { private static int getActionType(int menuItemId) { switch (menuItemId) { case TextView.ID_SELECT_ALL: - return SelectionEvent.ActionType.SELECT_ALL; + return SelectionEvent.ACTION_SELECT_ALL; case TextView.ID_CUT: - return SelectionEvent.ActionType.CUT; + return SelectionEvent.ACTION_CUT; case TextView.ID_COPY: - return SelectionEvent.ActionType.COPY; + return SelectionEvent.ACTION_COPY; case TextView.ID_PASTE: // fall through case TextView.ID_PASTE_AS_PLAIN_TEXT: - return SelectionEvent.ActionType.PASTE; + return SelectionEvent.ACTION_PASTE; case TextView.ID_SHARE: - return SelectionEvent.ActionType.SHARE; + return SelectionEvent.ACTION_SHARE; case TextView.ID_ASSIST: - return SelectionEvent.ActionType.SMART_SHARE; + return SelectionEvent.ACTION_SMART_SHARE; default: - return SelectionEvent.ActionType.OTHER; + return SelectionEvent.ACTION_OTHER; } } diff --git a/core/java/android/widget/TimePickerClockDelegate.java b/core/java/android/widget/TimePickerClockDelegate.java index 706b0ce225dc..77670b35a1ea 100644 --- a/core/java/android/widget/TimePickerClockDelegate.java +++ b/core/java/android/widget/TimePickerClockDelegate.java @@ -50,6 +50,7 @@ import com.android.internal.R; import com.android.internal.widget.NumericTextView; import com.android.internal.widget.NumericTextView.OnValueChangedListener; + import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.Calendar; @@ -804,20 +805,56 @@ class TimePickerClockDelegate extends TimePicker.AbstractTimePickerDelegate { private void updateHeaderSeparator() { final String bestDateTimePattern = DateFormat.getBestDateTimePattern(mLocale, (mIs24Hour) ? "Hm" : "hm"); - final String separatorText; - // See http://www.unicode.org/reports/tr35/tr35-dates.html for hour formats - final char[] hourFormats = {'H', 'h', 'K', 'k'}; - int hIndex = lastIndexOfAny(bestDateTimePattern, hourFormats); - if (hIndex == -1) { - // Default case - separatorText = ":"; - } else { - separatorText = Character.toString(bestDateTimePattern.charAt(hIndex + 1)); - } + final String separatorText = getHourMinSeparatorFromPattern(bestDateTimePattern); mSeparatorView.setText(separatorText); mTextInputPickerView.updateSeparator(separatorText); } + /** + * This helper method extracts the time separator from the {@code datetimePattern}. + * + * The time separator is defined in the Unicode CLDR and cannot be supposed to be ":". + * + * See http://unicode.org/cldr/trac/browser/trunk/common/main + * + * @return Separator string. This is the character or set of quoted characters just after the + * hour marker in {@code dateTimePattern}. Returns a colon (:) if it can't locate the + * separator. + * + * @hide + */ + private static String getHourMinSeparatorFromPattern(String dateTimePattern) { + final String defaultSeparator = ":"; + boolean foundHourPattern = false; + for (int i = 0; i < dateTimePattern.length(); i++) { + switch (dateTimePattern.charAt(i)) { + // See http://www.unicode.org/reports/tr35/tr35-dates.html for hour formats. + case 'H': + case 'h': + case 'K': + case 'k': + foundHourPattern = true; + continue; + case ' ': // skip spaces + continue; + case '\'': + if (!foundHourPattern) { + continue; + } + SpannableStringBuilder quotedSubstring = new SpannableStringBuilder( + dateTimePattern.substring(i)); + int quotedTextLength = DateFormat.appendQuotedText(quotedSubstring, 0); + return quotedSubstring.subSequence(0, quotedTextLength).toString(); + default: + if (!foundHourPattern) { + continue; + } + return Character.toString(dateTimePattern.charAt(i)); + } + } + return defaultSeparator; + } + static private int lastIndexOfAny(String str, char[] any) { final int lengthAny = any.length; if (lengthAny > 0) { diff --git a/core/java/android/widget/VideoView2.java b/core/java/android/widget/VideoView2.java index ac83313bd835..78ca0114b798 100644 --- a/core/java/android/widget/VideoView2.java +++ b/core/java/android/widget/VideoView2.java @@ -27,12 +27,11 @@ import android.media.session.MediaController; import android.media.session.MediaSession; import android.media.session.PlaybackState; import android.media.update.ApiLoader; +import android.media.update.FrameLayoutHelper; import android.media.update.VideoView2Provider; -import android.media.update.ViewProvider; import android.net.Uri; import android.os.Bundle; import android.util.AttributeSet; -import android.view.MotionEvent; import android.view.View; import java.lang.annotation.Retention; @@ -102,7 +101,7 @@ import java.util.concurrent.Executor; * * @hide */ -public class VideoView2 extends FrameLayout { +public class VideoView2 extends FrameLayoutHelper<VideoView2Provider> { /** @hide */ @IntDef({ VIEW_TYPE_TEXTUREVIEW, @@ -112,21 +111,19 @@ public class VideoView2 extends FrameLayout { public @interface ViewType {} /** - * Indicates video is rendering on SurfaceView + * Indicates video is rendering on SurfaceView. * * @see #setViewType */ public static final int VIEW_TYPE_SURFACEVIEW = 1; /** - * Indicates video is rendering on TextureView + * Indicates video is rendering on TextureView. * * @see #setViewType */ public static final int VIEW_TYPE_TEXTUREVIEW = 2; - private final VideoView2Provider mProvider; - public VideoView2(@NonNull Context context) { this(context, null); } @@ -142,17 +139,10 @@ public class VideoView2 extends FrameLayout { public VideoView2( @NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) { - super(context, attrs, defStyleAttr, defStyleRes); - - mProvider = ApiLoader.getProvider(context).createVideoView2(this, new SuperProvider(), - attrs, defStyleAttr, defStyleRes); - } - - /** - * @hide - */ - public VideoView2Provider getProvider() { - return mProvider; + super((instance, superProvider) -> + ApiLoader.getProvider(context).createVideoView2( + (VideoView2) instance, superProvider, attrs, defStyleAttr, defStyleRes), + context, attrs, defStyleAttr, defStyleRes); } /** @@ -188,18 +178,12 @@ public class VideoView2 extends FrameLayout { } /** - * Starts rendering closed caption or subtitles if there is any. The first subtitle track will - * be chosen by default if there multiple subtitle tracks exist. + * Shows or hides closed caption or subtitles if there is any. + * The first subtitle track will be chosen by default if there multiple subtitle tracks exist. + * @param show shows closed caption or subtitles if this value is true, or hides. */ - public void showSubtitle() { - mProvider.showSubtitle_impl(); - } - - /** - * Stops showing closed captions or subtitles. - */ - public void hideSubtitle() { - mProvider.hideSubtitle_impl(); + public void showSubtitle(boolean show) { + mProvider.showSubtitle_impl(show); } /** @@ -503,76 +487,4 @@ public class VideoView2 extends FrameLayout { */ void onCustomAction(String action, Bundle extras); } - - @Override - protected void onAttachedToWindow() { - mProvider.onAttachedToWindow_impl(); - } - - @Override - protected void onDetachedFromWindow() { - mProvider.onDetachedFromWindow_impl(); - } - - @Override - public CharSequence getAccessibilityClassName() { - return mProvider.getAccessibilityClassName_impl(); - } - - @Override - public boolean onTouchEvent(MotionEvent ev) { - return mProvider.onTouchEvent_impl(ev); - } - - @Override - public boolean onTrackballEvent(MotionEvent ev) { - return mProvider.onTrackballEvent_impl(ev); - } - - @Override - public void onFinishInflate() { - mProvider.onFinishInflate_impl(); - } - - @Override - public void setEnabled(boolean enabled) { - mProvider.setEnabled_impl(enabled); - } - - private class SuperProvider implements ViewProvider { - @Override - public void onAttachedToWindow_impl() { - VideoView2.super.onAttachedToWindow(); - } - - @Override - public void onDetachedFromWindow_impl() { - VideoView2.super.onDetachedFromWindow(); - } - - @Override - public CharSequence getAccessibilityClassName_impl() { - return VideoView2.super.getAccessibilityClassName(); - } - - @Override - public boolean onTouchEvent_impl(MotionEvent ev) { - return VideoView2.super.onTouchEvent(ev); - } - - @Override - public boolean onTrackballEvent_impl(MotionEvent ev) { - return VideoView2.super.onTrackballEvent(ev); - } - - @Override - public void onFinishInflate_impl() { - VideoView2.super.onFinishInflate(); - } - - @Override - public void setEnabled_impl(boolean enabled) { - VideoView2.super.setEnabled(enabled); - } - } } diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java index 997d47fe8cf0..6e0ba3413e8c 100644 --- a/core/java/com/android/internal/app/ChooserActivity.java +++ b/core/java/com/android/internal/app/ChooserActivity.java @@ -841,7 +841,7 @@ public class ChooserActivity extends ResolverActivity { } @Override - public boolean startAsCaller(ResolverActivity activity, Bundle options, int userId) { + public boolean startAsCaller(Activity activity, Bundle options, int userId) { final Intent intent = getBaseIntentToSend(); if (intent == null) { return false; @@ -860,7 +860,8 @@ public class ChooserActivity extends ResolverActivity { final boolean ignoreTargetSecurity = mSourceInfo != null && mSourceInfo.getResolvedComponentName().getPackageName() .equals(mChooserTarget.getComponentName().getPackageName()); - return activity.startAsCallerImpl(intent, options, ignoreTargetSecurity, userId); + activity.startActivityAsCaller(intent, options, ignoreTargetSecurity, userId); + return true; } @Override diff --git a/core/java/com/android/internal/app/IntentForwarderActivity.java b/core/java/com/android/internal/app/IntentForwarderActivity.java index 86731bcb4bf6..398d08791b5c 100644 --- a/core/java/com/android/internal/app/IntentForwarderActivity.java +++ b/core/java/com/android/internal/app/IntentForwarderActivity.java @@ -107,7 +107,7 @@ public class IntentForwarderActivity extends Activity { || ChooserActivity.class.getName().equals(ri.activityInfo.name)); try { - startActivityAsCaller(newIntent, null, null, false, targetUserId); + startActivityAsCaller(newIntent, null, false, targetUserId); } catch (RuntimeException e) { int launchedFromUid = -1; String launchedFromPackage = "?"; diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java index d6d44908a15b..ceb06f511108 100644 --- a/core/java/com/android/internal/app/ResolverActivity.java +++ b/core/java/com/android/internal/app/ResolverActivity.java @@ -43,7 +43,6 @@ import android.net.Uri; import android.os.AsyncTask; import android.os.Build; import android.os.Bundle; -import android.os.IBinder; import android.os.PatternMatcher; import android.os.RemoteException; import android.os.StrictMode; @@ -858,36 +857,6 @@ public class ResolverActivity extends Activity { } } - public boolean startAsCallerImpl(Intent intent, Bundle options, boolean ignoreTargetSecurity, - int userId) { - // Pass intent to delegate chooser activity with permission token. - // TODO: This should move to a trampoline Activity in the system when the ChooserActivity - // moves into systemui - try { - // TODO: Once this is a small springboard activity, it can move off the UI process - // and we can move the request method to ActivityManagerInternal. - IBinder permissionToken = ActivityManager.getService() - .requestStartActivityPermissionToken(getActivityToken()); - final Intent chooserIntent = new Intent(); - final ComponentName delegateActivity = ComponentName.unflattenFromString( - Resources.getSystem().getString(R.string.config_chooserActivity)); - chooserIntent.setClassName(delegateActivity.getPackageName(), - delegateActivity.getClassName()); - chooserIntent.putExtra(ActivityManager.EXTRA_PERMISSION_TOKEN, permissionToken); - - // TODO: These extras will change as chooser activity moves into systemui - chooserIntent.putExtra(Intent.EXTRA_INTENT, intent); - chooserIntent.putExtra(ActivityManager.EXTRA_OPTIONS, options); - chooserIntent.putExtra(ActivityManager.EXTRA_IGNORE_TARGET_SECURITY, - ignoreTargetSecurity); - chooserIntent.putExtra(Intent.EXTRA_USER_ID, userId); - startActivity(chooserIntent); - } catch (RemoteException e) { - Log.e(TAG, e.toString()); - } - return true; - } - public void onActivityStarted(TargetInfo cti) { // Do nothing } @@ -1212,8 +1181,9 @@ public class ResolverActivity extends Activity { } @Override - public boolean startAsCaller(ResolverActivity activity, Bundle options, int userId) { - return activity.startAsCallerImpl(mResolvedIntent, options, false, userId); + public boolean startAsCaller(Activity activity, Bundle options, int userId) { + activity.startActivityAsCaller(mResolvedIntent, options, false, userId); + return true; } @Override @@ -1272,7 +1242,7 @@ public class ResolverActivity extends Activity { * @param userId userId to start as or {@link UserHandle#USER_NULL} for activity's caller * @return true if the start completed successfully */ - boolean startAsCaller(ResolverActivity activity, Bundle options, int userId); + boolean startAsCaller(Activity activity, Bundle options, int userId); /** * Start the activity referenced by this target as a given user. diff --git a/core/java/com/android/internal/app/procstats/ProcessState.java b/core/java/com/android/internal/app/procstats/ProcessState.java index e0effb1497ff..65bd48f99cbd 100644 --- a/core/java/com/android/internal/app/procstats/ProcessState.java +++ b/core/java/com/android/internal/app/procstats/ProcessState.java @@ -17,25 +17,18 @@ package com.android.internal.app.procstats; import android.os.Parcel; -import android.os.Parcelable; import android.os.SystemClock; -import android.os.SystemProperties; import android.os.UserHandle; -import android.service.pm.PackageProto; import android.service.procstats.ProcessStatsProto; -import android.text.format.DateFormat; import android.util.ArrayMap; -import android.util.ArraySet; import android.util.DebugUtils; import android.util.Log; import android.util.LongSparseArray; import android.util.Slog; -import android.util.SparseArray; import android.util.TimeUtils; import android.util.proto.ProtoOutputStream; import android.util.proto.ProtoUtils; -import com.android.internal.app.procstats.ProcessStats; import com.android.internal.app.procstats.ProcessStats.PackageState; import com.android.internal.app.procstats.ProcessStats.ProcessStateHolder; import com.android.internal.app.procstats.ProcessStats.TotalMemoryUseCollection; @@ -43,6 +36,9 @@ import static com.android.internal.app.procstats.ProcessStats.PSS_SAMPLE_COUNT; import static com.android.internal.app.procstats.ProcessStats.PSS_MINIMUM; import static com.android.internal.app.procstats.ProcessStats.PSS_AVERAGE; import static com.android.internal.app.procstats.ProcessStats.PSS_MAXIMUM; +import static com.android.internal.app.procstats.ProcessStats.PSS_RSS_MINIMUM; +import static com.android.internal.app.procstats.ProcessStats.PSS_RSS_AVERAGE; +import static com.android.internal.app.procstats.ProcessStats.PSS_RSS_MAXIMUM; import static com.android.internal.app.procstats.ProcessStats.PSS_USS_MINIMUM; import static com.android.internal.app.procstats.ProcessStats.PSS_USS_AVERAGE; import static com.android.internal.app.procstats.ProcessStats.PSS_USS_MAXIMUM; @@ -64,20 +60,10 @@ import static com.android.internal.app.procstats.ProcessStats.STATE_CACHED_ACTIV import static com.android.internal.app.procstats.ProcessStats.STATE_CACHED_EMPTY; import static com.android.internal.app.procstats.ProcessStats.STATE_COUNT; -import dalvik.system.VMRuntime; -import libcore.util.EmptyArray; - -import java.io.IOException; -import java.io.InputStream; import java.io.PrintWriter; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; import java.util.Comparator; import java.util.HashMap; -import java.util.List; import java.util.Map; -import java.util.Objects; public final class ProcessState { private static final String TAG = "ProcessStats"; @@ -469,13 +455,21 @@ public final class ProcessState { } } - public void addPss(long pss, long uss, boolean always, int type, long duration, + public void addPss(long pss, long uss, long rss, boolean always, int type, long duration, ArrayMap<String, ProcessStateHolder> pkgList) { ensureNotDead(); switch (type) { - case ProcessStats.ADD_PSS_INTERNAL: - mStats.mInternalPssCount++; - mStats.mInternalPssTime += duration; + case ProcessStats.ADD_PSS_INTERNAL_SINGLE: + mStats.mInternalSinglePssCount++; + mStats.mInternalSinglePssTime += duration; + break; + case ProcessStats.ADD_PSS_INTERNAL_ALL_MEM: + mStats.mInternalAllMemPssCount++; + mStats.mInternalAllMemPssTime += duration; + break; + case ProcessStats.ADD_PSS_INTERNAL_ALL_POLL: + mStats.mInternalAllPollPssCount++; + mStats.mInternalAllPollPssTime += duration; break; case ProcessStats.ADD_PSS_EXTERNAL: mStats.mExternalPssCount++; @@ -496,7 +490,8 @@ public final class ProcessState { mLastPssTime = SystemClock.uptimeMillis(); if (mCurState != STATE_NOTHING) { // First update the common process. - mCommonProcess.mPssTable.mergeStats(mCurState, 1, pss, pss, pss, uss, uss, uss); + mCommonProcess.mPssTable.mergeStats(mCurState, 1, pss, pss, pss, uss, uss, uss, + rss, rss, rss); // If the common process is not multi-package, there is nothing else to do. if (!mCommonProcess.mMultiPackage) { @@ -506,7 +501,7 @@ public final class ProcessState { if (pkgList != null) { for (int ip=pkgList.size()-1; ip>=0; ip--) { pullFixedProc(pkgList, ip).mPssTable.mergeStats(mCurState, 1, - pss, pss, pss, uss, uss, uss); + pss, pss, pss, uss, uss, uss, rss, rss, rss); } } } @@ -658,6 +653,18 @@ public final class ProcessState { return mPssTable.getValueForId((byte)state, PSS_USS_MAXIMUM); } + public long getPssRssMinimum(int state) { + return mPssTable.getValueForId((byte)state, PSS_RSS_MINIMUM); + } + + public long getPssRssAverage(int state) { + return mPssTable.getValueForId((byte)state, PSS_RSS_AVERAGE); + } + + public long getPssRssMaximum(int state) { + return mPssTable.getValueForId((byte)state, PSS_RSS_MAXIMUM); + } + /** * Sums up the PSS data and adds it to 'data'. * @@ -793,6 +800,8 @@ public final class ProcessState { new int[] {STATE_SERVICE_RESTARTING}, now, totalTime, true); dumpProcessSummaryDetails(pw, prefix, " Receiver: ", screenStates, memStates, new int[] {STATE_RECEIVER}, now, totalTime, true); + dumpProcessSummaryDetails(pw, prefix, " Heavy: ", screenStates, memStates, + new int[] {STATE_HOME}, now, totalTime, true); dumpProcessSummaryDetails(pw, prefix, " (Home): ", screenStates, memStates, new int[] {STATE_HOME}, now, totalTime, true); dumpProcessSummaryDetails(pw, prefix, " (Last Act): ", screenStates, memStates, @@ -897,6 +906,12 @@ public final class ProcessState { DebugUtils.printSizeValue(pw, getPssUssAverage(bucket) * 1024); pw.print(" "); DebugUtils.printSizeValue(pw, getPssUssMaximum(bucket) * 1024); + pw.print(" / "); + DebugUtils.printSizeValue(pw, getPssRssMinimum(bucket) * 1024); + pw.print(" "); + DebugUtils.printSizeValue(pw, getPssRssAverage(bucket) * 1024); + pw.print(" "); + DebugUtils.printSizeValue(pw, getPssRssMaximum(bucket) * 1024); pw.println(); } } @@ -969,7 +984,8 @@ public final class ProcessState { public void computeProcessData(ProcessStats.ProcessDataCollection data, long now) { data.totalTime = 0; data.numPss = data.minPss = data.avgPss = data.maxPss = - data.minUss = data.avgUss = data.maxUss = 0; + data.minUss = data.avgUss = data.maxUss = + data.minRss = data.avgRss = data.maxRss = 0; for (int is=0; is<data.screenStates.length; is++) { for (int im=0; im<data.memStates.length; im++) { for (int ip=0; ip<data.procStates.length; ip++) { @@ -984,6 +1000,9 @@ public final class ProcessState { long minUss = getPssUssMinimum(bucket); long avgUss = getPssUssAverage(bucket); long maxUss = getPssUssMaximum(bucket); + long minRss = getPssRssMinimum(bucket); + long avgRss = getPssRssAverage(bucket); + long maxRss = getPssRssMaximum(bucket); if (data.numPss == 0) { data.minPss = minPss; data.avgPss = avgPss; @@ -991,6 +1010,9 @@ public final class ProcessState { data.minUss = minUss; data.avgUss = avgUss; data.maxUss = maxUss; + data.minRss = minRss; + data.avgRss = avgRss; + data.maxRss = maxRss; } else { if (minPss < data.minPss) { data.minPss = minPss; @@ -1008,6 +1030,14 @@ public final class ProcessState { if (maxUss > data.maxUss) { data.maxUss = maxUss; } + if (minRss < data.minRss) { + data.minRss = minRss; + } + data.avgRss = (long)( ((data.avgRss*(double)data.numPss) + + (avgRss*(double)samples)) / (data.numPss+samples) ); + if (maxRss > data.maxRss) { + data.maxRss = maxRss; + } } data.numPss += samples; } @@ -1176,6 +1206,12 @@ public final class ProcessState { pw.print(mPssTable.getValue(key, PSS_USS_AVERAGE)); pw.print(':'); pw.print(mPssTable.getValue(key, PSS_USS_MAXIMUM)); + pw.print(':'); + pw.print(mPssTable.getValue(key, PSS_RSS_MINIMUM)); + pw.print(':'); + pw.print(mPssTable.getValue(key, PSS_RSS_AVERAGE)); + pw.print(':'); + pw.print(mPssTable.getValue(key, PSS_RSS_MAXIMUM)); } } @@ -1249,6 +1285,10 @@ public final class ProcessState { mPssTable.getValue(key, PSS_USS_MINIMUM), mPssTable.getValue(key, PSS_USS_AVERAGE), mPssTable.getValue(key, PSS_USS_MAXIMUM)); + ProtoUtils.toAggStatsProto(proto, ProcessStatsProto.State.RSS, + mPssTable.getValue(key, PSS_RSS_MINIMUM), + mPssTable.getValue(key, PSS_RSS_AVERAGE), + mPssTable.getValue(key, PSS_RSS_MAXIMUM)); proto.end(stateToken); } diff --git a/core/java/com/android/internal/app/procstats/ProcessStats.java b/core/java/com/android/internal/app/procstats/ProcessStats.java index 591a58741591..d35bdddb3c57 100644 --- a/core/java/com/android/internal/app/procstats/ProcessStats.java +++ b/core/java/com/android/internal/app/procstats/ProcessStats.java @@ -27,7 +27,6 @@ import android.text.format.DateFormat; import android.util.ArrayMap; import android.util.ArraySet; import android.util.DebugUtils; -import android.util.Log; import android.util.LongSparseArray; import android.util.Slog; import android.util.SparseArray; @@ -35,16 +34,8 @@ import android.util.TimeUtils; import android.util.proto.ProtoOutputStream; import com.android.internal.app.ProcessMap; -import com.android.internal.app.procstats.DurationsTable; -import com.android.internal.app.procstats.ProcessState; -import com.android.internal.app.procstats.PssTable; -import com.android.internal.app.procstats.ServiceState; -import com.android.internal.app.procstats.SparseMappingTable; -import com.android.internal.app.procstats.SysMemUsageTable; -import com.android.internal.app.procstats.DumpUtils.*; import dalvik.system.VMRuntime; -import libcore.util.EmptyArray; import java.io.BufferedReader; import java.io.FileReader; @@ -54,7 +45,6 @@ import java.io.PrintWriter; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; -import java.util.Comparator; import java.util.Objects; import java.util.regex.Pattern; import java.util.regex.Matcher; @@ -99,7 +89,10 @@ public final class ProcessStats implements Parcelable { public static final int PSS_USS_MINIMUM = 4; public static final int PSS_USS_AVERAGE = 5; public static final int PSS_USS_MAXIMUM = 6; - public static final int PSS_COUNT = PSS_USS_MAXIMUM+1; + public static final int PSS_RSS_MINIMUM = 7; + public static final int PSS_RSS_AVERAGE = 8; + public static final int PSS_RSS_MAXIMUM = 9; + public static final int PSS_COUNT = PSS_RSS_MAXIMUM+1; public static final int SYS_MEM_USAGE_SAMPLE_COUNT = 0; public static final int SYS_MEM_USAGE_CACHED_MINIMUM = 1; @@ -134,9 +127,11 @@ public final class ProcessStats implements Parcelable { public static final int FLAG_SHUTDOWN = 1<<1; public static final int FLAG_SYSPROPS = 1<<2; - public static final int ADD_PSS_INTERNAL = 0; - public static final int ADD_PSS_EXTERNAL = 1; - public static final int ADD_PSS_EXTERNAL_SLOW = 2; + public static final int ADD_PSS_INTERNAL_SINGLE = 0; + public static final int ADD_PSS_INTERNAL_ALL_MEM = 1; + public static final int ADD_PSS_INTERNAL_ALL_POLL = 2; + public static final int ADD_PSS_EXTERNAL = 3; + public static final int ADD_PSS_EXTERNAL_SLOW = 4; public static final int[] ALL_MEM_ADJ = new int[] { ADJ_MEM_FACTOR_NORMAL, ADJ_MEM_FACTOR_MODERATE, ADJ_MEM_FACTOR_LOW, ADJ_MEM_FACTOR_CRITICAL }; @@ -162,7 +157,7 @@ public final class ProcessStats implements Parcelable { }; // Current version of the parcel format. - private static final int PARCEL_VERSION = 24; + private static final int PARCEL_VERSION = 27; // In-memory Parcel magic number, used to detect attempts to unmarshall bad data private static final int MAGIC = 0x50535454; @@ -187,9 +182,17 @@ public final class ProcessStats implements Parcelable { boolean mHasSwappedOutPss; - // Count and total time expended doing "quick" pss computations for internal use. - public long mInternalPssCount; - public long mInternalPssTime; + // Count and total time expended doing "quick" single pss computations for internal use. + public long mInternalSinglePssCount; + public long mInternalSinglePssTime; + + // Count and total time expended doing "quick" all mem pss computations for internal use. + public long mInternalAllMemPssCount; + public long mInternalAllMemPssTime; + + // Count and total time expended doing "quick" all poll pss computations for internal use. + public long mInternalAllPollPssCount; + public long mInternalAllPollPssTime; // Count and total time expended doing "quick" pss computations due to external requests. public long mExternalPssCount; @@ -318,8 +321,12 @@ public final class ProcessStats implements Parcelable { mTimePeriodEndRealtime += other.mTimePeriodEndRealtime - other.mTimePeriodStartRealtime; mTimePeriodEndUptime += other.mTimePeriodEndUptime - other.mTimePeriodStartUptime; - mInternalPssCount += other.mInternalPssCount; - mInternalPssTime += other.mInternalPssTime; + mInternalSinglePssCount += other.mInternalSinglePssCount; + mInternalSinglePssTime += other.mInternalSinglePssTime; + mInternalAllMemPssCount += other.mInternalAllMemPssCount; + mInternalAllMemPssTime += other.mInternalAllMemPssTime; + mInternalAllPollPssCount += other.mInternalAllPollPssCount; + mInternalAllPollPssTime += other.mInternalAllPollPssTime; mExternalPssCount += other.mExternalPssCount; mExternalPssTime += other.mExternalPssTime; mExternalSlowPssCount += other.mExternalSlowPssCount; @@ -523,8 +530,12 @@ public final class ProcessStats implements Parcelable { buildTimePeriodStartClockStr(); mTimePeriodStartRealtime = mTimePeriodEndRealtime = SystemClock.elapsedRealtime(); mTimePeriodStartUptime = mTimePeriodEndUptime = SystemClock.uptimeMillis(); - mInternalPssCount = 0; - mInternalPssTime = 0; + mInternalSinglePssCount = 0; + mInternalSinglePssTime = 0; + mInternalAllMemPssCount = 0; + mInternalAllMemPssTime = 0; + mInternalAllPollPssCount = 0; + mInternalAllPollPssTime = 0; mExternalPssCount = 0; mExternalPssTime = 0; mExternalSlowPssCount = 0; @@ -789,8 +800,12 @@ public final class ProcessStats implements Parcelable { out.writeLong(mTimePeriodEndRealtime); out.writeLong(mTimePeriodStartUptime); out.writeLong(mTimePeriodEndUptime); - out.writeLong(mInternalPssCount); - out.writeLong(mInternalPssTime); + out.writeLong(mInternalSinglePssCount); + out.writeLong(mInternalSinglePssTime); + out.writeLong(mInternalAllMemPssCount); + out.writeLong(mInternalAllMemPssTime); + out.writeLong(mInternalAllPollPssCount); + out.writeLong(mInternalAllPollPssTime); out.writeLong(mExternalPssCount); out.writeLong(mExternalPssTime); out.writeLong(mExternalSlowPssCount); @@ -963,8 +978,12 @@ public final class ProcessStats implements Parcelable { mTimePeriodEndRealtime = in.readLong(); mTimePeriodStartUptime = in.readLong(); mTimePeriodEndUptime = in.readLong(); - mInternalPssCount = in.readLong(); - mInternalPssTime = in.readLong(); + mInternalSinglePssCount = in.readLong(); + mInternalSinglePssTime = in.readLong(); + mInternalAllMemPssCount = in.readLong(); + mInternalAllMemPssTime = in.readLong(); + mInternalAllPollPssCount = in.readLong(); + mInternalAllPollPssTime = in.readLong(); mExternalPssCount = in.readLong(); mExternalPssTime = in.readLong(); mExternalSlowPssCount = in.readLong(); @@ -1526,10 +1545,20 @@ public final class ProcessStats implements Parcelable { totalMem.processStateSamples[STATE_SERVICE_RESTARTING]); pw.println(); pw.println("PSS collection stats:"); - pw.print(" Internal: "); - pw.print(mInternalPssCount); + pw.print(" Internal Single: "); + pw.print(mInternalSinglePssCount); pw.print("x over "); - TimeUtils.formatDuration(mInternalPssTime, pw); + TimeUtils.formatDuration(mInternalSinglePssTime, pw); + pw.println(); + pw.print(" Internal All Procs (Memory Change): "); + pw.print(mInternalAllMemPssCount); + pw.print("x over "); + TimeUtils.formatDuration(mInternalAllMemPssTime, pw); + pw.println(); + pw.print(" Internal All Procs (Polling): "); + pw.print(mInternalAllPollPssCount); + pw.print("x over "); + TimeUtils.formatDuration(mInternalAllPollPssTime, pw); pw.println(); pw.print(" External: "); pw.print(mExternalPssCount); @@ -1854,6 +1883,9 @@ public final class ProcessStats implements Parcelable { public long minUss; public long avgUss; public long maxUss; + public long minRss; + public long avgRss; + public long maxRss; public ProcessDataCollection(int[] _screenStates, int[] _memStates, int[] _procStates) { screenStates = _screenStates; @@ -1879,6 +1911,12 @@ public final class ProcessStats implements Parcelable { DebugUtils.printSizeValue(pw, avgUss * 1024); pw.print("-"); DebugUtils.printSizeValue(pw, maxUss * 1024); + pw.print("/"); + DebugUtils.printSizeValue(pw, minRss * 1024); + pw.print("-"); + DebugUtils.printSizeValue(pw, avgRss * 1024); + pw.print("-"); + DebugUtils.printSizeValue(pw, maxRss * 1024); if (full) { pw.print(" over "); pw.print(numPss); diff --git a/core/java/com/android/internal/app/procstats/PssTable.java b/core/java/com/android/internal/app/procstats/PssTable.java index de5f67330423..1e7c566e23a7 100644 --- a/core/java/com/android/internal/app/procstats/PssTable.java +++ b/core/java/com/android/internal/app/procstats/PssTable.java @@ -16,6 +16,9 @@ package com.android.internal.app.procstats; +import static com.android.internal.app.procstats.ProcessStats.PSS_RSS_AVERAGE; +import static com.android.internal.app.procstats.ProcessStats.PSS_RSS_MAXIMUM; +import static com.android.internal.app.procstats.ProcessStats.PSS_RSS_MINIMUM; import static com.android.internal.app.procstats.ProcessStats.PSS_SAMPLE_COUNT; import static com.android.internal.app.procstats.ProcessStats.PSS_MINIMUM; import static com.android.internal.app.procstats.ProcessStats.PSS_AVERAGE; @@ -51,7 +54,10 @@ public class PssTable extends SparseMappingTable.Table { that.getValue(key, PSS_MAXIMUM), that.getValue(key, PSS_USS_MINIMUM), that.getValue(key, PSS_USS_AVERAGE), - that.getValue(key, PSS_USS_MAXIMUM)); + that.getValue(key, PSS_USS_MAXIMUM), + that.getValue(key, PSS_RSS_MINIMUM), + that.getValue(key, PSS_RSS_AVERAGE), + that.getValue(key, PSS_RSS_MAXIMUM)); } } @@ -60,7 +66,7 @@ public class PssTable extends SparseMappingTable.Table { * one and the new one, the average will now incorporate the new average, etc. */ public void mergeStats(int state, int inCount, long minPss, long avgPss, long maxPss, - long minUss, long avgUss, long maxUss) { + long minUss, long avgUss, long maxUss, long minRss, long avgRss, long maxRss) { final int key = getOrAddKey((byte)state, PSS_COUNT); final long count = getValue(key, PSS_SAMPLE_COUNT); if (count == 0) { @@ -71,6 +77,9 @@ public class PssTable extends SparseMappingTable.Table { setValue(key, PSS_USS_MINIMUM, minUss); setValue(key, PSS_USS_AVERAGE, avgUss); setValue(key, PSS_USS_MAXIMUM, maxUss); + setValue(key, PSS_RSS_MINIMUM, minRss); + setValue(key, PSS_RSS_AVERAGE, avgRss); + setValue(key, PSS_RSS_MAXIMUM, maxRss); } else { setValue(key, PSS_SAMPLE_COUNT, count + inCount); @@ -103,6 +112,20 @@ public class PssTable extends SparseMappingTable.Table { if (val < maxUss) { setValue(key, PSS_USS_MAXIMUM, maxUss); } + + val = getValue(key, PSS_RSS_MINIMUM); + if (val > minUss) { + setValue(key, PSS_RSS_MINIMUM, minUss); + } + + val = getValue(key, PSS_RSS_AVERAGE); + setValue(key, PSS_RSS_AVERAGE, + (long)(((val*(double)count)+(avgUss*(double)inCount)) / (count+inCount))); + + val = getValue(key, PSS_RSS_MAXIMUM); + if (val < maxUss) { + setValue(key, PSS_RSS_MAXIMUM, maxUss); + } } } } diff --git a/core/java/com/android/internal/logging/EventLogTags.logtags b/core/java/com/android/internal/logging/EventLogTags.logtags index a440ee402294..693bd16e6170 100644 --- a/core/java/com/android/internal/logging/EventLogTags.logtags +++ b/core/java/com/android/internal/logging/EventLogTags.logtags @@ -13,3 +13,6 @@ option java_package com.android.internal.logging; # LatencyTracker.java # --------------------------- 36070 sysui_latency (action|1|6),(latency|1|3) + +# Generic event for logging when we write system files. +525000 commit_sys_config_file (name|3),(time|2|3) diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java index fd5fe100444a..7def87655ae3 100644 --- a/core/java/com/android/internal/os/BatteryStatsImpl.java +++ b/core/java/com/android/internal/os/BatteryStatsImpl.java @@ -10169,6 +10169,7 @@ public class BatteryStatsImpl extends BatteryStats { updateDailyDeadlineLocked(); if (hasData) { + final long startTime = SystemClock.uptimeMillis(); mDailyItems.add(item); while (mDailyItems.size() > MAX_DAILY_ITEMS) { mDailyItems.remove(0); @@ -10178,10 +10179,12 @@ public class BatteryStatsImpl extends BatteryStats { XmlSerializer out = new FastXmlSerializer(); out.setOutput(memStream, StandardCharsets.UTF_8.name()); writeDailyItemsLocked(out); + final long initialTime = SystemClock.uptimeMillis() - startTime; BackgroundThread.getHandler().post(new Runnable() { @Override public void run() { synchronized (mCheckinFile) { + final long startTime2 = SystemClock.uptimeMillis(); FileOutputStream stream = null; try { stream = mDailyFile.startWrite(); @@ -10190,6 +10193,9 @@ public class BatteryStatsImpl extends BatteryStats { FileUtils.sync(stream); stream.close(); mDailyFile.finishWrite(stream); + com.android.internal.logging.EventLogTags.writeCommitSysConfigFile( + "batterystats-daily", + initialTime + SystemClock.uptimeMillis() - startTime2); } catch (IOException e) { Slog.w("BatteryStats", "Error writing battery daily items", e); @@ -10890,7 +10896,7 @@ public class BatteryStatsImpl extends BatteryStats { return null; } - /** + /** * Distribute WiFi energy info and network traffic to apps. * @param info The energy information from the WiFi controller. */ @@ -11154,6 +11160,9 @@ public class BatteryStatsImpl extends BatteryStats { } } + private ModemActivityInfo mLastModemActivityInfo = + new ModemActivityInfo(0, 0, 0, new int[0], 0, 0); + /** * Distribute Cell radio energy info and network traffic to apps. */ @@ -11174,6 +11183,22 @@ public class BatteryStatsImpl extends BatteryStats { } } + int rxTimeMs = 0; + int[] txTimeMs = new int[ModemActivityInfo.TX_POWER_LEVELS]; + int idleTimeMs = 0; + int sleepTimeMs = 0; + if (activityInfo != null) { + rxTimeMs = activityInfo.getRxTimeMillis() - mLastModemActivityInfo.getRxTimeMillis(); + for (int i = 0; i < ModemActivityInfo.TX_POWER_LEVELS; i++) { + txTimeMs[i] = activityInfo.getTxTimeMillis()[i] + - mLastModemActivityInfo.getTxTimeMillis()[i]; + } + idleTimeMs = + activityInfo.getIdleTimeMillis() - mLastModemActivityInfo.getIdleTimeMillis(); + sleepTimeMs = + activityInfo.getSleepTimeMillis() - mLastModemActivityInfo.getSleepTimeMillis(); + } + synchronized (this) { if (!mOnBatteryInternal) { if (delta != null) { @@ -11185,11 +11210,11 @@ public class BatteryStatsImpl extends BatteryStats { if (activityInfo != null) { mHasModemReporting = true; mModemActivity.getIdleTimeCounter().addCountLocked( - activityInfo.getIdleTimeMillis()); - mModemActivity.getRxTimeCounter().addCountLocked(activityInfo.getRxTimeMillis()); + idleTimeMs); + mModemActivity.getRxTimeCounter().addCountLocked(rxTimeMs); for (int lvl = 0; lvl < ModemActivityInfo.TX_POWER_LEVELS; lvl++) { mModemActivity.getTxTimeCounters()[lvl] - .addCountLocked(activityInfo.getTxTimeMillis()[lvl]); + .addCountLocked(txTimeMs[lvl]); } // POWER_MODEM_CONTROLLER_OPERATING_VOLTAGE is measured in mV, so convert to V. @@ -11197,16 +11222,15 @@ public class BatteryStatsImpl extends BatteryStats { PowerProfile.POWER_MODEM_CONTROLLER_OPERATING_VOLTAGE) / 1000.0; if (opVolt != 0) { double energyUsed = - activityInfo.getSleepTimeMillis() * + sleepTimeMs * mPowerProfile.getAveragePower(PowerProfile.POWER_MODEM_CONTROLLER_SLEEP) - + activityInfo.getIdleTimeMillis() * + + idleTimeMs * mPowerProfile.getAveragePower(PowerProfile.POWER_MODEM_CONTROLLER_IDLE) - + activityInfo.getRxTimeMillis() * + + rxTimeMs * mPowerProfile.getAveragePower(PowerProfile.POWER_MODEM_CONTROLLER_RX); - int[] txCurrentMa = activityInfo.getTxTimeMillis(); - for (int i = 0; i < Math.min(txCurrentMa.length, + for (int i = 0; i < Math.min(txTimeMs.length, SignalStrength.NUM_SIGNAL_STRENGTH_BINS); i++) { - energyUsed += txCurrentMa[i] * mPowerProfile.getAveragePower( + energyUsed += txTimeMs[i] * mPowerProfile.getAveragePower( PowerProfile.POWER_MODEM_CONTROLLER_TX, i); } @@ -11287,7 +11311,7 @@ public class BatteryStatsImpl extends BatteryStats { ControllerActivityCounterImpl activityCounter = u.getOrCreateModemControllerActivityLocked(); if (totalRxPackets > 0 && entry.rxPackets > 0) { - final long rxMs = (entry.rxPackets * activityInfo.getRxTimeMillis()) + final long rxMs = (entry.rxPackets * rxTimeMs) / totalRxPackets; activityCounter.getRxTimeCounter().addCountLocked(rxMs); } @@ -11295,7 +11319,7 @@ public class BatteryStatsImpl extends BatteryStats { if (totalTxPackets > 0 && entry.txPackets > 0) { for (int lvl = 0; lvl < ModemActivityInfo.TX_POWER_LEVELS; lvl++) { long txMs = - entry.txPackets * activityInfo.getTxTimeMillis()[lvl]; + entry.txPackets * txTimeMs[lvl]; txMs /= totalTxPackets; activityCounter.getTxTimeCounters()[lvl].addCountLocked(txMs); } @@ -11316,6 +11340,10 @@ public class BatteryStatsImpl extends BatteryStats { } } + // Cache last value for comparison. + private BluetoothActivityEnergyInfo mLastBluetoothActivityEnergyInfo = + new BluetoothActivityEnergyInfo(0, 0, 0, 0, 0, 0); + /** * Distribute Bluetooth energy info and network traffic to apps. * @param info The energy information from the bluetooth controller. @@ -11332,14 +11360,17 @@ public class BatteryStatsImpl extends BatteryStats { mHasBluetoothReporting = true; final long elapsedRealtimeMs = mClocks.elapsedRealtime(); - final long rxTimeMs = info.getControllerRxTimeMillis(); - final long txTimeMs = info.getControllerTxTimeMillis(); - + final long rxTimeMs = info.getControllerRxTimeMillis() - + mLastBluetoothActivityEnergyInfo.getControllerRxTimeMillis(); + final long txTimeMs = info.getControllerTxTimeMillis() - + mLastBluetoothActivityEnergyInfo.getControllerTxTimeMillis(); + final long idleTimeMs = info.getControllerIdleTimeMillis() - + mLastBluetoothActivityEnergyInfo.getControllerIdleTimeMillis(); if (DEBUG_ENERGY) { Slog.d(TAG, "------ BEGIN BLE power blaming ------"); Slog.d(TAG, " Tx Time: " + txTimeMs + " ms"); Slog.d(TAG, " Rx Time: " + rxTimeMs + " ms"); - Slog.d(TAG, " Idle Time: " + info.getControllerIdleTimeMillis() + " ms"); + Slog.d(TAG, " Idle Time: " + idleTimeMs + " ms"); } long totalScanTimeMs = 0; @@ -11418,9 +11449,25 @@ public class BatteryStatsImpl extends BatteryStats { long totalRxBytes = 0; final UidTraffic[] uidTraffic = info.getUidTraffic(); - final int numUids = uidTraffic != null ? uidTraffic.length : 0; - for (int i = 0; i < numUids; i++) { - final UidTraffic traffic = uidTraffic[i]; + final UidTraffic[] lastUidTraffic = mLastBluetoothActivityEnergyInfo.getUidTraffic(); + final ArrayList<UidTraffic> deltaTraffic = new ArrayList<>(); + int m = 0, n = 0; + for (; m < uidTraffic.length && n < lastUidTraffic.length; m++) { + final UidTraffic traffic = uidTraffic[m]; + final UidTraffic lastTraffic = lastUidTraffic[n]; + if (traffic.getUid() == lastTraffic.getUid()) { + deltaTraffic.add(new UidTraffic(traffic.getUid(), + traffic.getRxBytes() - lastTraffic.getRxBytes(), + traffic.getTxBytes() - lastTraffic.getTxBytes())); + n++; + } + } + for (; m < uidTraffic.length; m ++) { + deltaTraffic.add(uidTraffic[m]); + } + + for (int i = 0, j = 0; i < deltaTraffic.size(); i++) { + final UidTraffic traffic = deltaTraffic.get(i); // Add to the global counters. mNetworkByteActivityCounters[NETWORK_BT_RX_DATA].addCountLocked( @@ -11440,8 +11487,8 @@ public class BatteryStatsImpl extends BatteryStats { if ((totalTxBytes != 0 || totalRxBytes != 0) && (leftOverRxTimeMs != 0 || leftOverTxTimeMs != 0)) { - for (int i = 0; i < numUids; i++) { - final UidTraffic traffic = uidTraffic[i]; + for (int i = 0; i < deltaTraffic.size(); i++) { + final UidTraffic traffic = deltaTraffic.get(i); final Uid u = getUidStatsLocked(mapUid(traffic.getUid())); final ControllerActivityCounterImpl counter = @@ -11472,12 +11519,9 @@ public class BatteryStatsImpl extends BatteryStats { } } - mBluetoothActivity.getRxTimeCounter().addCountLocked( - info.getControllerRxTimeMillis()); - mBluetoothActivity.getTxTimeCounters()[0].addCountLocked( - info.getControllerTxTimeMillis()); - mBluetoothActivity.getIdleTimeCounter().addCountLocked( - info.getControllerIdleTimeMillis()); + mBluetoothActivity.getRxTimeCounter().addCountLocked(rxTimeMs); + mBluetoothActivity.getTxTimeCounters()[0].addCountLocked(txTimeMs); + mBluetoothActivity.getIdleTimeCounter().addCountLocked(idleTimeMs); // POWER_BLUETOOTH_CONTROLLER_OPERATING_VOLTAGE is measured in mV, so convert to V. final double opVolt = mPowerProfile.getAveragePower( @@ -11485,8 +11529,10 @@ public class BatteryStatsImpl extends BatteryStats { if (opVolt != 0) { // We store the power drain as mAms. mBluetoothActivity.getPowerCounter().addCountLocked( - (long) (info.getControllerEnergyUsed() / opVolt)); + (long) ((info.getControllerEnergyUsed() - + mLastBluetoothActivityEnergyInfo.getControllerEnergyUsed() )/ opVolt)); } + mLastBluetoothActivityEnergyInfo = info; } /** @@ -12106,11 +12152,14 @@ public class BatteryStatsImpl extends BatteryStats { // stats to be reported in the next checkin. Only do this if we have // a sufficient amount of data to make it interesting. if (getLowDischargeAmountSinceCharge() >= 20) { + final long startTime = SystemClock.uptimeMillis(); final Parcel parcel = Parcel.obtain(); writeSummaryToParcel(parcel, true); + final long initialTime = SystemClock.uptimeMillis() - startTime; BackgroundThread.getHandler().post(new Runnable() { @Override public void run() { synchronized (mCheckinFile) { + final long startTime2 = SystemClock.uptimeMillis(); FileOutputStream stream = null; try { stream = mCheckinFile.startWrite(); @@ -12119,6 +12168,9 @@ public class BatteryStatsImpl extends BatteryStats { FileUtils.sync(stream); stream.close(); mCheckinFile.finishWrite(stream); + com.android.internal.logging.EventLogTags.writeCommitSysConfigFile( + "batterystats-checkin", + initialTime + SystemClock.uptimeMillis() - startTime2); } catch (IOException e) { Slog.w("BatteryStats", "Error writing checkin battery statistics", e); @@ -13121,12 +13173,15 @@ public class BatteryStatsImpl extends BatteryStats { mWriteLock.lock(); try { + final long startTime = SystemClock.uptimeMillis(); FileOutputStream stream = new FileOutputStream(mFile.chooseForWrite()); stream.write(next.marshall()); stream.flush(); FileUtils.sync(stream); stream.close(); mFile.commit(); + com.android.internal.logging.EventLogTags.writeCommitSysConfigFile( + "batterystats", SystemClock.uptimeMillis() - startTime); } catch (IOException e) { Slog.w("BatteryStats", "Error writing battery statistics", e); mFile.rollback(); diff --git a/core/java/com/android/internal/util/CollectionUtils.java b/core/java/com/android/internal/util/CollectionUtils.java index 2f2c747d6509..433d14f7b453 100644 --- a/core/java/com/android/internal/util/CollectionUtils.java +++ b/core/java/com/android/internal/util/CollectionUtils.java @@ -138,14 +138,14 @@ public class CollectionUtils { public static @NonNull <I, O> List<O> mapNotNull(@Nullable List<I> cur, Function<? super I, ? extends O> f) { if (isEmpty(cur)) return Collections.emptyList(); - final ArrayList<O> result = new ArrayList<>(); + List<O> result = null; for (int i = 0; i < cur.size(); i++) { O transformed = f.apply(cur.get(i)); if (transformed != null) { - result.add(transformed); + result = add(result, transformed); } } - return result; + return emptyIfNull(result); } /** diff --git a/core/java/com/android/server/BootReceiver.java b/core/java/com/android/server/BootReceiver.java index 8848e3939008..fb186693979d 100644 --- a/core/java/com/android/server/BootReceiver.java +++ b/core/java/com/android/server/BootReceiver.java @@ -35,6 +35,7 @@ import android.text.TextUtils; import android.util.AtomicFile; import android.util.EventLog; import android.util.Slog; +import android.util.StatsLog; import android.util.Xml; import com.android.internal.annotations.VisibleForTesting; @@ -85,7 +86,7 @@ public class BootReceiver extends BroadcastReceiver { private static final String LOG_FILES_FILE = "log-files.xml"; private static final AtomicFile sFile = new AtomicFile(new File( - Environment.getDataSystemDirectory(), LOG_FILES_FILE)); + Environment.getDataSystemDirectory(), LOG_FILES_FILE), "log-files"); private static final String LAST_HEADER_FILE = "last-header.txt"; private static final File lastHeaderFile = new File( Environment.getDataSystemDirectory(), LAST_HEADER_FILE); @@ -112,6 +113,8 @@ public class BootReceiver extends BroadcastReceiver { private static final String SHUTDOWN_METRICS_FILE = "/data/system/shutdown-metrics.txt"; private static final String SHUTDOWN_TRON_METRICS_PREFIX = "shutdown_"; + private static final String METRIC_SYSTEM_SERVER = "shutdown_system_server"; + private static final String METRIC_SHUTDOWN_TIME_START = "begin_shutdown"; @Override public void onReceive(final Context context, Intent intent) { @@ -401,6 +404,10 @@ public class BootReceiver extends BroadcastReceiver { } } if (!TextUtils.isEmpty(metricsStr)) { + String reboot = null; + String reason = null; + String start_time = null; + String duration = null; String[] array = metricsStr.split(","); for (String keyValueStr : array) { String[] keyValue = keyValueStr.split(":"); @@ -411,8 +418,19 @@ public class BootReceiver extends BroadcastReceiver { // Ignore keys that are not indended for tron if (keyValue[0].startsWith(SHUTDOWN_TRON_METRICS_PREFIX)) { logTronShutdownMetric(keyValue[0], keyValue[1]); + if (keyValue[0].equals(METRIC_SYSTEM_SERVER)) { + duration = keyValue[1]; + } + } + if (keyValue[0].equals("reboot")) { + reboot = keyValue[1]; + } else if (keyValue[0].equals("reason")) { + reason = keyValue[1]; + } else if (keyValue[0].equals(METRIC_SHUTDOWN_TIME_START)) { + start_time = keyValue[1]; } } + logStatsdShutdownAtom(reboot, reason, start_time, duration); } metricsFile.delete(); } @@ -430,6 +448,52 @@ public class BootReceiver extends BroadcastReceiver { } } + private static void logStatsdShutdownAtom( + String rebootStr, String reasonStr, String startStr, String durationStr) { + boolean reboot = false; + String reason = "<EMPTY>"; + long start = 0; + long duration = 0; + + if (rebootStr != null) { + if (rebootStr.equals("y")) { + reboot = true; + } else if (!rebootStr.equals("n")) { + Slog.e(TAG, "Unexpected value for reboot : " + rebootStr); + } + } else { + Slog.e(TAG, "No value received for reboot"); + } + + if (reasonStr != null) { + reason = reasonStr; + } else { + Slog.e(TAG, "No value received for shutdown reason"); + } + + if (startStr != null) { + try { + start = Long.parseLong(startStr); + } catch (NumberFormatException e) { + Slog.e(TAG, "Cannot parse shutdown start time: " + startStr); + } + } else { + Slog.e(TAG, "No value received for shutdown start time"); + } + + if (durationStr != null) { + try { + duration = Long.parseLong(durationStr); + } catch (NumberFormatException e) { + Slog.e(TAG, "Cannot parse shutdown duration: " + startStr); + } + } else { + Slog.e(TAG, "No value received for shutdown duration"); + } + + StatsLog.write(StatsLog.SHUTDOWN_SEQUENCE_REPORTED, reboot, reason, start, duration); + } + private static void logFsShutdownTime() { File f = null; for (String fileName : LAST_KMSG_FILES) { diff --git a/core/jni/android/graphics/AnimatedImageDrawable.cpp b/core/jni/android/graphics/AnimatedImageDrawable.cpp index 0e562c03b762..c88cf5cff04d 100644 --- a/core/jni/android/graphics/AnimatedImageDrawable.cpp +++ b/core/jni/android/graphics/AnimatedImageDrawable.cpp @@ -16,16 +16,16 @@ #include "GraphicsJNI.h" #include "ImageDecoder.h" -#include "core_jni_helpers.h" #include "Utils.h" +#include "core_jni_helpers.h" -#include <hwui/AnimatedImageDrawable.h> -#include <hwui/Canvas.h> #include <SkAndroidCodec.h> #include <SkAnimatedImage.h> #include <SkColorFilter.h> #include <SkPicture.h> #include <SkPictureRecorder.h> +#include <hwui/AnimatedImageDrawable.h> +#include <hwui/Canvas.h> using namespace android; @@ -85,6 +85,9 @@ static jlong AnimatedImageDrawable_nGetNativeFinalizer(JNIEnv* /*env*/, jobject return static_cast<jlong>(reinterpret_cast<uintptr_t>(&AnimatedImageDrawable_destruct)); } +// Java's FINISHED relies on this being -1 +static_assert(SkAnimatedImage::kFinished == -1); + static jlong AnimatedImageDrawable_nDraw(JNIEnv* env, jobject /*clazz*/, jlong nativePtr, jlong canvasPtr) { auto* drawable = reinterpret_cast<AnimatedImageDrawable*>(nativePtr); @@ -115,9 +118,6 @@ static jboolean AnimatedImageDrawable_nIsRunning(JNIEnv* env, jobject /*clazz*/, return drawable->isRunning(); } -// Java's NOT_RUNNING relies on this being -2.0. -static_assert(SkAnimatedImage::kNotRunning == -2.0); - static jboolean AnimatedImageDrawable_nStart(JNIEnv* env, jobject /*clazz*/, jlong nativePtr) { auto* drawable = reinterpret_cast<AnimatedImageDrawable*>(nativePtr); return drawable->start(); @@ -172,6 +172,11 @@ static long AnimatedImageDrawable_nNativeByteSize(JNIEnv* env, jobject /*clazz*/ return sizeof(drawable); } +static void AnimatedImageDrawable_nMarkInvisible(JNIEnv* env, jobject /*clazz*/, jlong nativePtr) { + auto* drawable = reinterpret_cast<AnimatedImageDrawable*>(nativePtr); + drawable->markInvisible(); +} + static const JNINativeMethod gAnimatedImageDrawableMethods[] = { { "nCreate", "(JLandroid/graphics/ImageDecoder;IILandroid/graphics/Rect;)J", (void*) AnimatedImageDrawable_nCreate }, { "nGetNativeFinalizer", "()J", (void*) AnimatedImageDrawable_nGetNativeFinalizer }, @@ -185,6 +190,7 @@ static const JNINativeMethod gAnimatedImageDrawableMethods[] = { { "nSetLoopCount", "(JI)V", (void*) AnimatedImageDrawable_nSetLoopCount }, { "nSetOnAnimationEndListener", "(JLandroid/graphics/drawable/AnimatedImageDrawable;)V", (void*) AnimatedImageDrawable_nSetOnAnimationEndListener }, { "nNativeByteSize", "(J)J", (void*) AnimatedImageDrawable_nNativeByteSize }, + { "nMarkInvisible", "(J)V", (void*) AnimatedImageDrawable_nMarkInvisible }, }; int register_android_graphics_drawable_AnimatedImageDrawable(JNIEnv* env) { diff --git a/core/jni/android/graphics/ImageDecoder.cpp b/core/jni/android/graphics/ImageDecoder.cpp index 743a7efa2b24..e2ce1a465fd7 100644 --- a/core/jni/android/graphics/ImageDecoder.cpp +++ b/core/jni/android/graphics/ImageDecoder.cpp @@ -74,7 +74,8 @@ static jobject native_create(JNIEnv* env, std::unique_ptr<SkStream> stream) { // FIXME: Avoid parsing the whole image? const bool animated = codec->getFrameCount() > 1; - decoder->mCodec = SkAndroidCodec::MakeFromCodec(std::move(codec)); + decoder->mCodec = SkAndroidCodec::MakeFromCodec(std::move(codec), + SkAndroidCodec::ExifOrientationBehavior::kRespect); if (!decoder->mCodec.get()) { doThrowIOE(env, "Could not create AndroidCodec"); return nullptr; diff --git a/core/jni/android/graphics/Paint.cpp b/core/jni/android/graphics/Paint.cpp index 49cbb545b019..115d0d5a608b 100644 --- a/core/jni/android/graphics/Paint.cpp +++ b/core/jni/android/graphics/Paint.cpp @@ -24,6 +24,7 @@ #include "core_jni_helpers.h" #include <nativehelper/ScopedStringChars.h> #include <nativehelper/ScopedUtfChars.h> +#include <nativehelper/ScopedPrimitiveArray.h> #include "SkBlurDrawLooper.h" #include "SkColorFilter.h" @@ -515,11 +516,10 @@ namespace PaintGlue { jint start, jint end, jint contextStart, jint contextEnd, jboolean isRtl, jint offset) { const Paint* paint = reinterpret_cast<Paint*>(paintHandle); const Typeface* typeface = paint->getAndroidTypeface(); - jchar* textArray = (jchar*) env->GetPrimitiveArrayCritical(text, nullptr); - jfloat result = doRunAdvance(paint, typeface, textArray + contextStart, + ScopedCharArrayRO textArray(env, text); + jfloat result = doRunAdvance(paint, typeface, textArray.get() + contextStart, start - contextStart, end - start, contextEnd - contextStart, isRtl, offset - contextStart); - env->ReleasePrimitiveArrayCritical(text, textArray, JNI_ABORT); return result; } @@ -537,11 +537,10 @@ namespace PaintGlue { jboolean isRtl, jfloat advance) { const Paint* paint = reinterpret_cast<Paint*>(paintHandle); const Typeface* typeface = paint->getAndroidTypeface(); - jchar* textArray = (jchar*) env->GetPrimitiveArrayCritical(text, nullptr); - jint result = doOffsetForAdvance(paint, typeface, textArray + contextStart, + ScopedCharArrayRO textArray(env, text); + jint result = doOffsetForAdvance(paint, typeface, textArray.get() + contextStart, start - contextStart, end - start, contextEnd - contextStart, isRtl, advance); result += contextStart; - env->ReleasePrimitiveArrayCritical(text, textArray, JNI_ABORT); return result; } diff --git a/core/jni/android_database_CursorWindow.cpp b/core/jni/android_database_CursorWindow.cpp index 0d09b56a2935..86cda4455734 100644 --- a/core/jni/android_database_CursorWindow.cpp +++ b/core/jni/android_database_CursorWindow.cpp @@ -519,8 +519,21 @@ static const JNINativeMethod sMethods[] = (void*)nativeDispose }, { "nativeWriteToParcel", "(JLandroid/os/Parcel;)V", (void*)nativeWriteToParcel }, + { "nativeGetName", "(J)Ljava/lang/String;", (void*)nativeGetName }, + { "nativeGetBlob", "(JII)[B", + (void*)nativeGetBlob }, + { "nativeGetString", "(JII)Ljava/lang/String;", + (void*)nativeGetString }, + { "nativeCopyStringToBuffer", "(JIILandroid/database/CharArrayBuffer;)V", + (void*)nativeCopyStringToBuffer }, + { "nativePutBlob", "(J[BII)Z", + (void*)nativePutBlob }, + { "nativePutString", "(JLjava/lang/String;II)Z", + (void*)nativePutString }, + + // ------- @FastNative below here ---------------------- { "nativeClear", "(J)V", (void*)nativeClear }, { "nativeGetNumRows", "(J)I", @@ -533,20 +546,11 @@ static const JNINativeMethod sMethods[] = (void*)nativeFreeLastRow }, { "nativeGetType", "(JII)I", (void*)nativeGetType }, - { "nativeGetBlob", "(JII)[B", - (void*)nativeGetBlob }, - { "nativeGetString", "(JII)Ljava/lang/String;", - (void*)nativeGetString }, { "nativeGetLong", "(JII)J", (void*)nativeGetLong }, { "nativeGetDouble", "(JII)D", (void*)nativeGetDouble }, - { "nativeCopyStringToBuffer", "(JIILandroid/database/CharArrayBuffer;)V", - (void*)nativeCopyStringToBuffer }, - { "nativePutBlob", "(J[BII)Z", - (void*)nativePutBlob }, - { "nativePutString", "(JLjava/lang/String;II)Z", - (void*)nativePutString }, + { "nativePutLong", "(JJII)Z", (void*)nativePutLong }, { "nativePutDouble", "(JDII)Z", diff --git a/core/jni/android_os_Debug.cpp b/core/jni/android_os_Debug.cpp index b9ff0a787931..bf183cc8c0aa 100644 --- a/core/jni/android_os_Debug.cpp +++ b/core/jni/android_os_Debug.cpp @@ -107,6 +107,7 @@ enum { struct stat_fields { jfieldID pss_field; jfieldID pssSwappable_field; + jfieldID rss_field; jfieldID privateDirty_field; jfieldID sharedDirty_field; jfieldID privateClean_field; @@ -118,6 +119,7 @@ struct stat_fields { struct stat_field_names { const char* pss_name; const char* pssSwappable_name; + const char* rss_name; const char* privateDirty_name; const char* sharedDirty_name; const char* privateClean_name; @@ -129,11 +131,11 @@ struct stat_field_names { static stat_fields stat_fields[_NUM_CORE_HEAP]; static stat_field_names stat_field_names[_NUM_CORE_HEAP] = { - { "otherPss", "otherSwappablePss", "otherPrivateDirty", "otherSharedDirty", + { "otherPss", "otherSwappablePss", "otherRss", "otherPrivateDirty", "otherSharedDirty", "otherPrivateClean", "otherSharedClean", "otherSwappedOut", "otherSwappedOutPss" }, - { "dalvikPss", "dalvikSwappablePss", "dalvikPrivateDirty", "dalvikSharedDirty", + { "dalvikPss", "dalvikSwappablePss", "dalvikRss", "dalvikPrivateDirty", "dalvikSharedDirty", "dalvikPrivateClean", "dalvikSharedClean", "dalvikSwappedOut", "dalvikSwappedOutPss" }, - { "nativePss", "nativeSwappablePss", "nativePrivateDirty", "nativeSharedDirty", + { "nativePss", "nativeSwappablePss", "nativeRss", "nativePrivateDirty", "nativeSharedDirty", "nativePrivateClean", "nativeSharedClean", "nativeSwappedOut", "nativeSwappedOutPss" } }; @@ -143,6 +145,7 @@ jfieldID hasSwappedOutPss_field; struct stats_t { int pss; int swappablePss; + int rss; int privateDirty; int sharedDirty; int privateClean; @@ -246,7 +249,7 @@ static void read_mapinfo(FILE *fp, stats_t* stats, bool* foundSwapPss) int len, nameLen; bool skip, done = false; - unsigned pss = 0, swappable_pss = 0; + unsigned pss = 0, swappable_pss = 0, rss = 0; float sharing_proportion = 0.0; unsigned shared_clean = 0, shared_dirty = 0; unsigned private_clean = 0, private_dirty = 0; @@ -412,7 +415,7 @@ static void read_mapinfo(FILE *fp, stats_t* stats, bool* foundSwapPss) if (line[0] == 'S' && sscanf(line, "Size: %d kB", &temp) == 1) { /* size = temp; */ } else if (line[0] == 'R' && sscanf(line, "Rss: %d kB", &temp) == 1) { - /* resident = temp; */ + rss = temp; } else if (line[0] == 'P' && sscanf(line, "Pss: %d kB", &temp) == 1) { pss = temp; } else if (line[0] == 'S' && sscanf(line, "Shared_Clean: %d kB", &temp) == 1) { @@ -450,6 +453,7 @@ static void read_mapinfo(FILE *fp, stats_t* stats, bool* foundSwapPss) stats[whichHeap].pss += pss; stats[whichHeap].swappablePss += swappable_pss; + stats[whichHeap].rss += rss; stats[whichHeap].privateDirty += private_dirty; stats[whichHeap].sharedDirty += shared_dirty; stats[whichHeap].privateClean += private_clean; @@ -460,6 +464,7 @@ static void read_mapinfo(FILE *fp, stats_t* stats, bool* foundSwapPss) whichHeap == HEAP_DEX || whichHeap == HEAP_ART) { stats[subHeap].pss += pss; stats[subHeap].swappablePss += swappable_pss; + stats[subHeap].rss += rss; stats[subHeap].privateDirty += private_dirty; stats[subHeap].sharedDirty += shared_dirty; stats[subHeap].privateClean += private_clean; @@ -495,15 +500,19 @@ static void android_os_Debug_getDirtyPagesPid(JNIEnv *env, jobject clazz, if (read_memtrack_memory(pid, &graphics_mem) == 0) { stats[HEAP_GRAPHICS].pss = graphics_mem.graphics; stats[HEAP_GRAPHICS].privateDirty = graphics_mem.graphics; + stats[HEAP_GRAPHICS].rss = graphics_mem.graphics; stats[HEAP_GL].pss = graphics_mem.gl; stats[HEAP_GL].privateDirty = graphics_mem.gl; + stats[HEAP_GL].rss = graphics_mem.gl; stats[HEAP_OTHER_MEMTRACK].pss = graphics_mem.other; stats[HEAP_OTHER_MEMTRACK].privateDirty = graphics_mem.other; + stats[HEAP_OTHER_MEMTRACK].rss = graphics_mem.other; } for (int i=_NUM_CORE_HEAP; i<_NUM_EXCLUSIVE_HEAP; i++) { stats[HEAP_UNKNOWN].pss += stats[i].pss; stats[HEAP_UNKNOWN].swappablePss += stats[i].swappablePss; + stats[HEAP_UNKNOWN].rss += stats[i].rss; stats[HEAP_UNKNOWN].privateDirty += stats[i].privateDirty; stats[HEAP_UNKNOWN].sharedDirty += stats[i].sharedDirty; stats[HEAP_UNKNOWN].privateClean += stats[i].privateClean; @@ -515,6 +524,7 @@ static void android_os_Debug_getDirtyPagesPid(JNIEnv *env, jobject clazz, for (int i=0; i<_NUM_CORE_HEAP; i++) { env->SetIntField(object, stat_fields[i].pss_field, stats[i].pss); env->SetIntField(object, stat_fields[i].pssSwappable_field, stats[i].swappablePss); + env->SetIntField(object, stat_fields[i].rss_field, stats[i].rss); env->SetIntField(object, stat_fields[i].privateDirty_field, stats[i].privateDirty); env->SetIntField(object, stat_fields[i].sharedDirty_field, stats[i].sharedDirty); env->SetIntField(object, stat_fields[i].privateClean_field, stats[i].privateClean); @@ -536,6 +546,7 @@ static void android_os_Debug_getDirtyPagesPid(JNIEnv *env, jobject clazz, for (int i=_NUM_CORE_HEAP; i<_NUM_HEAP; i++) { otherArray[j++] = stats[i].pss; otherArray[j++] = stats[i].swappablePss; + otherArray[j++] = stats[i].rss; otherArray[j++] = stats[i].privateDirty; otherArray[j++] = stats[i].sharedDirty; otherArray[j++] = stats[i].privateClean; @@ -580,10 +591,11 @@ UniqueFile OpenSmapsOrRollup(int pid) } static jlong android_os_Debug_getPssPid(JNIEnv *env, jobject clazz, jint pid, - jlongArray outUssSwapPss, jlongArray outMemtrack) + jlongArray outUssSwapPssRss, jlongArray outMemtrack) { - char line[1024]; + char lineBuffer[1024]; jlong pss = 0; + jlong rss = 0; jlong swapPss = 0; jlong uss = 0; jlong memtrack = 0; @@ -597,50 +609,70 @@ static jlong android_os_Debug_getPssPid(JNIEnv *env, jobject clazz, jint pid, UniqueFile fp = OpenSmapsOrRollup(pid); if (fp != nullptr) { + char* line; + while (true) { - if (fgets(line, sizeof (line), fp.get()) == NULL) { + if (fgets(lineBuffer, sizeof (lineBuffer), fp.get()) == NULL) { break; } - - if (line[0] == 'P') { - if (strncmp(line, "Pss:", 4) == 0) { - char* c = line + 4; - while (*c != 0 && (*c < '0' || *c > '9')) { - c++; + line = lineBuffer; + + switch (line[0]) { + case 'P': + if (strncmp(line, "Pss:", 4) == 0) { + char* c = line + 4; + while (*c != 0 && (*c < '0' || *c > '9')) { + c++; + } + pss += atoi(c); + } else if (strncmp(line, "Private_Clean:", 14) == 0 + || strncmp(line, "Private_Dirty:", 14) == 0) { + char* c = line + 14; + while (*c != 0 && (*c < '0' || *c > '9')) { + c++; + } + uss += atoi(c); } - pss += atoi(c); - } else if (strncmp(line, "Private_Clean:", 14) == 0 - || strncmp(line, "Private_Dirty:", 14) == 0) { - char* c = line + 14; - while (*c != 0 && (*c < '0' || *c > '9')) { - c++; + break; + case 'R': + if (strncmp(line, "Rss:", 4) == 0) { + char* c = line + 4; + while (*c != 0 && (*c < '0' || *c > '9')) { + c++; + } + rss += atoi(c); } - uss += atoi(c); - } - } else if (line[0] == 'S' && strncmp(line, "SwapPss:", 8) == 0) { - char* c = line + 8; - jlong lSwapPss; - while (*c != 0 && (*c < '0' || *c > '9')) { - c++; - } - lSwapPss = atoi(c); - swapPss += lSwapPss; - pss += lSwapPss; // Also in swap, those pages would be accounted as Pss without SWAP + break; + case 'S': + if (strncmp(line, "SwapPss:", 8) == 0) { + char* c = line + 8; + jlong lSwapPss; + while (*c != 0 && (*c < '0' || *c > '9')) { + c++; + } + lSwapPss = atoi(c); + swapPss += lSwapPss; + pss += lSwapPss; // Also in swap, those pages would be accounted as Pss without SWAP + } + break; } } } } - if (outUssSwapPss != NULL) { - if (env->GetArrayLength(outUssSwapPss) >= 1) { - jlong* outUssSwapPssArray = env->GetLongArrayElements(outUssSwapPss, 0); - if (outUssSwapPssArray != NULL) { - outUssSwapPssArray[0] = uss; - if (env->GetArrayLength(outUssSwapPss) >= 2) { - outUssSwapPssArray[1] = swapPss; + if (outUssSwapPssRss != NULL) { + if (env->GetArrayLength(outUssSwapPssRss) >= 1) { + jlong* outUssSwapPssRssArray = env->GetLongArrayElements(outUssSwapPssRss, 0); + if (outUssSwapPssRssArray != NULL) { + outUssSwapPssRssArray[0] = uss; + if (env->GetArrayLength(outUssSwapPssRss) >= 2) { + outUssSwapPssRssArray[1] = swapPss; + } + if (env->GetArrayLength(outUssSwapPssRss) >= 3) { + outUssSwapPssRssArray[2] = rss; } } - env->ReleaseLongArrayElements(outUssSwapPss, outUssSwapPssArray, 0); + env->ReleaseLongArrayElements(outUssSwapPssRss, outUssSwapPssRssArray, 0); } } @@ -1209,6 +1241,8 @@ int register_android_os_Debug(JNIEnv *env) env->GetFieldID(clazz, stat_field_names[i].pss_name, "I"); stat_fields[i].pssSwappable_field = env->GetFieldID(clazz, stat_field_names[i].pssSwappable_name, "I"); + stat_fields[i].rss_field = + env->GetFieldID(clazz, stat_field_names[i].rss_name, "I"); stat_fields[i].privateDirty_field = env->GetFieldID(clazz, stat_field_names[i].privateDirty_name, "I"); stat_fields[i].sharedDirty_field = diff --git a/core/jni/android_text_MeasuredParagraph.cpp b/core/jni/android_text_MeasuredParagraph.cpp index f0e449d52998..d33337dabae0 100644 --- a/core/jni/android_text_MeasuredParagraph.cpp +++ b/core/jni/android_text_MeasuredParagraph.cpp @@ -72,8 +72,7 @@ static void nAddStyleRun(JNIEnv* /* unused */, jclass /* unused */, jlong builde Paint* paint = toPaint(paintPtr); const Typeface* typeface = Typeface::resolveDefault(paint->getAndroidTypeface()); minikin::MinikinPaint minikinPaint = MinikinUtils::prepareMinikinPaint(paint, typeface); - toBuilder(builderPtr)->addStyleRun(start, end, std::move(minikinPaint), - typeface->fFontCollection, isRtl); + toBuilder(builderPtr)->addStyleRun(start, end, std::move(minikinPaint), isRtl); } // Regular JNI @@ -115,6 +114,10 @@ static jlong nGetReleaseFunc() { return toJLong(&releaseMeasuredParagraph); } +static jint nGetMemoryUsage(jlong ptr) { + return static_cast<jint>(toMeasuredParagraph(ptr)->getMemoryUsage()); +} + static const JNINativeMethod gMethods[] = { // MeasuredParagraphBuilder native functions. {"nInitBuilder", "()J", (void*) nInitBuilder}, @@ -126,6 +129,7 @@ static const JNINativeMethod gMethods[] = { // MeasuredParagraph native functions. {"nGetWidth", "(JII)F", (void*) nGetWidth}, // Critical Natives {"nGetReleaseFunc", "()J", (void*) nGetReleaseFunc}, // Critical Natives + {"nGetMemoryUsage", "(J)I", (void*) nGetMemoryUsage}, // Critical Native }; int register_android_text_MeasuredParagraph(JNIEnv* env) { diff --git a/core/jni/android_util_Process.cpp b/core/jni/android_util_Process.cpp index dec6c0226baa..62aa1f38ca30 100644 --- a/core/jni/android_util_Process.cpp +++ b/core/jni/android_util_Process.cpp @@ -610,7 +610,7 @@ static jlong getFreeMemoryImpl(const char* const sums[], const size_t sumsLen[], return -1; } - char buffer[256]; + char buffer[2048]; const int len = read(fd, buffer, sizeof(buffer)-1); close(fd); @@ -719,7 +719,7 @@ void android_os_Process_readProcLines(JNIEnv* env, jobject clazz, jstring fileSt int fd = open(file.string(), O_RDONLY | O_CLOEXEC); if (fd >= 0) { - const size_t BUFFER_SIZE = 2048; + const size_t BUFFER_SIZE = 4096; char* buffer = (char*)malloc(BUFFER_SIZE); int len = read(fd, buffer, BUFFER_SIZE-1); close(fd); diff --git a/core/jni/android_view_Surface.cpp b/core/jni/android_view_Surface.cpp index 421e0de52cc0..f5c09fd316de 100644 --- a/core/jni/android_view_Surface.cpp +++ b/core/jni/android_view_Surface.cpp @@ -524,15 +524,15 @@ static jint nativeAttachAndQueueBuffer(JNIEnv *env, jclass clazz, jlong nativeOb static jint nativeSetSharedBufferModeEnabled(JNIEnv* env, jclass clazz, jlong nativeObject, jboolean enabled) { Surface* surface = reinterpret_cast<Surface*>(nativeObject); - return ((ANativeWindow*) nativeObject)->perform(surface, - NATIVE_WINDOW_SET_SHARED_BUFFER_MODE, enabled); + ANativeWindow* anw = static_cast<ANativeWindow*>(surface); + return anw->perform(surface, NATIVE_WINDOW_SET_SHARED_BUFFER_MODE, int(enabled)); } static jint nativeSetAutoRefreshEnabled(JNIEnv* env, jclass clazz, jlong nativeObject, jboolean enabled) { Surface* surface = reinterpret_cast<Surface*>(nativeObject); - return ((ANativeWindow*) nativeObject)->perform(surface, - NATIVE_WINDOW_SET_AUTO_REFRESH, enabled); + ANativeWindow* anw = static_cast<ANativeWindow*>(surface); + return anw->perform(surface, NATIVE_WINDOW_SET_AUTO_REFRESH, int(enabled)); } namespace uirenderer { diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp index 13e6fcdc8426..32945bfbceb1 100644 --- a/core/jni/com_android_internal_os_Zygote.cpp +++ b/core/jni/com_android_internal_os_Zygote.cpp @@ -141,32 +141,45 @@ static void SigChldHandler(int /*signal_number*/) { errno = saved_errno; } -// Configures the SIGCHLD handler for the zygote process. This is configured -// very late, because earlier in the runtime we may fork() and exec() -// other processes, and we want to waitpid() for those rather than +// Configures the SIGCHLD/SIGHUP handlers for the zygote process. This is +// configured very late, because earlier in the runtime we may fork() and +// exec() other processes, and we want to waitpid() for those rather than // have them be harvested immediately. // +// Ignore SIGHUP because all processes forked by the zygote are in the same +// process group as the zygote and we don't want to be notified if we become +// an orphaned group and have one or more stopped processes. This is not a +// theoretical concern : +// - we can become an orphaned group if one of our direct descendants forks +// and is subsequently killed before its children. +// - crash_dump routinely STOPs the process it's tracing. +// +// See issues b/71965619 and b/25567761 for further details. +// // This ends up being called repeatedly before each fork(), but there's // no real harm in that. -static void SetSigChldHandler() { - struct sigaction sa; - memset(&sa, 0, sizeof(sa)); - sa.sa_handler = SigChldHandler; +static void SetSignalHandlers() { + struct sigaction sig_chld = {}; + sig_chld.sa_handler = SigChldHandler; - int err = sigaction(SIGCHLD, &sa, NULL); - if (err < 0) { + if (sigaction(SIGCHLD, &sig_chld, NULL) < 0) { ALOGW("Error setting SIGCHLD handler: %s", strerror(errno)); } + + struct sigaction sig_hup = {}; + sig_hup.sa_handler = SIG_IGN; + if (sigaction(SIGHUP, &sig_hup, NULL) < 0) { + ALOGW("Error setting SIGHUP handler: %s", strerror(errno)); + } } // Sets the SIGCHLD handler back to default behavior in zygote children. -static void UnsetSigChldHandler() { +static void UnsetChldSignalHandler() { struct sigaction sa; memset(&sa, 0, sizeof(sa)); sa.sa_handler = SIG_DFL; - int err = sigaction(SIGCHLD, &sa, NULL); - if (err < 0) { + if (sigaction(SIGCHLD, &sa, NULL) < 0) { ALOGW("Error unsetting SIGCHLD handler: %s", strerror(errno)); } } @@ -505,7 +518,7 @@ static pid_t ForkAndSpecializeCommon(JNIEnv* env, uid_t uid, gid_t gid, jintArra bool is_system_server, jintArray fdsToClose, jintArray fdsToIgnore, jstring instructionSet, jstring dataDir) { - SetSigChldHandler(); + SetSignalHandlers(); sigset_t sigchld; sigemptyset(&sigchld); @@ -682,7 +695,8 @@ static pid_t ForkAndSpecializeCommon(JNIEnv* env, uid_t uid, gid_t gid, jintArra delete se_info; delete se_name; - UnsetSigChldHandler(); + // Unset the SIGCHLD handler, but keep ignoring SIGHUP (rationale in SetSignalHandlers). + UnsetChldSignalHandler(); env->CallStaticVoidMethod(gZygoteClass, gCallPostForkChildHooks, runtime_flags, is_system_server, instructionSet); diff --git a/core/proto/android/app/window_configuration.proto b/core/proto/android/app/window_configuration.proto index 4d748e8fb1da..1e8ace4fa06b 100644 --- a/core/proto/android/app/window_configuration.proto +++ b/core/proto/android/app/window_configuration.proto @@ -21,9 +21,12 @@ option java_multiple_files = true; package android.app; import "frameworks/base/core/proto/android/graphics/rect.proto"; +import "frameworks/base/libs/incident/proto/android/privacy.proto"; /** Proto representation for WindowConfiguration.java class. */ message WindowConfigurationProto { + option (android.msg_privacy).dest = DEST_AUTOMATIC; + optional .android.graphics.RectProto app_bounds = 1; optional int32 windowing_mode = 2; optional int32 activity_type = 3; diff --git a/core/proto/android/media/audioattributes.proto b/core/proto/android/media/audioattributes.proto index 860d60807c0e..ef04720405f5 100644 --- a/core/proto/android/media/audioattributes.proto +++ b/core/proto/android/media/audioattributes.proto @@ -20,15 +20,19 @@ option java_multiple_files = true; package android.media; +import "frameworks/base/libs/incident/proto/android/privacy.proto"; + /** * An android.media.AudioAttributes object. */ message AudioAttributesProto { + option (android.msg_privacy).dest = DEST_AUTOMATIC; + optional Usage usage = 1; optional ContentType content_type = 2; // Bit representation of set flags. optional int32 flags = 3; - repeated string tags = 4; + repeated string tags = 4 [ (android.privacy).dest = DEST_EXPLICIT ]; } enum ContentType { diff --git a/core/proto/android/os/incident.proto b/core/proto/android/os/incident.proto index 3ec6f057af88..8a04bf7253e4 100644 --- a/core/proto/android/os/incident.proto +++ b/core/proto/android/os/incident.proto @@ -16,7 +16,6 @@ syntax = "proto2"; option java_multiple_files = true; -option java_outer_classname = "IncidentProtoMetadata"; import "frameworks/base/core/proto/android/os/batterytype.proto"; import "frameworks/base/core/proto/android/os/cpufreq.proto"; diff --git a/core/proto/android/server/forceappstandbytracker.proto b/core/proto/android/server/forceappstandbytracker.proto index d0fd0b4bbc4b..43c869c76705 100644 --- a/core/proto/android/server/forceappstandbytracker.proto +++ b/core/proto/android/server/forceappstandbytracker.proto @@ -17,13 +17,12 @@ syntax = "proto2"; import "frameworks/base/core/proto/android/server/statlogger.proto"; +import "frameworks/base/libs/incident/proto/android/privacy.proto"; package com.android.server; option java_multiple_files = true; -import "frameworks/base/libs/incident/proto/android/privacy.proto"; - // Dump from com.android.server.ForceAppStandbyTracker. message ForceAppStandbyTrackerProto { option (.android.msg_privacy).dest = DEST_AUTOMATIC; @@ -62,6 +61,8 @@ message ForceAppStandbyTrackerProto { optional StatLoggerProto stats = 9; message ExemptedPackage { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + optional int32 userId = 1; optional string package_name = 2; } diff --git a/core/proto/android/server/intentresolver.proto b/core/proto/android/server/intentresolver.proto index 60c060c02b1b..0ada89533cb4 100644 --- a/core/proto/android/server/intentresolver.proto +++ b/core/proto/android/server/intentresolver.proto @@ -19,9 +19,15 @@ option java_multiple_files = true; package com.android.server; +import "frameworks/base/libs/incident/proto/android/privacy.proto"; + message IntentResolverProto { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + message ArrayMapEntry { + option (.android.msg_privacy).dest = DEST_EXPLICIT; + optional string key = 1; repeated string values = 2; } diff --git a/core/proto/android/server/statlogger.proto b/core/proto/android/server/statlogger.proto index fa430d8264d0..2ae526a59966 100644 --- a/core/proto/android/server/statlogger.proto +++ b/core/proto/android/server/statlogger.proto @@ -20,8 +20,12 @@ package com.android.server; option java_multiple_files = true; +import "frameworks/base/libs/incident/proto/android/privacy.proto"; + // Dump from StatLogger. message StatLoggerProto { + option (.android.msg_privacy).dest = DEST_EXPLICIT; + message Event { optional int32 eventId = 1; optional string label = 2; diff --git a/core/proto/android/server/wirelesschargerdetector.proto b/core/proto/android/server/wirelesschargerdetector.proto index 89cf2f8900fe..2118deb6edeb 100644 --- a/core/proto/android/server/wirelesschargerdetector.proto +++ b/core/proto/android/server/wirelesschargerdetector.proto @@ -19,8 +19,14 @@ package com.android.server.power; option java_multiple_files = true; +import "frameworks/base/libs/incident/proto/android/privacy.proto"; + message WirelessChargerDetectorProto { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + message VectorProto { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + optional float x = 1; optional float y = 2; optional float z = 3; diff --git a/core/proto/android/service/notification.proto b/core/proto/android/service/notification.proto index 9013a23664f6..5c40e5fe346f 100644 --- a/core/proto/android/service/notification.proto +++ b/core/proto/android/service/notification.proto @@ -50,7 +50,7 @@ message NotificationServiceDumpProto { message NotificationRecordProto { option (.android.msg_privacy).dest = DEST_AUTOMATIC; - optional string key = 1 [ (.android.privacy).dest = DEST_EXPLICIT ]; + optional string key = 1; enum State { ENQUEUED = 0; @@ -88,7 +88,7 @@ message ManagedServiceInfoProto { message ManagedServicesProto { option (android.msg_privacy).dest = DEST_AUTOMATIC; - optional string caption = 1 [ (.android.privacy).dest = DEST_EXPLICIT ]; + optional string caption = 1; message ServiceProto { option (android.msg_privacy).dest = DEST_AUTOMATIC; diff --git a/core/proto/android/service/procstats.proto b/core/proto/android/service/procstats.proto index 4c11f1eb0220..15ede0c5e192 100644 --- a/core/proto/android/service/procstats.proto +++ b/core/proto/android/service/procstats.proto @@ -169,6 +169,9 @@ message ProcessStatsProto { // USS is memory shared between processes, divided evenly for accounting optional android.util.AggStats uss = 7; + + // RSS is memory resident for this process + optional android.util.AggStats rss = 8; } repeated State states = 5; } diff --git a/core/proto/android/util/event_log_tags.proto b/core/proto/android/util/event_log_tags.proto index cb039be55b85..457219fde835 100644 --- a/core/proto/android/util/event_log_tags.proto +++ b/core/proto/android/util/event_log_tags.proto @@ -19,17 +19,25 @@ package android.util; option java_multiple_files = true; +import "frameworks/base/libs/incident/proto/android/privacy.proto"; + // Proto representation of event.logtags. // Usually sit in /system/etc/event-log-tags. message EventLogTagMapProto { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + repeated EventLogTag event_log_tags = 1; } message EventLogTag { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + optional uint32 tag_number = 1; // keyed by tag number. optional string tag_name = 2; message ValueDescriptor { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + optional string name = 1; enum DataType { @@ -55,4 +63,4 @@ message EventLogTag { optional DataUnit unit = 3; } repeated ValueDescriptor value_descriptors = 3; -}
\ No newline at end of file +} diff --git a/core/proto/android/view/displaycutout.proto b/core/proto/android/view/displaycutout.proto index ff13fab35bf2..ee258b7369a0 100644 --- a/core/proto/android/view/displaycutout.proto +++ b/core/proto/android/view/displaycutout.proto @@ -17,11 +17,14 @@ syntax = "proto2"; import "frameworks/base/core/proto/android/graphics/rect.proto"; +import "frameworks/base/libs/incident/proto/android/privacy.proto"; package android.view; option java_multiple_files = true; message DisplayCutoutProto { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + optional .android.graphics.RectProto insets = 1; optional .android.graphics.RectProto bounds = 2; } diff --git a/core/proto/android/view/surfacecontrol.proto b/core/proto/android/view/surfacecontrol.proto index 9288b4fc3671..665d688b05ac 100644 --- a/core/proto/android/view/surfacecontrol.proto +++ b/core/proto/android/view/surfacecontrol.proto @@ -19,10 +19,14 @@ package android.view; option java_multiple_files = true; +import "frameworks/base/libs/incident/proto/android/privacy.proto"; + /** * Represents a {@link android.view.SurfaceControl} object. */ message SurfaceControlProto { + option (android.msg_privacy).dest = DEST_AUTOMATIC; + optional int32 hash_code = 1; - optional string name = 2; + optional string name = 2 [ (android.privacy).dest = DEST_EXPLICIT ]; } diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 2779cd6846d4..e5ba6d76578d 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -1457,6 +1457,12 @@ android:label="@string/permlab_nfc" android:protectionLevel="normal" /> + <!-- Allows applications to receive NFC transaction events. + <p>Protection level: normal + --> + <permission android:name="android.permission.NFC_TRANSACTION_EVENT" + android:protectionLevel="normal" /> + <!-- @SystemApi Allows an internal user to use privileged ConnectivityManager APIs. @hide --> <permission android:name="android.permission.CONNECTIVITY_INTERNAL" @@ -1953,12 +1959,6 @@ <permission android:name="android.permission.START_ANY_ACTIVITY" android:protectionLevel="signature" /> - <!-- Allows an application to start an activity as another app, provided that app has been - granted a permissionToken from the ActivityManagerService. - @hide --> - <permission android:name="android.permission.START_ACTIVITY_AS_CALLER" - android:protectionLevel="signature" /> - <!-- @deprecated The {@link android.app.ActivityManager#restartPackage} API is no longer supported. --> <permission android:name="android.permission.RESTART_PACKAGES" @@ -2723,6 +2723,15 @@ <permission android:name="android.permission.BIND_AUTOFILL_SERVICE" android:protectionLevel="signature" /> + <!-- Alternative version of android.permission.BIND_AUTOFILL_FIELD_CLASSIFICATION_SERVICE. + This permission was renamed during the O previews but it was supported on the final O + release, so we need to carry it over. + <p>Protection level: signature + @hide + --> + <permission android:name="android.permission.BIND_AUTOFILL" + android:protectionLevel="signature" /> + <!-- Must be required by an {@link android.service.autofill.AutofillFieldClassificationService} to ensure that only the system can bind to it. @hide This is not a third-party API (intended for OEMs and system apps). @@ -2730,6 +2739,14 @@ <permission android:name="android.permission.BIND_AUTOFILL_FIELD_CLASSIFICATION_SERVICE" android:protectionLevel="signature" /> + <!-- Must be required by a android.service.textclassifier.TextClassifierService, + to ensure that only the system can bind to it. + @SystemApi @hide This is not a third-party API (intended for OEMs and system apps). + <p>Protection level: signature + --> + <permission android:name="android.permission.BIND_TEXTCLASSIFIER_SERVICE" + android:protectionLevel="signature" /> + <!-- Must be required by hotword enrollment application, to ensure that only the system can interact with it. @hide <p>Not for use by third-party applications.</p> --> diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml index 5184dda70252..0543305f9880 100644 --- a/core/res/res/values/attrs.xml +++ b/core/res/res/values/attrs.xml @@ -8773,6 +8773,16 @@ <attr name="fontProviderCerts" format="reference" /> </declare-styleable> + <!-- Attributes that are read when parsing a tag. --> + <declare-styleable name="VideoView2"> + <attr name="enableControlView" format="boolean" /> + <attr name="showSubtitle" format="boolean" /> + <attr name="viewType" format="enum"> + <enum name="surfaceView" value="0" /> + <enum name="textureView" value="1" /> + </attr> + </declare-styleable> + <!-- @hide --> <declare-styleable name="RecyclerView"> <attr name="layoutManager" format="string" /> diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 38f890a1e95e..607414d666cc 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -2283,10 +2283,7 @@ Can be customized for other product types --> <string name="config_chooseTypeAndAccountActivity" translatable="false" >android/android.accounts.ChooseTypeAndAccountActivity</string> - <!-- Name of the activity that will handle requests to the system to choose an activity for - the purposes of resolving an intent. --> - <string name="config_chooserActivity" translatable="false" - >com.android.systemui/com.android.systemui.chooser.ChooserActivity</string> + <!-- Component name of a custom ResolverActivity (Intent resolver) to be used instead of the default framework version. If left empty, then the framework version will be used. Example: com.google.android.myapp/.resolver.MyResolverActivity --> @@ -2480,9 +2477,9 @@ <item>restart</item> <item>screenshot</item> <item>logout</item> + <item>lockdown</item> <item>bugreport</item> <item>users</item> - <item>lockdown</item> </string-array> <!-- Number of milliseconds to hold a wake lock to ensure that drawing is fully @@ -3126,6 +3123,15 @@ --> <string name="config_defaultAutofillService" translatable="false"></string> + <!-- The component name, flattened to a string, for the default system textclassifier service. + This service must be trusted, as it can be activated without explicit consent of the user. + (e.g. com.android.textclassifier/.TextClassifierServiceImpl). + If no textclassifier service with the specified name exists on the device (or if this is + set to empty string), a default textclassifier will be loaded in the calling app's process. + See android.view.textclassifier.TextClassificationManager. + --> + <string name="config_defaultTextClassifierService" translatable="false"></string> + <!-- Whether the device uses the default focus highlight when focus state isn't specified. --> <bool name="config_useDefaultFocusHighlight">true</bool> @@ -3269,4 +3275,6 @@ <string name="config_defaultAssistantAccessPackage" translatable="false">android.ext.services</string> <bool name="config_supportBluetoothPersistedState">true</bool> + + <bool name="config_keepRestrictedProfilesInBackground">true</bool> </resources> diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml index e57b8b242490..e610efd4ea6a 100644 --- a/core/res/res/values/dimens.xml +++ b/core/res/res/values/dimens.xml @@ -527,7 +527,7 @@ <dimen name="content_rect_bottom_clip_allowance">20dp</dimen> <!-- Magnifier dimensions --> - <dimen name="magnifier_width">164dp</dimen> + <dimen name="magnifier_width">100dp</dimen> <dimen name="magnifier_height">48dp</dimen> <dimen name="magnifier_elevation">2dp</dimen> <dimen name="magnifier_offset">42dp</dimen> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index e8ab0be78b36..a62d49ee7bfd 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -1071,7 +1071,6 @@ <java-symbol type="string" name="owner_name" /> <java-symbol type="string" name="config_chooseAccountActivity" /> <java-symbol type="string" name="config_chooseTypeAndAccountActivity" /> - <java-symbol type="string" name="config_chooserActivity" /> <java-symbol type="string" name="config_customResolverActivity" /> <java-symbol type="string" name="config_appsAuthorizedForSharedAccounts" /> <java-symbol type="string" name="error_message_title" /> @@ -3102,6 +3101,7 @@ <java-symbol type="string" name="notification_channel_usb" /> <java-symbol type="string" name="notification_channel_heavy_weight_app" /> <java-symbol type="string" name="config_defaultAutofillService" /> + <java-symbol type="string" name="config_defaultTextClassifierService" /> <java-symbol type="string" name="notification_channel_foreground_service" /> <java-symbol type="string" name="foreground_service_app_in_background" /> @@ -3241,4 +3241,6 @@ <java-symbol type="string" name="slices_permission_request" /> <java-symbol type="string" name="screenshot_edit" /> + + <java-symbol type="bool" name="config_keepRestrictedProfilesInBackground" /> </resources> diff --git a/core/res/res/xml/default_zen_mode_config.xml b/core/res/res/xml/default_zen_mode_config.xml index 1e4c03ee9637..a446088fb93d 100644 --- a/core/res/res/xml/default_zen_mode_config.xml +++ b/core/res/res/xml/default_zen_mode_config.xml @@ -19,5 +19,6 @@ <!-- Default configuration for zen mode. See android.service.notification.ZenModeConfig. --> <zen version="2"> - <allow alarms="true" media_system_other="true" calls="false" messages="false" reminders="false" events="false" /> + <allow alarms="true" media_system_other="true" calls="false" messages="false" reminders="false" + events="false" /> </zen> diff --git a/core/tests/coretests/Android.mk b/core/tests/coretests/Android.mk index 60b46b453100..2ea1b46ad208 100644 --- a/core/tests/coretests/Android.mk +++ b/core/tests/coretests/Android.mk @@ -52,10 +52,8 @@ LOCAL_JAVA_LIBRARIES := \ org.apache.http.legacy \ android.test.base \ android.test.mock \ - -ifeq ($(REMOVE_OAHL_FROM_BCP),true) -LOCAL_JAVA_LIBRARIES += framework-oahl-backward-compatibility -endif + framework-oahl-backward-compatibility \ + framework-atb-backward-compatibility \ LOCAL_PACKAGE_NAME := FrameworksCoreTests LOCAL_COMPATIBILITY_SUITE := device-tests diff --git a/core/tests/coretests/src/android/content/pm/AndroidTestBaseUpdaterTest.java b/core/tests/coretests/src/android/content/pm/AndroidTestBaseUpdaterTest.java new file mode 100644 index 000000000000..dce22ce6a6f2 --- /dev/null +++ b/core/tests/coretests/src/android/content/pm/AndroidTestBaseUpdaterTest.java @@ -0,0 +1,108 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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.content.pm; + +import static android.content.pm.PackageBuilder.builder; +import static android.content.pm.SharedLibraryNames.ANDROID_TEST_BASE; + +import android.os.Build; +import android.support.test.filters.SmallTest; + +import org.junit.Test; +import org.junit.runner.RunWith; + +/** + * Test for {@link AndroidTestBaseUpdater} + */ +@SmallTest +@RunWith(OptionalClassRunner.class) +@OptionalClassRunner.OptionalClass("android.content.pm.AndroidTestBaseUpdater") +public class AndroidTestBaseUpdaterTest extends PackageSharedLibraryUpdaterTest { + + private static final String OTHER_LIBRARY = "other.library"; + + @Test + public void targeted_at_O() { + PackageBuilder before = builder() + .targetSdkVersion(Build.VERSION_CODES.O); + + // Should add org.apache.http.legacy. + PackageBuilder after = builder() + .targetSdkVersion(Build.VERSION_CODES.O) + .requiredLibraries(ANDROID_TEST_BASE); + + checkBackwardsCompatibility(before, after); + } + + @Test + public void targeted_at_O_not_empty_usesLibraries() { + PackageBuilder before = builder() + .targetSdkVersion(Build.VERSION_CODES.O) + .requiredLibraries(OTHER_LIBRARY); + + // The org.apache.http.legacy jar should be added at the start of the list because it + // is not on the bootclasspath and the package targets pre-P. + PackageBuilder after = builder() + .targetSdkVersion(Build.VERSION_CODES.O) + .requiredLibraries(ANDROID_TEST_BASE, OTHER_LIBRARY); + + checkBackwardsCompatibility(before, after); + } + + @Test + public void targeted_at_O_in_usesLibraries() { + PackageBuilder before = builder() + .targetSdkVersion(Build.VERSION_CODES.O) + .requiredLibraries(ANDROID_TEST_BASE); + + // No change is required because although org.apache.http.legacy has been removed from + // the bootclasspath the package explicitly requests it. + checkBackwardsCompatibility(before, before); + } + + @Test + public void targeted_at_O_in_usesOptionalLibraries() { + PackageBuilder before = builder() + .targetSdkVersion(Build.VERSION_CODES.O) + .optionalLibraries(ANDROID_TEST_BASE); + + // No change is required because although org.apache.http.legacy has been removed from + // the bootclasspath the package explicitly requests it. + checkBackwardsCompatibility(before, before); + } + + @Test + public void in_usesLibraries() { + PackageBuilder before = builder().requiredLibraries(ANDROID_TEST_BASE); + + // No change is required because the package explicitly requests org.apache.http.legacy + // and is targeted at the current version so does not need backwards compatibility. + checkBackwardsCompatibility(before, before); + } + + @Test + public void in_usesOptionalLibraries() { + PackageBuilder before = builder().optionalLibraries(ANDROID_TEST_BASE); + + // No change is required because the package explicitly requests org.apache.http.legacy + // and is targeted at the current version so does not need backwards compatibility. + checkBackwardsCompatibility(before, before); + } + + private void checkBackwardsCompatibility(PackageBuilder before, PackageBuilder after) { + checkBackwardsCompatibility(before, after, AndroidTestBaseUpdater::new); + } +} diff --git a/core/tests/coretests/src/android/content/pm/AndroidTestRunnerSplitUpdaterTest.java b/core/tests/coretests/src/android/content/pm/AndroidTestRunnerSplitUpdaterTest.java new file mode 100644 index 000000000000..866de93c07fe --- /dev/null +++ b/core/tests/coretests/src/android/content/pm/AndroidTestRunnerSplitUpdaterTest.java @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.content.pm; + +import static android.content.pm.PackageBuilder.builder; +import static android.content.pm.SharedLibraryNames.ANDROID_TEST_MOCK; +import static android.content.pm.SharedLibraryNames.ANDROID_TEST_RUNNER; + +import android.content.pm.PackageBackwardCompatibility.AndroidTestRunnerSplitUpdater; +import android.support.test.filters.SmallTest; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** + * Test for {@link AndroidTestRunnerSplitUpdater} + */ +@SmallTest +@RunWith(JUnit4.class) +public class AndroidTestRunnerSplitUpdaterTest extends PackageSharedLibraryUpdaterTest { + + @Test + public void android_test_runner_in_usesOptionalLibraries() { + PackageBuilder before = builder().optionalLibraries(ANDROID_TEST_RUNNER); + + PackageBuilder after = builder() + .optionalLibraries(ANDROID_TEST_MOCK, ANDROID_TEST_RUNNER); + + checkBackwardsCompatibility(before, after); + } + + @Test + public void android_test_runner_in_usesLibraries_android_test_mock_in_usesOptionalLibraries() { + PackageBuilder before = builder() + .requiredLibraries(ANDROID_TEST_RUNNER) + .optionalLibraries(ANDROID_TEST_MOCK); + + PackageBuilder after = builder() + .requiredLibraries(ANDROID_TEST_RUNNER) + .optionalLibraries(ANDROID_TEST_MOCK); + + checkBackwardsCompatibility(before, after); + } + + private void checkBackwardsCompatibility(PackageBuilder before, PackageBuilder after) { + checkBackwardsCompatibility(before, after, AndroidTestRunnerSplitUpdater::new); + } +} diff --git a/core/tests/coretests/src/android/content/pm/OptionalClassRunner.java b/core/tests/coretests/src/android/content/pm/OptionalClassRunner.java new file mode 100644 index 000000000000..91697c0d74f3 --- /dev/null +++ b/core/tests/coretests/src/android/content/pm/OptionalClassRunner.java @@ -0,0 +1,141 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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.content.pm; + +import org.junit.Assume; +import org.junit.runner.Description; +import org.junit.runner.Runner; +import org.junit.runner.notification.RunNotifier; +import org.junit.runners.JUnit4; +import org.junit.runners.ParentRunner; +import org.junit.runners.model.InitializationError; +import org.junit.runners.model.Statement; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.util.Collections; +import java.util.List; + +/** + * Will run a test class, iff a class, specified by name in the {@link OptionalClass} annotation, + * exists on the class path. + * + * <p>It is an {@link InitializationError} if no {@link OptionalClass} annotation is specified on + * the class that has {@code @RunWith(OptionalClassRunner.class)}. + * + * <p>If the named class cannot be found then the test class is reported as having been ignored. + */ +public class OptionalClassRunner extends Runner { + + private final Runner mDelegate; + + public OptionalClassRunner(Class<?> testClass) throws InitializationError { + OptionalClass annotation = testClass.getAnnotation(OptionalClass.class); + if (annotation == null) { + throw new InitializationError( + "No " + OptionalClass.class.getName() + " annotation found on " + testClass); + } + + String className = annotation.value(); + Runner delegate; + try { + Class.forName(className); + // The class could be found so create a JUnit4 delegate for the class to run. + delegate = new JUnit4(testClass); + } catch (ClassNotFoundException e) { + // The class could not be found so create a Runner delegate that will treat the + // test as having failed a test assumption. + delegate = new ClassNotFoundRunner(testClass, className); + } + + this.mDelegate = delegate; + } + + @Override + public Description getDescription() { + return mDelegate.getDescription(); + } + + @Override + public void run(RunNotifier notifier) { + mDelegate.run(notifier); + } + + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.TYPE) + public @interface OptionalClass { + String value(); + } + + /** + * Emulates a class containing a single test that fails due to an invalid assumption caused by + * the missing class. + */ + private static class ClassNotFoundRunner extends ParentRunner<Runner> { + + private List<Runner> mChildren; + + ClassNotFoundRunner(Class<?> testClass, String className) + throws InitializationError { + super(testClass); + this.mChildren = Collections.singletonList(new ChildRunner(testClass, className)); + } + + @Override + protected List<Runner> getChildren() { + return mChildren; + } + + @Override + protected Description describeChild(Runner child) { + return child.getDescription(); + } + + @Override + protected void runChild(Runner child, RunNotifier notifier) { + child.run(notifier); + } + + private class ChildRunner extends Runner { + + private final Class<?> mTestClass; + + private final String mClassName; + + ChildRunner(Class<?> testClass, String className) { + this.mTestClass = testClass; + this.mClassName = className; + } + + @Override + public Description getDescription() { + return Description.createTestDescription(mTestClass, "classNotFound"); + } + + @Override + public void run(RunNotifier notifier) { + runLeaf(new Statement() { + @Override + public void evaluate() throws Throwable { + Assume.assumeTrue("Could not find class: " + mClassName, false); + } + }, getDescription(), notifier); + } + } + } +} diff --git a/core/tests/coretests/src/android/content/pm/OrgApacheHttpLegacyUpdaterTest.java b/core/tests/coretests/src/android/content/pm/OrgApacheHttpLegacyUpdaterTest.java new file mode 100644 index 000000000000..dcd2707a9540 --- /dev/null +++ b/core/tests/coretests/src/android/content/pm/OrgApacheHttpLegacyUpdaterTest.java @@ -0,0 +1,108 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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.content.pm; + +import static android.content.pm.PackageBuilder.builder; +import static android.content.pm.SharedLibraryNames.ORG_APACHE_HTTP_LEGACY; + +import android.os.Build; +import android.support.test.filters.SmallTest; + +import org.junit.Test; +import org.junit.runner.RunWith; + +/** + * Test for {@link OrgApacheHttpLegacyUpdater} + */ +@SmallTest +@RunWith(OptionalClassRunner.class) +@OptionalClassRunner.OptionalClass("android.content.pm.OrgApacheHttpLegacyUpdater") +public class OrgApacheHttpLegacyUpdaterTest extends PackageSharedLibraryUpdaterTest { + + private static final String OTHER_LIBRARY = "other.library"; + + @Test + public void targeted_at_O() { + PackageBuilder before = builder() + .targetSdkVersion(Build.VERSION_CODES.O); + + // Should add org.apache.http.legacy. + PackageBuilder after = builder() + .targetSdkVersion(Build.VERSION_CODES.O) + .requiredLibraries(ORG_APACHE_HTTP_LEGACY); + + checkBackwardsCompatibility(before, after); + } + + @Test + public void targeted_at_O_not_empty_usesLibraries() { + PackageBuilder before = builder() + .targetSdkVersion(Build.VERSION_CODES.O) + .requiredLibraries(OTHER_LIBRARY); + + // The org.apache.http.legacy jar should be added at the start of the list because it + // is not on the bootclasspath and the package targets pre-P. + PackageBuilder after = builder() + .targetSdkVersion(Build.VERSION_CODES.O) + .requiredLibraries(ORG_APACHE_HTTP_LEGACY, OTHER_LIBRARY); + + checkBackwardsCompatibility(before, after); + } + + @Test + public void targeted_at_O_in_usesLibraries() { + PackageBuilder before = builder() + .targetSdkVersion(Build.VERSION_CODES.O) + .requiredLibraries(ORG_APACHE_HTTP_LEGACY); + + // No change is required because although org.apache.http.legacy has been removed from + // the bootclasspath the package explicitly requests it. + checkBackwardsCompatibility(before, before); + } + + @Test + public void targeted_at_O_in_usesOptionalLibraries() { + PackageBuilder before = builder() + .targetSdkVersion(Build.VERSION_CODES.O) + .optionalLibraries(ORG_APACHE_HTTP_LEGACY); + + // No change is required because although org.apache.http.legacy has been removed from + // the bootclasspath the package explicitly requests it. + checkBackwardsCompatibility(before, before); + } + + @Test + public void in_usesLibraries() { + PackageBuilder before = builder().requiredLibraries(ORG_APACHE_HTTP_LEGACY); + + // No change is required because the package explicitly requests org.apache.http.legacy + // and is targeted at the current version so does not need backwards compatibility. + checkBackwardsCompatibility(before, before); + } + + @Test + public void in_usesOptionalLibraries() { + PackageBuilder before = builder().optionalLibraries(ORG_APACHE_HTTP_LEGACY); + + // No change is required because the package explicitly requests org.apache.http.legacy + // and is targeted at the current version so does not need backwards compatibility. + checkBackwardsCompatibility(before, before); + } + + private void checkBackwardsCompatibility(PackageBuilder before, PackageBuilder after) { + checkBackwardsCompatibility(before, after, OrgApacheHttpLegacyUpdater::new); + } +} diff --git a/core/tests/coretests/src/android/content/pm/PackageBackwardCompatibilityTest.java b/core/tests/coretests/src/android/content/pm/PackageBackwardCompatibilityTest.java index 6996e5056466..c64d5202e1fd 100644 --- a/core/tests/coretests/src/android/content/pm/PackageBackwardCompatibilityTest.java +++ b/core/tests/coretests/src/android/content/pm/PackageBackwardCompatibilityTest.java @@ -16,179 +16,158 @@ package android.content.pm; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNull; +import static android.content.pm.PackageBuilder.builder; +import static android.content.pm.SharedLibraryNames.ANDROID_TEST_BASE; +import static android.content.pm.SharedLibraryNames.ANDROID_TEST_MOCK; +import static android.content.pm.SharedLibraryNames.ANDROID_TEST_RUNNER; +import static android.content.pm.SharedLibraryNames.ORG_APACHE_HTTP_LEGACY; -import android.content.pm.PackageParser.Package; +import android.content.pm.PackageBackwardCompatibility.RemoveUnnecessaryAndroidTestBaseLibrary; import android.os.Build; import android.support.test.filters.SmallTest; -import org.junit.Before; +import org.junit.Assume; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; import java.util.ArrayList; -import java.util.Collections; +import java.util.List; @SmallTest @RunWith(JUnit4.class) -public class PackageBackwardCompatibilityTest { +public class PackageBackwardCompatibilityTest extends PackageSharedLibraryUpdaterTest { - private static final String ORG_APACHE_HTTP_LEGACY = "org.apache.http.legacy"; - - private static final String ANDROID_TEST_RUNNER = "android.test.runner"; - - private static final String ANDROID_TEST_MOCK = "android.test.mock"; - - private static final String OTHER_LIBRARY = "other.library"; - - private Package mPackage; - - private static ArrayList<String> arrayList(String... strings) { - ArrayList<String> list = new ArrayList<>(); - Collections.addAll(list, strings); - return list; - } + @Test + public void null_usesLibraries_and_usesOptionalLibraries() { + PackageBuilder before = builder(); + PackageBuilder after = builder(); - @Before - public void setUp() { - mPackage = new Package("org.package.name"); - mPackage.applicationInfo.targetSdkVersion = Build.VERSION_CODES.CUR_DEVELOPMENT; + checkBackwardsCompatibility(before, after); } + /** + * Detect when the org.apache.http.legacy is not on the bootclasspath. + * + * <p>This test will be ignored when org.apache.http.legacy is not on the bootclasspath and + * succeed otherwise. This allows a developer to ensure that the tests are being + */ @Test - public void null_usesLibraries() { - PackageBackwardCompatibility.modifySharedLibraries(mPackage); - assertNull("usesLibraries not updated correctly", mPackage.usesLibraries); + public void detectWhenOAHLisOnBCP() { + Assume.assumeTrue(PackageBackwardCompatibility.bootClassPathContainsOAHL()); } + /** + * Detect when the android.test.base is not on the bootclasspath. + * + * <p>This test will be ignored when org.apache.http.legacy is not on the bootclasspath and + * succeed otherwise. This allows a developer to ensure that the tests are being + */ @Test - public void null_usesOptionalLibraries() { - PackageBackwardCompatibility.modifySharedLibraries(mPackage); - assertNull("usesOptionalLibraries not updated correctly", mPackage.usesOptionalLibraries); + public void detectWhenATBisOnBCP() { + Assume.assumeTrue(PackageBackwardCompatibility.bootClassPathContainsATB()); } + /** + * Ensures that the {@link PackageBackwardCompatibility} uses {@link OrgApacheHttpLegacyUpdater} + * and {@link AndroidTestBaseUpdater} when necessary. + * + * <p>More comprehensive tests for that class can be found in + * {@link OrgApacheHttpLegacyUpdaterTest}. + */ @Test public void targeted_at_O() { - mPackage.applicationInfo.targetSdkVersion = Build.VERSION_CODES.O; - PackageBackwardCompatibility.modifySharedLibraries(mPackage); - if (PackageBackwardCompatibility.removeOAHLFromBCP()) { - assertEquals("usesLibraries not updated correctly", - arrayList(ORG_APACHE_HTTP_LEGACY), - mPackage.usesLibraries); - } else { - assertNull("usesOptionalLibraries not updated correctly", mPackage.usesLibraries); - } - assertNull("usesOptionalLibraries not updated correctly", mPackage.usesOptionalLibraries); - } + PackageBuilder before = builder() + .targetSdkVersion(Build.VERSION_CODES.O); - @Test - public void targeted_at_O_not_empty_usesLibraries() { - mPackage.applicationInfo.targetSdkVersion = Build.VERSION_CODES.O; - mPackage.usesLibraries = arrayList(OTHER_LIBRARY); - PackageBackwardCompatibility.modifySharedLibraries(mPackage); - if (PackageBackwardCompatibility.removeOAHLFromBCP()) { - // The org.apache.http.legacy jar should be added at the start of the list. - assertEquals("usesLibraries not updated correctly", - arrayList(ORG_APACHE_HTTP_LEGACY, OTHER_LIBRARY), - mPackage.usesLibraries); - } else { - assertEquals("usesLibraries not updated correctly", - arrayList(OTHER_LIBRARY), - mPackage.usesLibraries); + List<String> expected = new ArrayList<>(); + if (!PackageBackwardCompatibility.bootClassPathContainsATB()) { + expected.add(ANDROID_TEST_BASE); } - assertNull("usesOptionalLibraries not updated correctly", mPackage.usesOptionalLibraries); - } - - @Test - public void targeted_at_O_org_apache_http_legacy_in_usesLibraries() { - mPackage.applicationInfo.targetSdkVersion = Build.VERSION_CODES.O; - mPackage.usesLibraries = arrayList(ORG_APACHE_HTTP_LEGACY); - PackageBackwardCompatibility.modifySharedLibraries(mPackage); - if (PackageBackwardCompatibility.removeOAHLFromBCP()) { - assertEquals("usesLibraries not updated correctly", - arrayList(ORG_APACHE_HTTP_LEGACY), - mPackage.usesLibraries); - } else { - assertNull("usesLibraries not updated correctly", mPackage.usesLibraries); + if (!PackageBackwardCompatibility.bootClassPathContainsOAHL()) { + expected.add(ORG_APACHE_HTTP_LEGACY); } - assertNull("usesOptionalLibraries not updated correctly", mPackage.usesOptionalLibraries); - } - @Test - public void targeted_at_O_org_apache_http_legacy_in_usesOptionalLibraries() { - mPackage.applicationInfo.targetSdkVersion = Build.VERSION_CODES.O; - mPackage.usesOptionalLibraries = arrayList(ORG_APACHE_HTTP_LEGACY); - PackageBackwardCompatibility.modifySharedLibraries(mPackage); - assertNull("usesLibraries not updated correctly", mPackage.usesLibraries); - if (PackageBackwardCompatibility.removeOAHLFromBCP()) { - assertEquals("usesOptionalLibraries not updated correctly", - arrayList(ORG_APACHE_HTTP_LEGACY), - mPackage.usesOptionalLibraries); - } else { - assertNull("usesOptionalLibraries not updated correctly", - mPackage.usesOptionalLibraries); - } + PackageBuilder after = builder() + .targetSdkVersion(Build.VERSION_CODES.O) + .requiredLibraries(expected); + + checkBackwardsCompatibility(before, after); } + /** + * Ensures that the {@link PackageBackwardCompatibility} uses + * {@link RemoveUnnecessaryOrgApacheHttpLegacyLibraryTest} + * when necessary. + * + * <p>More comprehensive tests for that class can be found in + * {@link RemoveUnnecessaryOrgApacheHttpLegacyLibraryTest}. + */ @Test public void org_apache_http_legacy_in_usesLibraries() { - mPackage.usesLibraries = arrayList(ORG_APACHE_HTTP_LEGACY); - PackageBackwardCompatibility.modifySharedLibraries(mPackage); - if (PackageBackwardCompatibility.removeOAHLFromBCP()) { - assertEquals("usesLibraries not updated correctly", - arrayList(ORG_APACHE_HTTP_LEGACY), - mPackage.usesLibraries); - } else { - assertNull("usesLibraries not updated correctly", mPackage.usesLibraries); - } - assertNull("usesOptionalLibraries not updated correctly", mPackage.usesOptionalLibraries); + Assume.assumeTrue("Test requires that " + + ORG_APACHE_HTTP_LEGACY + " is on the bootclasspath", + PackageBackwardCompatibility.bootClassPathContainsOAHL()); + + PackageBuilder before = builder() + .requiredLibraries(ORG_APACHE_HTTP_LEGACY); + + // org.apache.http.legacy should be removed from the libraries because it is provided + // on the bootclasspath and providing both increases start up cost unnecessarily. + PackageBuilder after = builder(); + + checkBackwardsCompatibility(before, after); } + /** + * Ensures that the {@link PackageBackwardCompatibility} uses + * {@link RemoveUnnecessaryAndroidTestBaseLibrary} + * when necessary. + * + * <p>More comprehensive tests for that class can be found in + * {@link RemoveUnnecessaryAndroidTestBaseLibraryTest}. + */ @Test - public void org_apache_http_legacy_in_usesOptionalLibraries() { - mPackage.usesOptionalLibraries = arrayList(ORG_APACHE_HTTP_LEGACY); - PackageBackwardCompatibility.modifySharedLibraries(mPackage); - assertNull("usesLibraries not updated correctly", mPackage.usesLibraries); - if (PackageBackwardCompatibility.removeOAHLFromBCP()) { - assertEquals("usesOptionalLibraries not updated correctly", - arrayList(ORG_APACHE_HTTP_LEGACY), - mPackage.usesOptionalLibraries); - } else { - assertNull("usesOptionalLibraries not updated correctly", - mPackage.usesOptionalLibraries); - } + public void android_test_base_in_usesLibraries() { + Assume.assumeTrue("Test requires that " + + ANDROID_TEST_BASE + " is on the bootclasspath", + PackageBackwardCompatibility.bootClassPathContainsATB()); + + PackageBuilder before = builder() + .requiredLibraries(ANDROID_TEST_BASE); + + // android.test.base should be removed from the libraries because it is provided + // on the bootclasspath and providing both increases start up cost unnecessarily. + PackageBuilder after = builder(); + + checkBackwardsCompatibility(before, after); } + /** + * Ensures that the {@link PackageBackwardCompatibility} uses a + * {@link PackageBackwardCompatibility.AndroidTestRunnerSplitUpdater}. + * + * <p>More comprehensive tests for that class can be found in + * {@link AndroidTestRunnerSplitUpdaterTest}. + */ @Test public void android_test_runner_in_usesLibraries() { - mPackage.usesLibraries = arrayList(ANDROID_TEST_RUNNER); - PackageBackwardCompatibility.modifySharedLibraries(mPackage); - assertEquals("usesLibraries not updated correctly", - arrayList(ANDROID_TEST_RUNNER, ANDROID_TEST_MOCK), - mPackage.usesLibraries); - } + PackageBuilder before = builder().requiredLibraries(ANDROID_TEST_RUNNER); - @Test - public void android_test_runner_in_usesOptionalLibraries() { - mPackage.usesOptionalLibraries = arrayList(ANDROID_TEST_RUNNER); - PackageBackwardCompatibility.modifySharedLibraries(mPackage); - assertEquals("usesOptionalLibraries not updated correctly", - arrayList(ANDROID_TEST_RUNNER, ANDROID_TEST_MOCK), - mPackage.usesOptionalLibraries); + List<String> expected = new ArrayList<>(); + if (!PackageBackwardCompatibility.bootClassPathContainsATB()) { + expected.add(ANDROID_TEST_BASE); + } + expected.add(ANDROID_TEST_MOCK); + expected.add(ANDROID_TEST_RUNNER); + + PackageBuilder after = builder() + .requiredLibraries(expected); + + checkBackwardsCompatibility(before, after); } - @Test - public void android_test_runner_in_usesLibraries_android_test_mock_in_usesOptionalLibraries() { - mPackage.usesLibraries = arrayList(ANDROID_TEST_RUNNER); - mPackage.usesOptionalLibraries = arrayList(ANDROID_TEST_MOCK); - PackageBackwardCompatibility.modifySharedLibraries(mPackage); - assertEquals("usesLibraries not updated correctly", - arrayList(ANDROID_TEST_RUNNER), - mPackage.usesLibraries); - assertEquals("usesOptionalLibraries not updated correctly", - arrayList(ANDROID_TEST_MOCK), - mPackage.usesOptionalLibraries); + private void checkBackwardsCompatibility(PackageBuilder before, PackageBuilder after) { + checkBackwardsCompatibility(before, after, PackageBackwardCompatibility::getInstance); } } diff --git a/core/tests/coretests/src/android/content/pm/PackageBuilder.java b/core/tests/coretests/src/android/content/pm/PackageBuilder.java new file mode 100644 index 000000000000..4ceed834aab7 --- /dev/null +++ b/core/tests/coretests/src/android/content/pm/PackageBuilder.java @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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.content.pm; + +import static org.junit.Assert.assertEquals; + +import android.os.Build; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * Test support for building {@link PackageParser.Package} instances. + */ +class PackageBuilder { + + private int mTargetSdkVersion = Build.VERSION_CODES.CUR_DEVELOPMENT; + + private ArrayList<String> mRequiredLibraries; + + private ArrayList<String> mOptionalLibraries; + + public static PackageBuilder builder() { + return new PackageBuilder(); + } + + public PackageParser.Package build() { + PackageParser.Package pkg = new PackageParser.Package("org.package.name"); + pkg.applicationInfo.targetSdkVersion = mTargetSdkVersion; + pkg.usesLibraries = mRequiredLibraries; + pkg.usesOptionalLibraries = mOptionalLibraries; + return pkg; + } + + PackageBuilder targetSdkVersion(int version) { + this.mTargetSdkVersion = version; + return this; + } + + PackageBuilder requiredLibraries(String... names) { + this.mRequiredLibraries = arrayListOrNull(names); + return this; + } + + PackageBuilder requiredLibraries(List<String> names) { + this.mRequiredLibraries = arrayListOrNull(names.toArray(new String[names.size()])); + return this; + } + + PackageBuilder optionalLibraries(String... names) { + this.mOptionalLibraries = arrayListOrNull(names); + return this; + } + + /** + * Check that this matches the supplied {@link PackageParser.Package}. + * + * @param pkg the instance to compare with this. + */ + public void check(PackageParser.Package pkg) { + assertEquals("targetSdkVersion should not be changed", + mTargetSdkVersion, + pkg.applicationInfo.targetSdkVersion); + assertEquals("usesLibraries not updated correctly", + mRequiredLibraries, + pkg.usesLibraries); + assertEquals("usesOptionalLibraries not updated correctly", + mOptionalLibraries, + pkg.usesOptionalLibraries); + } + + private static ArrayList<String> arrayListOrNull(String... strings) { + if (strings == null || strings.length == 0) { + return null; + } + ArrayList<String> list = new ArrayList<>(); + Collections.addAll(list, strings); + return list; + } + +} diff --git a/core/tests/coretests/src/android/content/pm/PackageSharedLibraryUpdaterTest.java b/core/tests/coretests/src/android/content/pm/PackageSharedLibraryUpdaterTest.java new file mode 100644 index 000000000000..d5d3d7a6f2f4 --- /dev/null +++ b/core/tests/coretests/src/android/content/pm/PackageSharedLibraryUpdaterTest.java @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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.content.pm; + +import java.util.function.Supplier; + +/** + * Helper for classes that test {@link PackageSharedLibraryUpdater}. + */ +abstract class PackageSharedLibraryUpdaterTest { + + static void checkBackwardsCompatibility(PackageBuilder before, PackageBuilder after, + Supplier<PackageSharedLibraryUpdater> updaterSupplier) { + PackageParser.Package pkg = before.build(); + updaterSupplier.get().updatePackage(pkg); + after.check(pkg); + } +} diff --git a/core/tests/coretests/src/android/content/pm/RemoveUnnecessaryAndroidTestBaseLibraryTest.java b/core/tests/coretests/src/android/content/pm/RemoveUnnecessaryAndroidTestBaseLibraryTest.java new file mode 100644 index 000000000000..3dba4406de47 --- /dev/null +++ b/core/tests/coretests/src/android/content/pm/RemoveUnnecessaryAndroidTestBaseLibraryTest.java @@ -0,0 +1,131 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.content.pm; + +import static android.content.pm.PackageBuilder.builder; +import static android.content.pm.SharedLibraryNames.ANDROID_TEST_BASE; + +import android.content.pm.PackageBackwardCompatibility.RemoveUnnecessaryAndroidTestBaseLibrary; +import android.os.Build; +import android.support.test.filters.SmallTest; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** + * Test for {@link RemoveUnnecessaryAndroidTestBaseLibrary} + */ +@SmallTest +@RunWith(JUnit4.class) +public class RemoveUnnecessaryAndroidTestBaseLibraryTest + extends PackageSharedLibraryUpdaterTest { + + private static final String OTHER_LIBRARY = "other.library"; + + @Test + public void targeted_at_O() { + PackageBuilder before = builder() + .targetSdkVersion(Build.VERSION_CODES.O); + + // No change required. + checkBackwardsCompatibility(before, before); + } + + @Test + public void targeted_at_O_not_empty_usesLibraries() { + PackageBuilder before = builder() + .targetSdkVersion(Build.VERSION_CODES.O) + .requiredLibraries(OTHER_LIBRARY); + + // No change required. + checkBackwardsCompatibility(before, before); + } + + @Test + public void targeted_at_O_in_usesLibraries() { + PackageBuilder before = builder() + .targetSdkVersion(Build.VERSION_CODES.O) + .requiredLibraries(ANDROID_TEST_BASE); + + // android.test.base should be removed from the libraries because it is provided + // on the bootclasspath and providing both increases start up cost unnecessarily. + PackageBuilder after = builder() + .targetSdkVersion(Build.VERSION_CODES.O); + + checkBackwardsCompatibility(before, after); + } + + @Test + public void targeted_at_O_in_usesOptionalLibraries() { + PackageBuilder before = builder() + .targetSdkVersion(Build.VERSION_CODES.O) + .optionalLibraries(ANDROID_TEST_BASE); + + // android.test.base should be removed from the libraries because it is provided + // on the bootclasspath and providing both increases start up cost unnecessarily. + PackageBuilder after = builder() + .targetSdkVersion(Build.VERSION_CODES.O); + + checkBackwardsCompatibility(before, after); + } + + @Test + public void in_usesLibraries() { + PackageBuilder before = builder().requiredLibraries(ANDROID_TEST_BASE); + + // android.test.base should be removed from the libraries because it is provided + // on the bootclasspath and providing both increases start up cost unnecessarily. + PackageBuilder after = builder(); + + checkBackwardsCompatibility(before, after); + } + + @Test + public void in_usesOptionalLibraries() { + PackageBuilder before = builder().optionalLibraries(ANDROID_TEST_BASE); + + // android.test.base should be removed from the libraries because it is provided + // on the bootclasspath and providing both increases start up cost unnecessarily. + PackageBuilder after = builder(); + + checkBackwardsCompatibility(before, after); + } + + @Test + public void in_bothLibraries() { + PackageBuilder before = builder() + .requiredLibraries(ANDROID_TEST_BASE) + .optionalLibraries(ANDROID_TEST_BASE); + + // android.test.base should be removed from the libraries because it is provided + // on the bootclasspath and providing both increases start up cost unnecessarily. + PackageBuilder after = builder(); + + checkBackwardsCompatibility(before, after); + } + + private void checkBackwardsCompatibility(PackageBuilder before, PackageBuilder after) { + // TODO(b/72538146) - Cannot use constructor reference here because it is also used in + // PackageBackwardCompatibility and that seems to create a package-private lambda in + // android.content.pm which this then tries to reuse but fails because it cannot access + // package-private classes/members because the test is loaded by a different ClassLoader + // than the lambda. + checkBackwardsCompatibility(before, after, + () -> new RemoveUnnecessaryAndroidTestBaseLibrary()); + } + +} diff --git a/core/tests/coretests/src/android/content/pm/RemoveUnnecessaryOrgApacheHttpLegacyLibraryTest.java b/core/tests/coretests/src/android/content/pm/RemoveUnnecessaryOrgApacheHttpLegacyLibraryTest.java new file mode 100644 index 000000000000..15b27d71f81d --- /dev/null +++ b/core/tests/coretests/src/android/content/pm/RemoveUnnecessaryOrgApacheHttpLegacyLibraryTest.java @@ -0,0 +1,130 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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.content.pm; + +import static android.content.pm.PackageBuilder.builder; +import static android.content.pm.SharedLibraryNames.ORG_APACHE_HTTP_LEGACY; + +import android.content.pm.PackageBackwardCompatibility.RemoveUnnecessaryOrgApacheHttpLegacyLibrary; +import android.os.Build; +import android.support.test.filters.SmallTest; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** + * Test for {@link RemoveUnnecessaryOrgApacheHttpLegacyLibrary} + */ +@SmallTest +@RunWith(JUnit4.class) +public class RemoveUnnecessaryOrgApacheHttpLegacyLibraryTest + extends PackageSharedLibraryUpdaterTest { + + private static final String OTHER_LIBRARY = "other.library"; + + @Test + public void targeted_at_O() { + PackageBuilder before = builder() + .targetSdkVersion(Build.VERSION_CODES.O); + + // No change required. + checkBackwardsCompatibility(before, before); + } + + @Test + public void targeted_at_O_not_empty_usesLibraries() { + PackageBuilder before = builder() + .targetSdkVersion(Build.VERSION_CODES.O) + .requiredLibraries(OTHER_LIBRARY); + + // No change required. + checkBackwardsCompatibility(before, before); + } + + @Test + public void targeted_at_O_in_usesLibraries() { + PackageBuilder before = builder() + .targetSdkVersion(Build.VERSION_CODES.O) + .requiredLibraries(ORG_APACHE_HTTP_LEGACY); + + // org.apache.http.legacy should be removed from the libraries because it is provided + // on the bootclasspath and providing both increases start up cost unnecessarily. + PackageBuilder after = builder() + .targetSdkVersion(Build.VERSION_CODES.O); + + checkBackwardsCompatibility(before, after); + } + + @Test + public void targeted_at_O_in_usesOptionalLibraries() { + PackageBuilder before = builder() + .targetSdkVersion(Build.VERSION_CODES.O) + .optionalLibraries(ORG_APACHE_HTTP_LEGACY); + + // org.apache.http.legacy should be removed from the libraries because it is provided + // on the bootclasspath and providing both increases start up cost unnecessarily. + PackageBuilder after = builder() + .targetSdkVersion(Build.VERSION_CODES.O); + + checkBackwardsCompatibility(before, after); + } + + @Test + public void in_usesLibraries() { + PackageBuilder before = builder().requiredLibraries(ORG_APACHE_HTTP_LEGACY); + + // org.apache.http.legacy should be removed from the libraries because it is provided + // on the bootclasspath and providing both increases start up cost unnecessarily. + PackageBuilder after = builder(); + + checkBackwardsCompatibility(before, after); + } + + @Test + public void in_usesOptionalLibraries() { + PackageBuilder before = builder().optionalLibraries(ORG_APACHE_HTTP_LEGACY); + + // org.apache.http.legacy should be removed from the libraries because it is provided + // on the bootclasspath and providing both increases start up cost unnecessarily. + PackageBuilder after = builder(); + + checkBackwardsCompatibility(before, after); + } + + @Test + public void in_bothLibraries() { + PackageBuilder before = builder() + .requiredLibraries(ORG_APACHE_HTTP_LEGACY) + .optionalLibraries(ORG_APACHE_HTTP_LEGACY); + + // org.apache.http.legacy should be removed from the libraries because it is provided + // on the bootclasspath and providing both increases start up cost unnecessarily. + PackageBuilder after = builder(); + + checkBackwardsCompatibility(before, after); + } + + private void checkBackwardsCompatibility(PackageBuilder before, PackageBuilder after) { + // TODO(b/72538146) - Cannot use constructor reference here because it is also used in + // PackageBackwardCompatibility and that seems to create a package-private lambda in + // android.content.pm which this then tries to reuse but fails because it cannot access + // package-private classes/members because the test is loaded by a different ClassLoader + // than the lambda. + checkBackwardsCompatibility(before, after, + () -> new RemoveUnnecessaryOrgApacheHttpLegacyLibrary()); + } +} diff --git a/core/tests/coretests/src/android/provider/SettingsBackupTest.java b/core/tests/coretests/src/android/provider/SettingsBackupTest.java index 733f7a107fa8..0083b017033b 100644 --- a/core/tests/coretests/src/android/provider/SettingsBackupTest.java +++ b/core/tests/coretests/src/android/provider/SettingsBackupTest.java @@ -245,6 +245,7 @@ public class SettingsBackupTest { Settings.Global.INTENT_FIREWALL_UPDATE_CONTENT_URL, Settings.Global.INTENT_FIREWALL_UPDATE_METADATA_URL, Settings.Global.JOB_SCHEDULER_CONSTANTS, + Settings.Global.KEEP_PROFILE_IN_BACKGROUND, Settings.Global.LANG_ID_UPDATE_CONTENT_URL, Settings.Global.LANG_ID_UPDATE_METADATA_URL, Settings.Global.LOCATION_BACKGROUND_THROTTLE_INTERVAL_MS, diff --git a/core/tests/coretests/src/android/view/textclassifier/TextClassificationTest.java b/core/tests/coretests/src/android/view/textclassifier/TextClassificationTest.java index 8a81743c8154..cf41eb89ba20 100644 --- a/core/tests/coretests/src/android/view/textclassifier/TextClassificationTest.java +++ b/core/tests/coretests/src/android/view/textclassifier/TextClassificationTest.java @@ -86,15 +86,11 @@ public class TextClassificationTest { .setSignature(signature) .build(); - // Parcel and unparcel using ParcelableWrapper. - final TextClassification.ParcelableWrapper parcelableReference = new TextClassification - .ParcelableWrapper(reference); + // Parcel and unparcel final Parcel parcel = Parcel.obtain(); - parcelableReference.writeToParcel(parcel, parcelableReference.describeContents()); + reference.writeToParcel(parcel, reference.describeContents()); parcel.setDataPosition(0); - final TextClassification result = - TextClassification.ParcelableWrapper.CREATOR.createFromParcel( - parcel).getTextClassification(); + final TextClassification result = TextClassification.CREATOR.createFromParcel(parcel); assertEquals(text, result.getText()); assertEquals(signature, result.getSignature()); diff --git a/core/tests/coretests/src/android/view/textclassifier/TextLinksTest.java b/core/tests/coretests/src/android/view/textclassifier/TextLinksTest.java index a82542cd91c5..d6ac8454b86b 100644 --- a/core/tests/coretests/src/android/view/textclassifier/TextLinksTest.java +++ b/core/tests/coretests/src/android/view/textclassifier/TextLinksTest.java @@ -68,8 +68,8 @@ public class TextLinksTest { public void testParcel() { final String fullText = "this is just a test"; final TextLinks reference = new TextLinks.Builder(fullText) - .addLink(new TextLinks.TextLink(fullText, 0, 4, getEntityScores(0.f, 0.f, 1.f))) - .addLink(new TextLinks.TextLink(fullText, 5, 12, getEntityScores(.8f, .1f, .5f))) + .addLink(0, 4, getEntityScores(0.f, 0.f, 1.f)) + .addLink(5, 12, getEntityScores(.8f, .1f, .5f)) .build(); // Parcel and unparcel. diff --git a/core/tests/coretests/src/android/view/textclassifier/TextSelectionTest.java b/core/tests/coretests/src/android/view/textclassifier/TextSelectionTest.java index e9202361c84a..a6ea0211fbc0 100644 --- a/core/tests/coretests/src/android/view/textclassifier/TextSelectionTest.java +++ b/core/tests/coretests/src/android/view/textclassifier/TextSelectionTest.java @@ -45,15 +45,11 @@ public class TextSelectionTest { .setSignature(signature) .build(); - // Parcel and unparcel using ParcelableWrapper. - final TextSelection.ParcelableWrapper parcelableReference = new TextSelection - .ParcelableWrapper(reference); + // Parcel and unparcel final Parcel parcel = Parcel.obtain(); - parcelableReference.writeToParcel(parcel, parcelableReference.describeContents()); + reference.writeToParcel(parcel, reference.describeContents()); parcel.setDataPosition(0); - final TextSelection result = - TextSelection.ParcelableWrapper.CREATOR.createFromParcel( - parcel).getTextSelection(); + final TextSelection result = TextSelection.CREATOR.createFromParcel(parcel); assertEquals(startIndex, result.getSelectionStartIndex()); assertEquals(endIndex, result.getSelectionEndIndex()); diff --git a/core/tests/coretests/src/android/widget/TextViewActivityTest.java b/core/tests/coretests/src/android/widget/TextViewActivityTest.java index bbca12f9d457..69e5670f02d5 100644 --- a/core/tests/coretests/src/android/widget/TextViewActivityTest.java +++ b/core/tests/coretests/src/android/widget/TextViewActivityTest.java @@ -325,9 +325,9 @@ public class TextViewActivityTest { TextClassificationManager textClassificationManager = mActivity.getSystemService(TextClassificationManager.class); TextClassifier textClassifier = textClassificationManager.getTextClassifier(); - SpannableString content = new SpannableString("Call me at +19148277737"); + Spannable content = new SpannableString("Call me at +19148277737"); TextLinks links = textClassifier.generateLinks(content); - links.apply(content, null); + links.apply(content, TextLinks.APPLY_STRATEGY_REPLACE, null); mActivityRule.runOnUiThread(() -> { textView.setText(content); diff --git a/core/tests/coretests/src/com/android/internal/app/IntentForwarderActivityTest.java b/core/tests/coretests/src/com/android/internal/app/IntentForwarderActivityTest.java index c0bc3a8eeb9e..b18fa747557d 100644 --- a/core/tests/coretests/src/com/android/internal/app/IntentForwarderActivityTest.java +++ b/core/tests/coretests/src/com/android/internal/app/IntentForwarderActivityTest.java @@ -24,7 +24,6 @@ import android.content.pm.IPackageManager; import android.content.pm.PackageManager; import android.content.pm.UserInfo; import android.os.Bundle; -import android.os.IBinder; import android.os.UserHandle; import android.os.UserManager; import android.support.test.InstrumentationRegistry; @@ -270,8 +269,8 @@ public class IntentForwarderActivityTest { } @Override - public void startActivityAsCaller(Intent intent, @Nullable Bundle options, - IBinder permissionToken, boolean ignoreTargetSecurity, int userId) { + public void startActivityAsCaller(Intent intent, @Nullable Bundle options, boolean + ignoreTargetSecurity, int userId) { mStartActivityIntent = intent; mUserIdActivityLaunchedIn = userId; } @@ -294,4 +293,4 @@ public class IntentForwarderActivityTest { return mPm; } } -} +}
\ No newline at end of file diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyTestServices/Android.mk b/core/tests/hosttests/test-apps/MultiDexLegacyTestServices/Android.mk index 99bcd6c62b56..a6c537373f26 100644 --- a/core/tests/hosttests/test-apps/MultiDexLegacyTestServices/Android.mk +++ b/core/tests/hosttests/test-apps/MultiDexLegacyTestServices/Android.mk @@ -36,10 +36,8 @@ LOCAL_DEX_PREOPT := false include $(BUILD_PACKAGE) -ifndef LOCAL_JACK_ENABLED $(mainDexList): $(full_classes_proguard_jar) | $(MAINDEXCLASSES) $(hide) mkdir -p $(dir $@) $(MAINDEXCLASSES) $< 1>$@ $(built_dex_intermediate): $(mainDexList) -endif diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyTestServices/AndroidManifest.xml b/core/tests/hosttests/test-apps/MultiDexLegacyTestServices/AndroidManifest.xml index e30689203c1b..7cd01e54a64e 100644 --- a/core/tests/hosttests/test-apps/MultiDexLegacyTestServices/AndroidManifest.xml +++ b/core/tests/hosttests/test-apps/MultiDexLegacyTestServices/AndroidManifest.xml @@ -7,6 +7,8 @@ <uses-sdk android:minSdkVersion="9" android:targetSdkVersion="19" /> + <!-- Required for com.android.framework.multidexlegacytestservices.test2 --> + <uses-permission android:name="android.permission.KILL_BACKGROUND_PROCESSES"/> <application android:label="MultiDexLegacyTestServices"> @@ -124,6 +126,6 @@ <action android:name="com.android.framework.multidexlegacytestservices.action.Service19" /> </intent-filter> </service> - </application> + </application> </manifest> diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyTestServices/src/com/android/framework/multidexlegacytestservices/AbstractService.java b/core/tests/hosttests/test-apps/MultiDexLegacyTestServices/src/com/android/framework/multidexlegacytestservices/AbstractService.java index 7b83999d0ca9..cb0a591559db 100644 --- a/core/tests/hosttests/test-apps/MultiDexLegacyTestServices/src/com/android/framework/multidexlegacytestservices/AbstractService.java +++ b/core/tests/hosttests/test-apps/MultiDexLegacyTestServices/src/com/android/framework/multidexlegacytestservices/AbstractService.java @@ -60,35 +60,40 @@ public abstract class AbstractService extends Service implements Runnable { // of the result file will be too big. RandomAccessFile raf = new RandomAccessFile(resultFile, "rw"); raf.seek(raf.length()); - Log.i(TAG, "Writing 0x42434445 at " + raf.length() + " in " + resultFile.getPath()); - raf.writeInt(0x42434445); + if (raf.length() == 0) { + Log.i(TAG, "Writing 0x42434445 at " + raf.length() + " in " + resultFile.getPath()); + raf.writeInt(0x42434445); + } else { + Log.w(TAG, "Service was restarted appending 0x42434445 twice at " + raf.length() + + " in " + resultFile.getPath()); + raf.writeInt(0x42434445); + raf.writeInt(0x42434445); + } raf.close(); - } catch (IOException e) { - e.printStackTrace(); - } - MultiDex.install(applicationContext); - Log.i(TAG, "Multi dex installation done."); + MultiDex.install(applicationContext); + Log.i(TAG, "Multi dex installation done."); - int value = getValue(); - Log.i(TAG, "Saving the result (" + value + ") to " + resultFile.getPath()); - try { + int value = getValue(); + Log.i(TAG, "Saving the result (" + value + ") to " + resultFile.getPath()); // Append the check value in result file, keeping the constant values already written. - RandomAccessFile raf = new RandomAccessFile(resultFile, "rw"); + raf = new RandomAccessFile(resultFile, "rw"); raf.seek(raf.length()); Log.i(TAG, "Writing result at " + raf.length() + " in " + resultFile.getPath()); raf.writeInt(value); raf.close(); } catch (IOException e) { - e.printStackTrace(); - } - try { - // Writing end of processing flags, the existence of the file is the criteria - RandomAccessFile raf = new RandomAccessFile(new File(applicationContext.getFilesDir(), getId() + ".complete"), "rw"); - Log.i(TAG, "creating complete file " + resultFile.getPath()); - raf.writeInt(0x32333435); - raf.close(); - } catch (IOException e) { - e.printStackTrace(); + throw new AssertionError(e); + } finally { + try { + // Writing end of processing flags, the existence of the file is the criteria + RandomAccessFile raf = new RandomAccessFile( + new File(applicationContext.getFilesDir(), getId() + ".complete"), "rw"); + Log.i(TAG, "creating complete file " + resultFile.getPath()); + raf.writeInt(0x32333435); + raf.close(); + } catch (IOException e) { + e.printStackTrace(); + } } } @@ -119,9 +124,10 @@ public abstract class AbstractService extends Service implements Runnable { intermediate = ReflectIntermediateClass.get(45, 80, 20 /* 5 seems enough on a nakasi, using 20 to get some margin */); } catch (Exception e) { - e.printStackTrace(); + throw new AssertionError(e); } - int value = new com.android.framework.multidexlegacytestservices.manymethods.Big001().get1() + + int value = + new com.android.framework.multidexlegacytestservices.manymethods.Big001().get1() + new com.android.framework.multidexlegacytestservices.manymethods.Big002().get2() + new com.android.framework.multidexlegacytestservices.manymethods.Big003().get3() + new com.android.framework.multidexlegacytestservices.manymethods.Big004().get4() + diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyTestServicesTests2/Android.mk b/core/tests/hosttests/test-apps/MultiDexLegacyTestServicesTests2/Android.mk new file mode 100644 index 000000000000..f3d98a88d485 --- /dev/null +++ b/core/tests/hosttests/test-apps/MultiDexLegacyTestServicesTests2/Android.mk @@ -0,0 +1,33 @@ +# Copyright (C) 2014 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +LOCAL_PATH:= $(call my-dir) + +include $(CLEAR_VARS) + +LOCAL_MODULE_TAGS := tests + +LOCAL_SRC_FILES := $(call all-java-files-under, src) + +LOCAL_PACKAGE_NAME := MultiDexLegacyTestServicesTests2 + +LOCAL_JAVA_LIBRARIES := android-support-multidex +LOCAL_STATIC_JAVA_LIBRARIES := android-support-test + +LOCAL_SDK_VERSION := 9 + +LOCAL_DEX_PREOPT := false + +include $(BUILD_PACKAGE) + diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyTestServicesTests2/AndroidManifest.xml b/core/tests/hosttests/test-apps/MultiDexLegacyTestServicesTests2/AndroidManifest.xml new file mode 100644 index 000000000000..0ab29591be18 --- /dev/null +++ b/core/tests/hosttests/test-apps/MultiDexLegacyTestServicesTests2/AndroidManifest.xml @@ -0,0 +1,18 @@ +<?xml version="1.0" encoding="utf-8"?> +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.framework.multidexlegacytestservices.test2" + android:versionCode="1" + android:versionName="1.0" > + + <uses-sdk android:minSdkVersion="9" /> + <uses-permission android:name="android.permission.KILL_BACKGROUND_PROCESSES"/> + <instrumentation + android:name="android.support.test.runner.AndroidJUnitRunner" + android:targetPackage="com.android.framework.multidexlegacytestservices" /> + + <application + android:label="multidexlegacytestservices.test2" > + <uses-library android:name="android.test.runner" /> + </application> + +</manifest>
\ No newline at end of file diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyTestServicesTests2/src/com/android/framework/multidexlegacytestservices/test2/ServicesTests.java b/core/tests/hosttests/test-apps/MultiDexLegacyTestServicesTests2/src/com/android/framework/multidexlegacytestservices/test2/ServicesTests.java new file mode 100644 index 000000000000..900f20387c49 --- /dev/null +++ b/core/tests/hosttests/test-apps/MultiDexLegacyTestServicesTests2/src/com/android/framework/multidexlegacytestservices/test2/ServicesTests.java @@ -0,0 +1,381 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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.framework.multidexlegacytestservices.test2; + +import android.app.ActivityManager; +import android.content.Context; +import android.content.Intent; +import android.support.test.InstrumentationRegistry; +import android.support.test.runner.AndroidJUnit4; +import android.util.Log; +import java.io.File; +import java.io.FileFilter; +import java.io.IOException; +import java.io.RandomAccessFile; +import java.util.concurrent.TimeoutException; +import junit.framework.Assert; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +/** + * Run the tests with: <code>adb shell am instrument -w + * com.android.framework.multidexlegacytestservices.test2/android.support.test.runner.AndroidJUnitRunner + * </code> + */ +@RunWith(AndroidJUnit4.class) +public class ServicesTests { + private static final String TAG = "ServicesTests"; + + static { + Log.i(TAG, "Initializing"); + } + + private class ExtensionFilter implements FileFilter { + private final String ext; + + public ExtensionFilter(String ext) { + this.ext = ext; + } + + @Override + public boolean accept(File file) { + return file.getName().endsWith(ext); + } + } + + private class ExtractedZipFilter extends ExtensionFilter { + public ExtractedZipFilter() { + super(".zip"); + } + + @Override + public boolean accept(File file) { + return super.accept(file) && !file.getName().startsWith("tmp-"); + } + } + + private static final int ENDHDR = 22; + + private static final String SERVICE_BASE_ACTION = + "com.android.framework.multidexlegacytestservices.action.Service"; + private static final int MIN_SERVICE = 1; + private static final int MAX_SERVICE = 19; + private static final String COMPLETION_SUCCESS = "Success"; + + private File targetFilesDir; + + @Before + public void setup() throws Exception { + Log.i(TAG, "setup"); + killServices(); + + File applicationDataDir = + new File(InstrumentationRegistry.getTargetContext().getApplicationInfo().dataDir); + clearDirContent(applicationDataDir); + targetFilesDir = InstrumentationRegistry.getTargetContext().getFilesDir(); + + Log.i(TAG, "setup done"); + } + + @Test + public void testStressConcurentLaunch() throws Exception { + startServices(); + waitServicesCompletion(); + String completionStatus = getServicesCompletionStatus(); + if (completionStatus != COMPLETION_SUCCESS) { + Assert.fail(completionStatus); + } + } + + @Test + public void testRecoverFromZipCorruption() throws Exception { + int serviceId = 1; + // Ensure extraction. + initServicesWorkFiles(); + startService(serviceId); + waitServicesCompletion(serviceId); + + // Corruption of the extracted zips. + tamperAllExtractedZips(); + + killServices(); + checkRecover(); + } + + @Test + public void testRecoverFromDexCorruption() throws Exception { + int serviceId = 1; + // Ensure extraction. + initServicesWorkFiles(); + startService(serviceId); + waitServicesCompletion(serviceId); + + // Corruption of the odex files. + tamperAllOdex(); + + killServices(); + checkRecover(); + } + + @Test + public void testRecoverFromZipCorruptionStressTest() throws Exception { + Thread startServices = + new Thread() { + @Override + public void run() { + startServices(); + } + }; + + startServices.start(); + + // Start services lasts more than 80s, lets cause a few corruptions during this interval. + for (int i = 0; i < 80; i++) { + Thread.sleep(1000); + tamperAllExtractedZips(); + } + startServices.join(); + try { + waitServicesCompletion(); + } catch (TimeoutException e) { + // Can happen. + } + + killServices(); + checkRecover(); + } + + @Test + public void testRecoverFromDexCorruptionStressTest() throws Exception { + Thread startServices = + new Thread() { + @Override + public void run() { + startServices(); + } + }; + + startServices.start(); + + // Start services lasts more than 80s, lets cause a few corruptions during this interval. + for (int i = 0; i < 80; i++) { + Thread.sleep(1000); + tamperAllOdex(); + } + startServices.join(); + try { + waitServicesCompletion(); + } catch (TimeoutException e) { + // Will probably happen most of the time considering what we're doing... + } + + killServices(); + checkRecover(); + } + + private static void clearDirContent(File dir) { + for (File subElement : dir.listFiles()) { + if (subElement.isDirectory()) { + clearDirContent(subElement); + } + if (!subElement.delete()) { + throw new AssertionError("Failed to clear '" + subElement.getAbsolutePath() + "'"); + } + } + } + + private void startServices() { + Log.i(TAG, "start services"); + initServicesWorkFiles(); + for (int i = MIN_SERVICE; i <= MAX_SERVICE; i++) { + startService(i); + try { + Thread.sleep((i - 1) * (1 << (i / 5))); + } catch (InterruptedException e) { + } + } + } + + private void startService(int serviceId) { + Log.i(TAG, "start service " + serviceId); + InstrumentationRegistry.getContext().startService(new Intent(SERVICE_BASE_ACTION + serviceId)); + } + + private void initServicesWorkFiles() { + for (int i = MIN_SERVICE; i <= MAX_SERVICE; i++) { + File resultFile = new File(targetFilesDir, "Service" + i); + resultFile.delete(); + Assert.assertFalse( + "Failed to delete result file '" + resultFile.getAbsolutePath() + "'.", + resultFile.exists()); + File completeFile = new File(targetFilesDir, "Service" + i + ".complete"); + completeFile.delete(); + Assert.assertFalse( + "Failed to delete completion file '" + completeFile.getAbsolutePath() + "'.", + completeFile.exists()); + } + } + + private void waitServicesCompletion() throws TimeoutException { + Log.i(TAG, "start sleeping"); + int attempt = 0; + int maxAttempt = 50; // 10 is enough for a nexus S + do { + try { + Thread.sleep(5000); + } catch (InterruptedException e) { + } + attempt++; + if (attempt >= maxAttempt) { + throw new TimeoutException(); + } + } while (!areAllServicesCompleted()); + } + + private void waitServicesCompletion(int serviceId) throws TimeoutException { + Log.i(TAG, "start sleeping"); + int attempt = 0; + int maxAttempt = 50; // 10 is enough for a nexus S + do { + try { + Thread.sleep(5000); + } catch (InterruptedException e) { + } + attempt++; + if (attempt >= maxAttempt) { + throw new TimeoutException(); + } + } while (isServiceRunning(serviceId)); + } + + private String getServicesCompletionStatus() { + String status = COMPLETION_SUCCESS; + for (int i = MIN_SERVICE; i <= MAX_SERVICE; i++) { + File resultFile = new File(targetFilesDir, "Service" + i); + if (!resultFile.isFile()) { + status = "Service" + i + " never completed."; + break; + } + if (resultFile.length() != 8) { + status = "Service" + i + " was restarted."; + break; + } + } + Log.i(TAG, "Services completion status: " + status); + return status; + } + + private String getServiceCompletionStatus(int serviceId) { + String status = COMPLETION_SUCCESS; + File resultFile = new File(targetFilesDir, "Service" + serviceId); + if (!resultFile.isFile()) { + status = "Service" + serviceId + " never completed."; + } else if (resultFile.length() != 8) { + status = "Service" + serviceId + " was restarted."; + } + Log.i(TAG, "Service " + serviceId + " completion status: " + status); + return status; + } + + private boolean areAllServicesCompleted() { + for (int i = MIN_SERVICE; i <= MAX_SERVICE; i++) { + if (isServiceRunning(i)) { + return false; + } + } + return true; + } + + private boolean isServiceRunning(int i) { + File completeFile = new File(targetFilesDir, "Service" + i + ".complete"); + return !completeFile.exists(); + } + + private File getSecondaryFolder() { + File dir = + new File( + new File( + InstrumentationRegistry.getTargetContext().getApplicationInfo().dataDir, + "code_cache"), + "secondary-dexes"); + Assert.assertTrue(dir.getAbsolutePath(), dir.isDirectory()); + return dir; + } + + private void tamperAllExtractedZips() throws IOException { + // First attempt was to just overwrite zip entries but keep central directory, this was no + // trouble for Dalvik that was just ignoring those zip and using the odex files. + Log.i(TAG, "Tamper extracted zip files by overwriting all content by '\\0's."); + byte[] zeros = new byte[4 * 1024]; + // Do not tamper tmp zip during their extraction. + for (File zip : getSecondaryFolder().listFiles(new ExtractedZipFilter())) { + long fileLength = zip.length(); + Assert.assertTrue(fileLength > ENDHDR); + zip.setWritable(true); + RandomAccessFile raf = new RandomAccessFile(zip, "rw"); + try { + int index = 0; + while (index < fileLength) { + int length = (int) Math.min(zeros.length, fileLength - index); + raf.write(zeros, 0, length); + index += length; + } + } finally { + raf.close(); + } + } + } + + private void tamperAllOdex() throws IOException { + Log.i(TAG, "Tamper odex files by overwriting some content by '\\0's."); + byte[] zeros = new byte[4 * 1024]; + // I think max size would be 40 (u1[8] + 8 u4) but it's a test so lets take big margins. + int savedSizeForOdexHeader = 80; + for (File odex : getSecondaryFolder().listFiles(new ExtensionFilter(".dex"))) { + long fileLength = odex.length(); + Assert.assertTrue(fileLength > zeros.length + savedSizeForOdexHeader); + odex.setWritable(true); + RandomAccessFile raf = new RandomAccessFile(odex, "rw"); + try { + raf.seek(savedSizeForOdexHeader); + raf.write(zeros, 0, zeros.length); + } finally { + raf.close(); + } + } + } + + private void checkRecover() throws TimeoutException { + Log.i(TAG, "Check recover capability"); + int serviceId = 1; + // Start one service and check it was able to run correctly even if a previous run failed. + initServicesWorkFiles(); + startService(serviceId); + waitServicesCompletion(serviceId); + String completionStatus = getServiceCompletionStatus(serviceId); + if (completionStatus != COMPLETION_SUCCESS) { + Assert.fail(completionStatus); + } + } + + private void killServices() { + ((ActivityManager) + InstrumentationRegistry.getContext().getSystemService(Context.ACTIVITY_SERVICE)) + .killBackgroundProcesses("com.android.framework.multidexlegacytestservices"); + } +} diff --git a/data/etc/platform.xml b/data/etc/platform.xml index 09192f4fa72f..04006b1733a3 100644 --- a/data/etc/platform.xml +++ b/data/etc/platform.xml @@ -179,6 +179,8 @@ <!-- This is a list of all the libraries available for application code to link against. --> + <library name="android.test.base" + file="/system/framework/android.test.base.jar" /> <library name="android.test.mock" file="/system/framework/android.test.mock.jar" /> <library name="android.test.runner" diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml index 6c8aaf0b1e8c..8addffbb02db 100644 --- a/data/etc/privapp-permissions-platform.xml +++ b/data/etc/privapp-permissions-platform.xml @@ -369,7 +369,6 @@ applications that come with the platform <permission name="android.permission.READ_PRIVILEGED_PHONE_STATE"/> <permission name="android.permission.REAL_GET_TASKS"/> <permission name="android.permission.RECEIVE_MEDIA_RESOURCE_USAGE"/> - <permission name="android.permission.START_ACTIVITY_AS_CALLER"/> <permission name="android.permission.START_TASKS_FROM_RECENTS"/> <permission name="android.permission.STATUS_BAR"/> <permission name="android.permission.STOP_APP_SWITCHES"/> diff --git a/graphics/java/android/graphics/ImageDecoder.java b/graphics/java/android/graphics/ImageDecoder.java index 3ead5911d890..bbf214559459 100644 --- a/graphics/java/android/graphics/ImageDecoder.java +++ b/graphics/java/android/graphics/ImageDecoder.java @@ -148,7 +148,7 @@ public final class ImageDecoder implements AutoCloseable { throw new FileNotFoundException(mUri.toString()); } - return createFromStream(is); + return createFromStream(is, true); } final FileDescriptor fd = assetFd.getFileDescriptor(); @@ -160,7 +160,7 @@ public final class ImageDecoder implements AutoCloseable { Os.lseek(fd, offset, SEEK_SET); decoder = nCreate(fd); } catch (ErrnoException e) { - decoder = createFromStream(new FileInputStream(fd)); + decoder = createFromStream(new FileInputStream(fd), true); } } finally { if (decoder == null) { @@ -180,7 +180,7 @@ public final class ImageDecoder implements AutoCloseable { try { Os.lseek(fd, 0, SEEK_CUR); } catch (ErrnoException e) { - return createFromStream(stream); + return createFromStream(stream, true); } ImageDecoder decoder = null; @@ -191,13 +191,15 @@ public final class ImageDecoder implements AutoCloseable { IoUtils.closeQuietly(stream); } else { decoder.mInputStream = stream; + decoder.mOwnsInputStream = true; } } return decoder; } @NonNull - private static ImageDecoder createFromStream(@NonNull InputStream is) throws IOException { + private static ImageDecoder createFromStream(@NonNull InputStream is, + boolean closeInputStream) throws IOException { // Arbitrary size matches BitmapFactory. byte[] storage = new byte[16 * 1024]; ImageDecoder decoder = null; @@ -205,9 +207,12 @@ public final class ImageDecoder implements AutoCloseable { decoder = nCreate(is, storage); } finally { if (decoder == null) { - IoUtils.closeQuietly(is); + if (closeInputStream) { + IoUtils.closeQuietly(is); + } } else { decoder.mInputStream = is; + decoder.mOwnsInputStream = closeInputStream; decoder.mTempStorage = storage; } } @@ -215,6 +220,9 @@ public final class ImageDecoder implements AutoCloseable { return decoder; } + /** + * For backwards compatibility, this does *not* close the InputStream. + */ private static class InputStreamSource extends Source { InputStreamSource(Resources res, InputStream is, int inputDensity) { if (is == null) { @@ -244,7 +252,7 @@ public final class ImageDecoder implements AutoCloseable { } InputStream is = mInputStream; mInputStream = null; - return createFromStream(is); + return createFromStream(is, false); } } } @@ -293,6 +301,7 @@ public final class ImageDecoder implements AutoCloseable { IoUtils.closeQuietly(is); } else { decoder.mInputStream = is; + decoder.mOwnsInputStream = true; } } return decoder; @@ -436,6 +445,7 @@ public final class ImageDecoder implements AutoCloseable { // Objects for interacting with the input. private InputStream mInputStream; + private boolean mOwnsInputStream; private byte[] mTempStorage; private AssetFileDescriptor mAssetFd; private final AtomicBoolean mClosed = new AtomicBoolean(); @@ -811,7 +821,9 @@ public final class ImageDecoder implements AutoCloseable { nClose(mNativePtr); mNativePtr = 0; - IoUtils.closeQuietly(mInputStream); + if (mOwnsInputStream) { + IoUtils.closeQuietly(mInputStream); + } IoUtils.closeQuietly(mAssetFd); mInputStream = null; diff --git a/graphics/java/android/graphics/drawable/AnimatedImageDrawable.java b/graphics/java/android/graphics/drawable/AnimatedImageDrawable.java index 0ec19f9a4aee..bd49b87ec200 100644 --- a/graphics/java/android/graphics/drawable/AnimatedImageDrawable.java +++ b/graphics/java/android/graphics/drawable/AnimatedImageDrawable.java @@ -16,6 +16,8 @@ package android.graphics.drawable; +import dalvik.annotation.optimization.FastNative; + import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; @@ -60,7 +62,6 @@ public class AnimatedImageDrawable extends Drawable implements Animatable2 { private int mIntrinsicHeight; private boolean mStarting; - private boolean mRunning; private Handler mHandler; @@ -222,8 +223,8 @@ public class AnimatedImageDrawable extends Drawable implements Animatable2 { return mIntrinsicHeight; } - // nDraw returns -2 if the animation is not running. - private static final int NOT_RUNNING = -2; + // nDraw returns -1 if the animation has finished. + private static final int FINISHED = -1; @Override public void draw(@NonNull Canvas canvas) { @@ -235,8 +236,6 @@ public class AnimatedImageDrawable extends Drawable implements Animatable2 { mStarting = false; postOnAnimationStart(); - - mRunning = true; } long nextUpdate = nDraw(mState.mNativePtr, canvas.getNativeCanvasWrapper()); @@ -244,12 +243,9 @@ public class AnimatedImageDrawable extends Drawable implements Animatable2 { // will manage the animation if (nextUpdate > 0) { scheduleSelf(mRunnable, nextUpdate); - } else if (nextUpdate == NOT_RUNNING) { - // -2 means the animation ended, when drawn in software mode. - if (mRunning) { - postOnAnimationEnd(); - mRunning = false; - } + } else if (nextUpdate == FINISHED) { + // This means the animation was drawn in software mode and ended. + postOnAnimationEnd(); } } @@ -292,6 +288,19 @@ public class AnimatedImageDrawable extends Drawable implements Animatable2 { return PixelFormat.TRANSLUCENT; } + @Override + public boolean setVisible(boolean visible, boolean restart) { + if (!super.setVisible(visible, restart)) { + return false; + } + + if (!visible) { + nMarkInvisible(mState.mNativePtr); + } + + return true; + } + // Animatable overrides /** * Return whether the animation is currently running. @@ -301,13 +310,17 @@ public class AnimatedImageDrawable extends Drawable implements Animatable2 { */ @Override public boolean isRunning() { - return mRunning; + if (mState == null) { + throw new IllegalStateException("called isRunning on empty AnimatedImageDrawable"); + } + return nIsRunning(mState.mNativePtr); } /** * Start the animation. * - * <p>Does nothing if the animation is already running. + * <p>Does nothing if the animation is already running. If the animation is stopped, + * this will reset it.</p> * * <p>If the animation starts, this will call * {@link Animatable2.AnimationCallback#onAnimationStart}.</p> @@ -336,7 +349,6 @@ public class AnimatedImageDrawable extends Drawable implements Animatable2 { throw new IllegalStateException("called stop on empty AnimatedImageDrawable"); } nStop(mState.mNativePtr); - mRunning = false; } // Animatable2 overrides @@ -405,18 +417,29 @@ public class AnimatedImageDrawable extends Drawable implements Animatable2 { private static native long nCreate(long nativeImageDecoder, @Nullable ImageDecoder decoder, int width, int height, Rect cropRect) throws IOException; + @FastNative private static native long nGetNativeFinalizer(); private static native long nDraw(long nativePtr, long canvasNativePtr); + @FastNative private static native void nSetAlpha(long nativePtr, int alpha); + @FastNative private static native int nGetAlpha(long nativePtr); + @FastNative private static native void nSetColorFilter(long nativePtr, long nativeFilter); + @FastNative private static native boolean nIsRunning(long nativePtr); // Return whether the animation started. + @FastNative private static native boolean nStart(long nativePtr); + @FastNative private static native void nStop(long nativePtr); + @FastNative private static native void nSetLoopCount(long nativePtr, int loopCount); // Pass the drawable down to native so it can call onAnimationEnd. private static native void nSetOnAnimationEndListener(long nativePtr, @Nullable AnimatedImageDrawable drawable); + @FastNative private static native long nNativeByteSize(long nativePtr); + @FastNative + private static native void nMarkInvisible(long nativePtr); } diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp index 17f9b7cd62fc..3323bce8b5ad 100644 --- a/libs/hwui/Android.bp +++ b/libs/hwui/Android.bp @@ -138,6 +138,7 @@ cc_defaults { srcs: [ "hwui/AnimatedImageDrawable.cpp", + "hwui/AnimatedImageThread.cpp", "hwui/Bitmap.cpp", "font/CacheTexture.cpp", "font/Font.cpp", diff --git a/libs/hwui/hwui/AnimatedImageDrawable.cpp b/libs/hwui/hwui/AnimatedImageDrawable.cpp index e01bf3d003e6..5356d3bfc7c9 100644 --- a/libs/hwui/hwui/AnimatedImageDrawable.cpp +++ b/libs/hwui/hwui/AnimatedImageDrawable.cpp @@ -15,21 +15,21 @@ */ #include "AnimatedImageDrawable.h" +#include "AnimatedImageThread.h" -#include "thread/Task.h" -#include "thread/TaskManager.h" -#include "thread/TaskProcessor.h" #include "utils/TraceUtils.h" #include <SkPicture.h> #include <SkRefCnt.h> -#include <SkTime.h> #include <SkTLazy.h> +#include <SkTime.h> namespace android { AnimatedImageDrawable::AnimatedImageDrawable(sk_sp<SkAnimatedImage> animatedImage) - : mSkAnimatedImage(std::move(animatedImage)) { } + : mSkAnimatedImage(std::move(animatedImage)) { + mTimeToShowNextSnapshot = mSkAnimatedImage->currentFrameDuration(); +} void AnimatedImageDrawable::syncProperties() { mAlpha = mStagingAlpha; @@ -37,88 +37,81 @@ void AnimatedImageDrawable::syncProperties() { } bool AnimatedImageDrawable::start() { - SkAutoExclusive lock(mLock); - if (mSkAnimatedImage->isRunning()) { + if (mRunning) { return false; } - if (!mSnapshot) { - mSnapshot.reset(mSkAnimatedImage->newPictureSnapshot()); - } + // This will trigger a reset. + mFinished = true; - // While stopped, update() does not decode, but it does advance the time. - // This prevents us from skipping ahead when we resume. - const double currentTime = SkTime::GetMSecs(); - mSkAnimatedImage->update(currentTime); - mSkAnimatedImage->start(); - return mSkAnimatedImage->isRunning(); + mRunning = true; + return true; } void AnimatedImageDrawable::stop() { - SkAutoExclusive lock(mLock); - mSkAnimatedImage->stop(); + mRunning = false; } bool AnimatedImageDrawable::isRunning() { - return mSkAnimatedImage->isRunning(); + return mRunning; } -// This is really a Task<void> but that doesn't really work when Future<> -// expects to be able to get/set a value -class AnimatedImageDrawable::AnimatedImageTask : public uirenderer::Task<bool> { -public: - AnimatedImageTask(AnimatedImageDrawable* animatedImageDrawable) - : mAnimatedImageDrawable(sk_ref_sp(animatedImageDrawable)) {} - - sk_sp<AnimatedImageDrawable> mAnimatedImageDrawable; - bool mIsCompleted = false; -}; - -class AnimatedImageDrawable::AnimatedImageTaskProcessor : public uirenderer::TaskProcessor<bool> { -public: - explicit AnimatedImageTaskProcessor(uirenderer::TaskManager* taskManager) - : uirenderer::TaskProcessor<bool>(taskManager) {} - ~AnimatedImageTaskProcessor() {} - - virtual void onProcess(const sp<uirenderer::Task<bool>>& task) override { - ATRACE_NAME("Updating AnimatedImageDrawables"); - AnimatedImageTask* t = static_cast<AnimatedImageTask*>(task.get()); - t->mAnimatedImageDrawable->update(); - t->mIsCompleted = true; - task->setResult(true); - }; -}; - -void AnimatedImageDrawable::scheduleUpdate(uirenderer::TaskManager* taskManager) { - if (!mSkAnimatedImage->isRunning() - || (mDecodeTask.get() != nullptr && !mDecodeTask->mIsCompleted)) { - return; - } - - if (!mDecodeTaskProcessor.get()) { - mDecodeTaskProcessor = new AnimatedImageTaskProcessor(taskManager); - } - - // TODO get one frame ahead and only schedule updates when you need to replenish - mDecodeTask = new AnimatedImageTask(this); - mDecodeTaskProcessor->add(mDecodeTask); +bool AnimatedImageDrawable::nextSnapshotReady() const { + return mNextSnapshot.valid() && + mNextSnapshot.wait_for(std::chrono::seconds(0)) == std::future_status::ready; } -void AnimatedImageDrawable::update() { - SkAutoExclusive lock(mLock); +// Only called on the RenderThread while UI thread is locked. +bool AnimatedImageDrawable::isDirty() { + const double currentTime = SkTime::GetMSecs(); + const double lastWallTime = mLastWallTime; - if (!mSkAnimatedImage->isRunning()) { - return; + mLastWallTime = currentTime; + if (!mRunning) { + mDidDraw = false; + return false; } - const double currentTime = SkTime::GetMSecs(); - if (currentTime >= mNextFrameTime) { - mNextFrameTime = mSkAnimatedImage->update(currentTime); - mSnapshot.reset(mSkAnimatedImage->newPictureSnapshot()); - mIsDirty = true; + std::unique_lock lock{mSwapLock}; + if (mDidDraw) { + mCurrentTime += currentTime - lastWallTime; + mDidDraw = false; + } + + if (!mNextSnapshot.valid()) { + // Need to trigger onDraw in order to start decoding the next frame. + return true; + } + + return nextSnapshotReady() && mCurrentTime >= mTimeToShowNextSnapshot; +} + +// Only called on the AnimatedImageThread. +AnimatedImageDrawable::Snapshot AnimatedImageDrawable::decodeNextFrame() { + Snapshot snap; + { + std::unique_lock lock{mImageLock}; + snap.mDuration = mSkAnimatedImage->decodeNextFrame(); + snap.mPic.reset(mSkAnimatedImage->newPictureSnapshot()); } + + return snap; } +// Only called on the AnimatedImageThread. +AnimatedImageDrawable::Snapshot AnimatedImageDrawable::reset() { + Snapshot snap; + { + std::unique_lock lock{mImageLock}; + mSkAnimatedImage->reset(); + snap.mPic.reset(mSkAnimatedImage->newPictureSnapshot()); + snap.mDuration = mSkAnimatedImage->currentFrameDuration(); + } + + return snap; +} + +// Only called on the RenderThread. void AnimatedImageDrawable::onDraw(SkCanvas* canvas) { SkTLazy<SkPaint> lazyPaint; if (mAlpha != SK_AlphaOPAQUE || mColorFilter.get()) { @@ -128,25 +121,71 @@ void AnimatedImageDrawable::onDraw(SkCanvas* canvas) { lazyPaint.get()->setFilterQuality(kLow_SkFilterQuality); } - SkAutoExclusive lock(mLock); - if (mSnapshot) { - canvas->drawPicture(mSnapshot, nullptr, lazyPaint.getMaybeNull()); - } else { - // TODO: we could potentially keep the cached surface around if there is a paint and we know - // the drawable is attached to the view system + mDidDraw = true; + + bool drewDirectly = false; + if (!mSnapshot.mPic) { + // The image is not animating, and never was. Draw directly from + // mSkAnimatedImage. SkAutoCanvasRestore acr(canvas, false); if (lazyPaint.isValid()) { canvas->saveLayer(mSkAnimatedImage->getBounds(), lazyPaint.get()); } + + std::unique_lock lock{mImageLock}; mSkAnimatedImage->draw(canvas); + drewDirectly = true; + } + + if (mRunning && mFinished) { + auto& thread = uirenderer::AnimatedImageThread::getInstance(); + mNextSnapshot = thread.reset(sk_ref_sp(this)); + mFinished = false; + } + + bool finalFrame = false; + if (mRunning && nextSnapshotReady()) { + std::unique_lock lock{mSwapLock}; + if (mCurrentTime >= mTimeToShowNextSnapshot) { + mSnapshot = mNextSnapshot.get(); + const double timeToShowCurrentSnap = mTimeToShowNextSnapshot; + if (mSnapshot.mDuration == SkAnimatedImage::kFinished) { + finalFrame = true; + mRunning = false; + mFinished = true; + } else { + mTimeToShowNextSnapshot += mSnapshot.mDuration; + if (mCurrentTime >= mTimeToShowNextSnapshot) { + // This would mean showing the current frame very briefly. It's + // possible that not being displayed for a time resulted in + // mCurrentTime being far ahead. Prevent showing many frames + // rapidly by going back to the beginning of this frame time. + mCurrentTime = timeToShowCurrentSnap; + } + } + } + } + + if (mRunning && !mNextSnapshot.valid()) { + auto& thread = uirenderer::AnimatedImageThread::getInstance(); + mNextSnapshot = thread.decodeNextFrame(sk_ref_sp(this)); } - mIsDirty = false; + if (!drewDirectly) { + // No other thread will modify mCurrentSnap so this should be safe to + // use without locking. + canvas->drawPicture(mSnapshot.mPic, nullptr, lazyPaint.getMaybeNull()); + } + + if (finalFrame) { + if (mEndListener) { + mEndListener->onAnimationEnd(); + mEndListener = nullptr; + } + } } double AnimatedImageDrawable::drawStaging(SkCanvas* canvas) { - // update the drawable with the current time - double nextUpdate = mSkAnimatedImage->update(SkTime::GetMSecs()); SkAutoCanvasRestore acr(canvas, false); if (mStagingAlpha != SK_AlphaOPAQUE || mStagingColorFilter.get()) { SkPaint paint; @@ -154,8 +193,59 @@ double AnimatedImageDrawable::drawStaging(SkCanvas* canvas) { paint.setColorFilter(mStagingColorFilter); canvas->saveLayer(mSkAnimatedImage->getBounds(), &paint); } - canvas->drawDrawable(mSkAnimatedImage.get()); - return nextUpdate; + + if (mFinished && !mRunning) { + // Continue drawing the last frame, and return 0 to indicate no need to + // redraw. + std::unique_lock lock{mImageLock}; + canvas->drawDrawable(mSkAnimatedImage.get()); + return 0.0; + } + + bool update = false; + { + const double currentTime = SkTime::GetMSecs(); + std::unique_lock lock{mSwapLock}; + // mLastWallTime starts off at 0. If it is still 0, just update it to + // the current time and avoid updating + if (mLastWallTime == 0.0) { + mCurrentTime = currentTime; + } else if (mRunning) { + if (mFinished) { + mCurrentTime = currentTime; + { + std::unique_lock lock{mImageLock}; + mSkAnimatedImage->reset(); + } + mTimeToShowNextSnapshot = currentTime + mSkAnimatedImage->currentFrameDuration(); + } else { + mCurrentTime += currentTime - mLastWallTime; + update = mCurrentTime >= mTimeToShowNextSnapshot; + } + } + mLastWallTime = currentTime; + } + + double duration = 0.0; + { + std::unique_lock lock{mImageLock}; + if (update) { + duration = mSkAnimatedImage->decodeNextFrame(); + } + + canvas->drawDrawable(mSkAnimatedImage.get()); + } + + std::unique_lock lock{mSwapLock}; + if (update) { + if (duration == SkAnimatedImage::kFinished) { + mRunning = false; + mFinished = true; + } else { + mTimeToShowNextSnapshot += duration; + } + } + return mTimeToShowNextSnapshot; } -}; // namespace android +} // namespace android diff --git a/libs/hwui/hwui/AnimatedImageDrawable.h b/libs/hwui/hwui/AnimatedImageDrawable.h index df8a5e48d18e..9d84ed5f4470 100644 --- a/libs/hwui/hwui/AnimatedImageDrawable.h +++ b/libs/hwui/hwui/AnimatedImageDrawable.h @@ -17,22 +17,20 @@ #pragma once #include <cutils/compiler.h> +#include <utils/Macros.h> #include <utils/RefBase.h> #include <SkAnimatedImage.h> #include <SkCanvas.h> #include <SkColorFilter.h> #include <SkDrawable.h> -#include <SkMutex.h> +#include <SkPicture.h> -class SkPicture; +#include <future> +#include <mutex> namespace android { -namespace uirenderer { -class TaskManager; -} - class OnAnimationEndListener { public: virtual ~OnAnimationEndListener() {} @@ -41,68 +39,102 @@ public: }; /** - * Native component of android.graphics.drawable.AnimatedImageDrawables.java. This class can be - * drawn into Canvas.h and maintains the state needed to drive the animation from the RenderThread. + * Native component of android.graphics.drawable.AnimatedImageDrawables.java. + * This class can be drawn into Canvas.h and maintains the state needed to drive + * the animation from the RenderThread. */ class ANDROID_API AnimatedImageDrawable : public SkDrawable { public: AnimatedImageDrawable(sk_sp<SkAnimatedImage> animatedImage); /** - * This returns true if the animation has updated and signals that the next draw will contain - * new content. + * This updates the internal time and returns true if the animation needs + * to be redrawn. + * + * This is called on RenderThread, while the UI thread is locked. */ - bool isDirty() const { return mIsDirty; } + bool isDirty(); int getStagingAlpha() const { return mStagingAlpha; } void setStagingAlpha(int alpha) { mStagingAlpha = alpha; } void setStagingColorFilter(sk_sp<SkColorFilter> filter) { mStagingColorFilter = filter; } void syncProperties(); - virtual SkRect onGetBounds() override { - return mSkAnimatedImage->getBounds(); - } + virtual SkRect onGetBounds() override { return mSkAnimatedImage->getBounds(); } + // Draw to software canvas, and return time to next draw. double drawStaging(SkCanvas* canvas); - // Returns true if the animation was started; false otherwise (e.g. it was already running) + // Returns true if the animation was started; false otherwise (e.g. it was + // already running) bool start(); void stop(); bool isRunning(); - void setRepetitionCount(int count) { - mSkAnimatedImage->setRepetitionCount(count); - } + void setRepetitionCount(int count) { mSkAnimatedImage->setRepetitionCount(count); } void setOnAnimationEndListener(std::unique_ptr<OnAnimationEndListener> listener) { mEndListener = std::move(listener); } - void scheduleUpdate(uirenderer::TaskManager* taskManager); + void markInvisible() { mDidDraw = false; } + + struct Snapshot { + sk_sp<SkPicture> mPic; + int mDuration; + + Snapshot() = default; + + Snapshot(Snapshot&&) = default; + Snapshot& operator=(Snapshot&&) = default; + + PREVENT_COPY_AND_ASSIGN(Snapshot); + }; + + // These are only called on AnimatedImageThread. + Snapshot decodeNextFrame(); + Snapshot reset(); protected: virtual void onDraw(SkCanvas* canvas) override; private: - void update(); - sk_sp<SkAnimatedImage> mSkAnimatedImage; - sk_sp<SkPicture> mSnapshot; - SkMutex mLock; + bool mRunning = false; + bool mFinished = false; + + // A snapshot of the current frame to draw. + Snapshot mSnapshot; + + std::future<Snapshot> mNextSnapshot; + + bool nextSnapshotReady() const; + + // When to switch from mSnapshot to mNextSnapshot. + double mTimeToShowNextSnapshot = 0.0; + + // The current time for the drawable itself. + double mCurrentTime = 0.0; + + // The wall clock of the last time we called isDirty. + double mLastWallTime = 0.0; + + // Whether we drew since the last call to isDirty. + bool mDidDraw = false; + + // Locked when assigning snapshots and times. Operations while this is held + // should be short. + std::mutex mSwapLock; + + // Locked when mSkAnimatedImage is being updated or drawn. + std::mutex mImageLock; int mStagingAlpha = SK_AlphaOPAQUE; sk_sp<SkColorFilter> mStagingColorFilter; int mAlpha = SK_AlphaOPAQUE; sk_sp<SkColorFilter> mColorFilter; - double mNextFrameTime = 0.0; - bool mIsDirty = false; - - class AnimatedImageTask; - class AnimatedImageTaskProcessor; - sp<AnimatedImageTask> mDecodeTask; - sp<AnimatedImageTaskProcessor> mDecodeTaskProcessor; std::unique_ptr<OnAnimationEndListener> mEndListener; }; -}; // namespace android +} // namespace android diff --git a/libs/hwui/hwui/AnimatedImageThread.cpp b/libs/hwui/hwui/AnimatedImageThread.cpp new file mode 100644 index 000000000000..c8990039875e --- /dev/null +++ b/libs/hwui/hwui/AnimatedImageThread.cpp @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "AnimatedImageThread.h" + +#include <sys/resource.h> + +namespace android { +namespace uirenderer { + +AnimatedImageThread& AnimatedImageThread::getInstance() { + static AnimatedImageThread* sInstance = new AnimatedImageThread(); + return *sInstance; +} + +AnimatedImageThread::AnimatedImageThread() { + setpriority(PRIO_PROCESS, 0, PRIORITY_NORMAL + PRIORITY_MORE_FAVORABLE); + start("AnimatedImageThread"); +} + +std::future<AnimatedImageDrawable::Snapshot> AnimatedImageThread::decodeNextFrame( + const sk_sp<AnimatedImageDrawable>& drawable) { + return queue().async([drawable]() { return drawable->decodeNextFrame(); }); +} + +std::future<AnimatedImageDrawable::Snapshot> AnimatedImageThread::reset( + const sk_sp<AnimatedImageDrawable>& drawable) { + return queue().async([drawable]() { return drawable->reset(); }); +} + +} // namespace uirenderer +} // namespace android diff --git a/libs/hwui/hwui/AnimatedImageThread.h b/libs/hwui/hwui/AnimatedImageThread.h new file mode 100644 index 000000000000..9e3537430d5a --- /dev/null +++ b/libs/hwui/hwui/AnimatedImageThread.h @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ANIMATEDIMAGETHREAD_H_ +#define ANIMATEDIMAGETHREAD_H_ + +#include "AnimatedImageDrawable.h" +#include "thread/ThreadBase.h" + +#include <SkRefCnt.h> + +namespace android { + +namespace uirenderer { + +class AnimatedImageThread : private ThreadBase { + PREVENT_COPY_AND_ASSIGN(AnimatedImageThread); + +public: + static AnimatedImageThread& getInstance(); + + std::future<AnimatedImageDrawable::Snapshot> decodeNextFrame( + const sk_sp<AnimatedImageDrawable>&); + std::future<AnimatedImageDrawable::Snapshot> reset(const sk_sp<AnimatedImageDrawable>&); + +private: + AnimatedImageThread(); +}; + +} // namespace uirenderer + +} // namespace android + +#endif // ANIMATEDIMAGETHREAD_H_ diff --git a/libs/hwui/hwui/MinikinUtils.cpp b/libs/hwui/hwui/MinikinUtils.cpp index ba877d395c8f..43f46ef758ca 100644 --- a/libs/hwui/hwui/MinikinUtils.cpp +++ b/libs/hwui/hwui/MinikinUtils.cpp @@ -31,7 +31,7 @@ minikin::MinikinPaint MinikinUtils::prepareMinikinPaint(const Paint* paint, const Typeface* typeface) { const Typeface* resolvedFace = Typeface::resolveDefault(typeface); - minikin::MinikinPaint minikinPaint; + minikin::MinikinPaint minikinPaint(resolvedFace->fFontCollection); /* Prepare minikin Paint */ minikinPaint.size = paint->isLinearText() ? paint->getTextSize() : static_cast<int>(paint->getTextSize()); @@ -53,21 +53,20 @@ minikin::Layout MinikinUtils::doLayout(const Paint* paint, minikin::Bidi bidiFla size_t count, size_t bufSize, minikin::MeasuredText* mt, int mtOffset) { minikin::MinikinPaint minikinPaint = prepareMinikinPaint(paint, typeface); - const auto& fc = Typeface::resolveDefault(typeface)->fFontCollection; minikin::Layout layout; if (mt == nullptr) { - layout.doLayout(buf, start, count, bufSize, bidiFlags, minikinPaint, fc); + layout.doLayout(buf, start, count, bufSize, bidiFlags, minikinPaint); return layout; } if (mt->buildLayout(minikin::U16StringPiece(buf, bufSize), minikin::Range(start, start + count), - minikinPaint, fc, bidiFlags, mtOffset, &layout)) { + minikinPaint, bidiFlags, mtOffset, &layout)) { return layout; } - layout.doLayout(buf, start, count, bufSize, bidiFlags, minikinPaint, fc); + layout.doLayout(buf, start, count, bufSize, bidiFlags, minikinPaint); return layout; } @@ -75,10 +74,8 @@ float MinikinUtils::measureText(const Paint* paint, minikin::Bidi bidiFlags, const Typeface* typeface, const uint16_t* buf, size_t start, size_t count, size_t bufSize, float* advances) { minikin::MinikinPaint minikinPaint = prepareMinikinPaint(paint, typeface); - const Typeface* resolvedTypeface = Typeface::resolveDefault(typeface); return minikin::Layout::measureText(buf, start, count, bufSize, bidiFlags, minikinPaint, - resolvedTypeface->fFontCollection, advances, - nullptr /* extent */); + advances, nullptr /* extent */); } bool MinikinUtils::hasVariationSelector(const Typeface* typeface, uint32_t codepoint, uint32_t vs) { diff --git a/libs/hwui/pipeline/skia/SkiaDisplayList.cpp b/libs/hwui/pipeline/skia/SkiaDisplayList.cpp index cf0b6a4d1dcc..aa14699ae4c2 100644 --- a/libs/hwui/pipeline/skia/SkiaDisplayList.cpp +++ b/libs/hwui/pipeline/skia/SkiaDisplayList.cpp @@ -98,8 +98,6 @@ bool SkiaDisplayList::prepareListAndChildren( isDirty = true; } if (animatedImage->isRunning()) { - static_cast<SkiaPipeline*>(info.canvasContext.getRenderPipeline()) - ->scheduleDeferredUpdate(animatedImage); info.out.hasAnimations = true; } } diff --git a/libs/hwui/pipeline/skia/SkiaPipeline.cpp b/libs/hwui/pipeline/skia/SkiaPipeline.cpp index 534782a5dc02..9db39d954e4c 100644 --- a/libs/hwui/pipeline/skia/SkiaPipeline.cpp +++ b/libs/hwui/pipeline/skia/SkiaPipeline.cpp @@ -40,7 +40,6 @@ uint8_t SkiaPipeline::mSpotShadowAlpha = 0; Vector3 SkiaPipeline::mLightCenter = {FLT_MIN, FLT_MIN, FLT_MIN}; SkiaPipeline::SkiaPipeline(RenderThread& thread) : mRenderThread(thread) { - mAnimatedImageDrawables.reserve(30); mVectorDrawables.reserve(30); } @@ -327,15 +326,6 @@ void SkiaPipeline::renderFrame(const LayerUpdateQueue& layers, const SkRect& cli ATRACE_NAME("flush commands"); surface->getCanvas()->flush(); - - // TODO move to another method - if (!mAnimatedImageDrawables.empty()) { - ATRACE_NAME("Update AnimatedImageDrawables"); - for (auto animatedImage : mAnimatedImageDrawables) { - animatedImage->scheduleUpdate(getTaskManager()); - } - mAnimatedImageDrawables.clear(); - } } namespace { diff --git a/libs/hwui/pipeline/skia/SkiaPipeline.h b/libs/hwui/pipeline/skia/SkiaPipeline.h index cc75e9c5b38d..3800194440f9 100644 --- a/libs/hwui/pipeline/skia/SkiaPipeline.h +++ b/libs/hwui/pipeline/skia/SkiaPipeline.h @@ -55,12 +55,6 @@ public: std::vector<VectorDrawableRoot*>* getVectorDrawables() { return &mVectorDrawables; } - void scheduleDeferredUpdate(AnimatedImageDrawable* imageDrawable) { - mAnimatedImageDrawables.push_back(imageDrawable); - } - - std::vector<AnimatedImageDrawable*>* getAnimatingImages() { return &mAnimatedImageDrawables; } - static void destroyLayer(RenderNode* node); static void prepareToDraw(const renderthread::RenderThread& thread, Bitmap* bitmap); @@ -144,11 +138,6 @@ private: */ std::vector<VectorDrawableRoot*> mVectorDrawables; - /** - * populated by prepareTree with images with active animations - */ - std::vector<AnimatedImageDrawable*> mAnimatedImageDrawables; - // Block of properties used only for debugging to record a SkPicture and save it in a file. /** * mCapturedFile is used to enforce we don't capture more than once for a given name (cause diff --git a/libs/services/src/os/DropBoxManager.cpp b/libs/services/src/os/DropBoxManager.cpp index f5685d9ca753..c2907a66fb99 100644 --- a/libs/services/src/os/DropBoxManager.cpp +++ b/libs/services/src/os/DropBoxManager.cpp @@ -208,6 +208,11 @@ DropBoxManager::addFile(const String16& tag, const string& filename, int flags) Status DropBoxManager::addFile(const String16& tag, int fd, int flags) { + if (fd == -1) { + string message("invalid fd (-1) passed to to addFile"); + ALOGW("DropboxManager: %s", message.c_str()); + return Status::fromExceptionCode(Status::EX_ILLEGAL_STATE, message.c_str()); + } Entry entry(tag, flags, fd); return add(entry); } @@ -235,4 +240,3 @@ DropBoxManager::getNextEntry(const String16& tag, long msec, Entry* entry) } }} // namespace android::os - diff --git a/location/java/com/android/internal/location/gnssmetrics/GnssMetrics.java b/location/java/com/android/internal/location/gnssmetrics/GnssMetrics.java index 603926f4fe4d..98e67c21c1a1 100644 --- a/location/java/com/android/internal/location/gnssmetrics/GnssMetrics.java +++ b/location/java/com/android/internal/location/gnssmetrics/GnssMetrics.java @@ -17,7 +17,9 @@ package com.android.internal.location.gnssmetrics; import android.os.SystemClock; +import android.os.connectivity.GpsBatteryStats; +import android.text.format.DateUtils; import android.util.Base64; import android.util.Log; import android.util.TimeUtils; @@ -26,6 +28,7 @@ import java.util.Arrays; import com.android.internal.app.IBatteryStats; import com.android.internal.location.nano.GnssLogsProto.GnssLog; +import com.android.internal.location.nano.GnssLogsProto.PowerMetrics; /** * GnssMetrics: Is used for logging GNSS metrics @@ -171,6 +174,7 @@ public class GnssMetrics { msg.standardDeviationTopFourAverageCn0DbHz = topFourAverageCn0Statistics.getStandardDeviation(); } + msg.powerMetrics = mGnssPowerMetrics.buildProto(); String s = Base64.encodeToString(GnssLog.toByteArray(msg), Base64.DEFAULT); reset(); return s; @@ -218,6 +222,21 @@ public class GnssMetrics { topFourAverageCn0Statistics.getStandardDeviation()).append("\n"); } s.append("GNSS_KPI_END").append("\n"); + GpsBatteryStats stats = mGnssPowerMetrics.getGpsBatteryStats(); + if (stats != null) { + s.append("Power Metrics").append('\n'); + long[] t = stats.getTimeInGpsSignalQualityLevel(); + if (t != null && t.length == NUM_GPS_SIGNAL_QUALITY_LEVELS) { + s.append(" Amount of time (while on battery) Top 4 Avg CN0 > " + + Double.toString(GnssPowerMetrics.POOR_TOP_FOUR_AVG_CN0_THRESHOLD_DB_HZ) + + " dB-Hz (min): ").append(t[1] / ((double) DateUtils.MINUTE_IN_MILLIS)).append("\n"); + s.append(" Amount of time (while on battery) Top 4 Avg CN0 <= " + + Double.toString(GnssPowerMetrics.POOR_TOP_FOUR_AVG_CN0_THRESHOLD_DB_HZ) + + " dB-Hz (min): ").append(t[0] / ((double) DateUtils.MINUTE_IN_MILLIS)).append("\n"); + } + s.append(" Energy consumed while on battery (mAh): ").append( + stats.getEnergyConsumedMaMs() / ((double) DateUtils.HOUR_IN_MILLIS)).append("\n"); + } return s.toString(); } @@ -294,7 +313,7 @@ public class GnssMetrics { private class GnssPowerMetrics { /* Threshold for Top Four Average CN0 below which GNSS signal quality is declared poor */ - private static final double POOR_TOP_FOUR_AVG_CN0_THRESHOLD_DB_HZ = 20.0; + public static final double POOR_TOP_FOUR_AVG_CN0_THRESHOLD_DB_HZ = 20.0; /* Minimum change in Top Four Average CN0 needed to trigger a report */ private static final double REPORTING_THRESHOLD_DB_HZ = 1.0; @@ -313,6 +332,38 @@ public class GnssMetrics { } /** + * Builds power metrics proto buf. This is included in the gnss proto buf. + * @return PowerMetrics + */ + public PowerMetrics buildProto() { + PowerMetrics p = new PowerMetrics(); + GpsBatteryStats stats = mGnssPowerMetrics.getGpsBatteryStats(); + if (stats != null) { + p.loggingDurationMs = stats.getLoggingDurationMs(); + p.energyConsumedMah = stats.getEnergyConsumedMaMs() / ((double) DateUtils.HOUR_IN_MILLIS); + long[] t = stats.getTimeInGpsSignalQualityLevel(); + p.timeInSignalQualityLevelMs = new long[t.length]; + for (int i = 0; i < t.length; i++) { + p.timeInSignalQualityLevelMs[i] = t[i]; + } + } + return p; + } + + /** + * Returns the GPS power stats + * @return GpsBatteryStats + */ + public GpsBatteryStats getGpsBatteryStats() { + try { + return mBatteryStats.getGpsBatteryStats(); + } catch (Exception e) { + Log.w(TAG, "Exception", e); + return null; + } + } + + /** * Reports signal quality to BatteryStats. Signal quality is based on Top four average CN0. If * the number of SVs seen is less than 4, then signal quality is the average CN0. * Changes are reported only if the average CN0 changes by more than REPORTING_THRESHOLD_DB_HZ. @@ -347,4 +398,4 @@ public class GnssMetrics { return GnssMetrics.GPS_SIGNAL_QUALITY_POOR; } } -}
\ No newline at end of file +} diff --git a/location/lib/Android.mk b/location/lib/Android.mk index 62f56778109f..8424601a57ef 100644 --- a/location/lib/Android.mk +++ b/location/lib/Android.mk @@ -22,9 +22,7 @@ include $(CLEAR_VARS) LOCAL_MODULE:= com.android.location.provider LOCAL_MODULE_TAGS := optional -LOCAL_SRC_FILES := \ - $(call all-subdir-java-files) \ - $(call all-aidl-files-under, java) +LOCAL_SRC_FILES := $(call all-subdir-java-files) include $(BUILD_JAVA_LIBRARY) diff --git a/media/java/android/media/AudioDeviceInfo.java b/media/java/android/media/AudioDeviceInfo.java index 41f9f09fbb88..3d879f5a4660 100644 --- a/media/java/android/media/AudioDeviceInfo.java +++ b/media/java/android/media/AudioDeviceInfo.java @@ -22,6 +22,7 @@ import android.util.SparseIntArray; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.util.Objects; import java.util.TreeSet; /** @@ -176,6 +177,19 @@ public final class AudioDeviceInfo { } } + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + AudioDeviceInfo that = (AudioDeviceInfo) o; + return Objects.equals(getPort(), that.getPort()); + } + + @Override + public int hashCode() { + return Objects.hash(getPort()); + } + private final AudioDevicePort mPort; AudioDeviceInfo(AudioDevicePort port) { diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java index 2ac4063d1b08..22fa6202df5c 100644 --- a/media/java/android/media/AudioManager.java +++ b/media/java/android/media/AudioManager.java @@ -400,6 +400,18 @@ public class AudioManager { public static final int ADJUST_TOGGLE_MUTE = 101; /** @hide */ + @IntDef(flag = false, prefix = "ADJUST", value = { + ADJUST_RAISE, + ADJUST_LOWER, + ADJUST_SAME, + ADJUST_MUTE, + ADJUST_UNMUTE, + ADJUST_TOGGLE_MUTE } + ) + @Retention(RetentionPolicy.SOURCE) + public @interface VolumeAdjustment {} + + /** @hide */ public static final String adjustToString(int adj) { switch (adj) { case ADJUST_RAISE: return "ADJUST_RAISE"; @@ -2989,7 +3001,7 @@ public class AudioManager { final IAudioService service = getService(); try { String regId = service.registerAudioPolicy(policy.getConfig(), policy.cb(), - policy.hasFocusListener(), policy.isFocusPolicy()); + policy.hasFocusListener(), policy.isFocusPolicy(), policy.isVolumeController()); if (regId == null) { return ERROR; } else { diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl index 6c6522328e8d..88d0a6088026 100644 --- a/media/java/android/media/IAudioService.aidl +++ b/media/java/android/media/IAudioService.aidl @@ -166,7 +166,8 @@ interface IAudioService { boolean isHdmiSystemAudioSupported(); String registerAudioPolicy(in AudioPolicyConfig policyConfig, - in IAudioPolicyCallback pcb, boolean hasFocusListener, boolean isFocusPolicy); + in IAudioPolicyCallback pcb, boolean hasFocusListener, boolean isFocusPolicy, + boolean isVolumeController); oneway void unregisterAudioPolicyAsync(in IAudioPolicyCallback pcb); diff --git a/media/java/android/media/IMediaSession2.aidl b/media/java/android/media/IMediaSession2.aidl deleted file mode 100644 index 3783e5fd94e4..000000000000 --- a/media/java/android/media/IMediaSession2.aidl +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT 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.media; - -import android.media.IMediaSession2Callback; -import android.os.Bundle; - -/** - * Interface to MediaSession2. Framework MUST only call oneway APIs. - * - * @hide - */ -oneway interface IMediaSession2 { - // TODO(jaewan): add onCommand() to send private command - // TODO(jaewan): Due to the nature of oneway calls, APIs can be called in out of order - // Add id for individual calls to address this. - - // TODO(jaewan): We may consider to add another binder just for the connection - // not to expose other methods to the controller whose connection wasn't accepted. - // But this would be enough for now because it's the same as existing - // MediaBrowser and MediaBrowserService. - void connect(String callingPackage, IMediaSession2Callback callback); - void release(IMediaSession2Callback caller); - - ////////////////////////////////////////////////////////////////////////////////////////////// - // send command - ////////////////////////////////////////////////////////////////////////////////////////////// - void sendCommand(IMediaSession2Callback caller, in Bundle command, in Bundle args); - void sendTransportControlCommand(IMediaSession2Callback caller, - int commandCode, long arg); - - ////////////////////////////////////////////////////////////////////////////////////////////// - // Get library service specific - ////////////////////////////////////////////////////////////////////////////////////////////// - void getBrowserRoot(IMediaSession2Callback callback, in Bundle rootHints); -} diff --git a/media/java/android/media/IMediaSession2Callback.aidl b/media/java/android/media/IMediaSession2Callback.aidl deleted file mode 100644 index 45d40e6cc155..000000000000 --- a/media/java/android/media/IMediaSession2Callback.aidl +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT 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.media; - -import android.os.Bundle; -import android.media.session.PlaybackState; -import android.media.IMediaSession2; - -/** - * Interface from MediaSession2 to MediaSession2Record. - * <p> - * Keep this interface oneway. Otherwise a malicious app may implement fake version of this, - * and holds calls from session to make session owner(s) frozen. - * - * @hide - */ -oneway interface IMediaSession2Callback { - void onPlaybackStateChanged(in Bundle state); - void onPlaylistParamsChanged(in Bundle params); - - /** - * Called only when the controller is created with service's token. - * - * @param sessionBinder {@code null} if the connect is rejected or is disconnected. a session - * binder if the connect is accepted. - * @param commands initially allowed commands. - */ - // TODO(jaewan): Also need to pass flags for allowed actions for permission check. - // For example, a media can allow setRating only for whitelisted apps - // it's better for controller to know such information in advance. - // Follow-up TODO: Add similar functions to the session. - // TODO(jaewan): Is term 'accepted/rejected' correct? For permission, 'grant' is used. - void onConnectionChanged(IMediaSession2 sessionBinder, in Bundle commandGroup); - - void onCustomLayoutChanged(in List<Bundle> commandButtonlist); - - ////////////////////////////////////////////////////////////////////////////////////////////// - // Browser sepcific - ////////////////////////////////////////////////////////////////////////////////////////////// - void onGetRootResult(in Bundle rootHints, String rootMediaId, in Bundle rootExtra); -} diff --git a/media/java/android/media/MediaController2.java b/media/java/android/media/MediaController2.java index a8b2411a9013..b32e5398d0de 100644 --- a/media/java/android/media/MediaController2.java +++ b/media/java/android/media/MediaController2.java @@ -30,6 +30,7 @@ import android.media.MediaSession2.PlaylistParams; import android.media.session.MediaSessionManager; import android.media.update.ApiLoader; import android.media.update.MediaController2Provider; +import android.media.update.PlaybackInfoProvider; import android.net.Uri; import android.os.Bundle; import android.os.ResultReceiver; @@ -104,7 +105,7 @@ public class MediaController2 implements AutoCloseable { * * @param info new playback info */ - public void onAudioInfoChanged(PlaybackInfo info) { } + public void onPlaybackInfoChanged(PlaybackInfo info) { } /** * Called when the allowed commands are changed by session. @@ -126,11 +127,9 @@ public class MediaController2 implements AutoCloseable { /** * Called when the playlist is changed. * - * @param list - * @param param + * @param playlist A new playlist set by the session. */ - public void onPlaylistChanged( - @NonNull List<MediaItem2> list, @NonNull PlaylistParams param) { } + public void onPlaylistChanged(@NonNull List<MediaItem2> playlist) { } /** * Called when the playback state is changed, or connection success. @@ -162,21 +161,14 @@ public class MediaController2 implements AutoCloseable { */ public static final int PLAYBACK_TYPE_LOCAL = 1; - private final int mVolumeType; - private final int mVolumeControl; - private final int mMaxVolume; - private final int mCurrentVolume; - private final AudioAttributes mAudioAttrs; + private final PlaybackInfoProvider mProvider; /** * @hide */ - public PlaybackInfo(int type, AudioAttributes attrs, int control, int max, int current) { - mVolumeType = type; - mAudioAttrs = attrs; - mVolumeControl = control; - mMaxVolume = max; - mCurrentVolume = current; + @SystemApi + public PlaybackInfo(PlaybackInfoProvider provider) { + mProvider = provider; } /** @@ -189,7 +181,7 @@ public class MediaController2 implements AutoCloseable { * @return The type of playback this session is using. */ public int getPlaybackType() { - return mVolumeType; + return mProvider.getPlaybackType_impl(); } /** @@ -201,7 +193,7 @@ public class MediaController2 implements AutoCloseable { * @return The attributes for this session. */ public AudioAttributes getAudioAttributes() { - return mAudioAttrs; + return mProvider.getAudioAttributes_impl(); } /** @@ -212,11 +204,10 @@ public class MediaController2 implements AutoCloseable { * <li>{@link VolumeProvider#VOLUME_CONTROL_FIXED}</li> * </ul> * - * @return The type of volume control that may be used with this - * session. + * @return The type of volume control that may be used with this session. */ - public int getVolumeControl() { - return mVolumeControl; + public int getControlType() { + return mProvider.getControlType_impl(); } /** @@ -225,7 +216,7 @@ public class MediaController2 implements AutoCloseable { * @return The maximum allowed volume where this session is playing. */ public int getMaxVolume() { - return mMaxVolume; + return mProvider.getMaxVolume_impl(); } /** @@ -234,7 +225,7 @@ public class MediaController2 implements AutoCloseable { * @return The current volume where this session is playing. */ public int getCurrentVolume() { - return mCurrentVolume; + return mProvider.getCurrentVolume_impl(); } } @@ -279,6 +270,9 @@ public class MediaController2 implements AutoCloseable { mProvider.close_impl(); } + /** + * @hide + */ @SystemApi public MediaController2Provider getProvider() { return mProvider; @@ -599,9 +593,13 @@ public class MediaController2 implements AutoCloseable { return mProvider.getPlaylist_impl(); } - public @Nullable - PlaylistParams getPlaylistParam() { - return mProvider.getPlaylistParam_impl(); + /** + * Returns the {@link PlaylistParams} for the current play list. + * Can return {@code null} if the controller doesn't have enough permission, or if the session + * has not set the parameters. + */ + public @Nullable PlaylistParams getPlaylistParams() { + return mProvider.getPlaylistParams_impl(); } /** diff --git a/media/java/android/media/MediaDrm.java b/media/java/android/media/MediaDrm.java index a0edefa17875..90fcaabd12fa 100644 --- a/media/java/android/media/MediaDrm.java +++ b/media/java/android/media/MediaDrm.java @@ -942,43 +942,78 @@ public final class MediaDrm implements AutoCloseable { throws DeniedByServerException; /** - * A means of enforcing limits on the number of concurrent streams per subscriber - * across devices is provided via SecureStop. This is achieved by securely - * monitoring the lifetime of sessions. + * Secure stops are a way to enforce limits on the number of concurrent + * streams per subscriber across devices. They provide secure monitoring of + * the lifetime of content decryption keys in MediaDrm sessions. * <p> - * Information from the server related to the current playback session is written - * to persistent storage on the device when each MediaCrypto object is created. + * A secure stop is written to secure persistent memory when keys are loaded + * into a MediaDrm session. The secure stop state indicates that the keys + * are available for use. When playback completes and the keys are removed + * or the session is destroyed, the secure stop state is updated to indicate + * that keys are no longer usable. * <p> - * In the normal case, playback will be completed, the session destroyed and the - * Secure Stops will be queried. The app queries secure stops and forwards the - * secure stop message to the server which verifies the signature and notifies the - * server side database that the session destruction has been confirmed. The persisted - * record on the client is only removed after positive confirmation that the server - * received the message using releaseSecureStops(). + * After playback, the app can query the secure stop and send it in a + * message to the license server confirming that the keys are no longer + * active. The license server returns a secure stop release response + * message to the app which then deletes the secure stop from persistent + * memory using {@link #releaseSecureStops}. + * @return a list of all secure stops from secure persistent memory */ @NonNull public native List<byte[]> getSecureStops(); /** - * Access secure stop by secure stop ID. + * Return a list of all secure stop IDs currently in persistent memory. * - * @param ssid - The secure stop ID provided by the license server. + * @return a list of secure stop IDs + */ + @NonNull + public native List<byte[]> getSecureStopIds(); + + /** + * Access a specific secure stop given its secure stop ID. + * + * @param ssid the ID of the secure stop to return + * @return the secure stop identified by ssid */ @NonNull public native byte[] getSecureStop(@NonNull byte[] ssid); /** - * Process the SecureStop server response message ssRelease. After authenticating - * the message, remove the SecureStops identified in the response. + * Process the secure stop server response message ssRelease. After + * authenticating the message, remove the secure stops identified in the + * response. * * @param ssRelease the server response indicating which secure stops to release */ public native void releaseSecureStops(@NonNull byte[] ssRelease); /** - * Remove all secure stops without requiring interaction with the server. + * Remove a specific secure stop without requiring a secure stop release message + * from the license server. + * @param ssid the ID of the secure stop to remove + */ + public native void removeSecureStop(@NonNull byte[] ssid); + + /** + * Remove all secure stops without requiring a secure stop release message from + * the license server. + * + * This method was added in API 28. In API versions 18 through 27, + * {@link #releaseAllSecureStops} should be called instead. There is no need to + * do anything for API versions prior to 18. */ - public native void releaseAllSecureStops(); + public native void removeAllSecureStops(); + + /** + * Remove all secure stops without requiring a secure stop release message from + * the license server. + * + * @deprecated Remove all secure stops using {@link #removeAllSecureStops} instead. + */ + public void releaseAllSecureStops() { + removeAllSecureStops();; + } @Retention(RetentionPolicy.SOURCE) @IntDef({HDCP_LEVEL_UNKNOWN, HDCP_NONE, HDCP_V1, HDCP_V2, diff --git a/media/java/android/media/MediaItem2.java b/media/java/android/media/MediaItem2.java index f9711aa3b13d..eae4436cae78 100644 --- a/media/java/android/media/MediaItem2.java +++ b/media/java/android/media/MediaItem2.java @@ -20,8 +20,10 @@ import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemApi; +import android.content.Context; +import android.media.update.ApiLoader; +import android.media.update.MediaItem2Provider; import android.os.Bundle; -import android.text.TextUtils; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -36,10 +38,6 @@ import java.lang.annotation.RetentionPolicy; * @hide */ public class MediaItem2 { - private final int mFlags; - private MediaMetadata2 mMetadata; - private DataSourceDesc mDataSourceDesc; - /** @hide */ @Retention(RetentionPolicy.SOURCE) @IntDef(flag=true, value = { FLAG_BROWSABLE, FLAG_PLAYABLE }) @@ -58,17 +56,29 @@ public class MediaItem2 { */ public static final int FLAG_PLAYABLE = 1 << 1; + private final MediaItem2Provider mProvider; + /** * Create a new media item. * + * @param mediaId id of this item. It must be unique whithin this app * @param metadata metadata with the media id. * @param flags The flags for this item. */ - public MediaItem2(@Nullable MediaMetadata2 metadata, - @Nullable DataSourceDesc data, @Flags int flags) { - mFlags = flags; - mDataSourceDesc = data; - setMetadata(metadata); + public MediaItem2(@NonNull Context context, @NonNull String mediaId, + @NonNull DataSourceDesc dsd, @Nullable MediaMetadata2 metadata, + @Flags int flags) { + mProvider = ApiLoader.getProvider(context).createMediaItem2( + context, this, mediaId, dsd, metadata, flags); + } + + /** + * Create a new media item + * @hide + */ + @SystemApi + public MediaItem2(MediaItem2Provider provider) { + mProvider = provider; } /** @@ -78,22 +88,22 @@ public class MediaItem2 { */ public Bundle toBundle() { // TODO(jaewan): Fill here when we rebase. - return new Bundle(); + return mProvider.toBundle_impl(); + } + + public static MediaItem2 fromBundle(Context context, Bundle bundle) { + return ApiLoader.getProvider(context).fromBundle_MediaItem2(context, bundle); } public String toString() { - final StringBuilder sb = new StringBuilder("MediaItem2{"); - sb.append("mFlags=").append(mFlags); - sb.append(", mMetadata=").append(mMetadata); - sb.append('}'); - return sb.toString(); + return mProvider.toString_impl(); } /** * Gets the flags of the item. */ public @Flags int getFlags() { - return mFlags; + return mProvider.getFlags_impl(); } /** @@ -101,7 +111,7 @@ public class MediaItem2 { * @see #FLAG_BROWSABLE */ public boolean isBrowsable() { - return (mFlags & FLAG_BROWSABLE) != 0; + return mProvider.isBrowsable_impl(); } /** @@ -109,29 +119,24 @@ public class MediaItem2 { * @see #FLAG_PLAYABLE */ public boolean isPlayable() { - return (mFlags & FLAG_PLAYABLE) != 0; + return mProvider.isPlayable_impl(); } /** - * Set a metadata. Metadata shouldn't be null and should have non-empty media id. + * Set a metadata. Metadata shouldn't be {@code null} and its id should be match + * with this instance's id. * - * @param metadata + * @param metadata metadata to update */ public void setMetadata(@NonNull MediaMetadata2 metadata) { - if (metadata == null) { - throw new IllegalArgumentException("metadata cannot be null"); - } - if (TextUtils.isEmpty(metadata.getMediaId())) { - throw new IllegalArgumentException("metadata must have a non-empty media id"); - } - mMetadata = metadata; + mProvider.setMetadata_impl(metadata); } /** * Returns the metadata of the media. */ public @NonNull MediaMetadata2 getMetadata() { - return mMetadata; + return mProvider.getMetadata_impl(); } /** @@ -139,10 +144,17 @@ public class MediaItem2 { * @see MediaMetadata2#METADATA_KEY_MEDIA_ID */ public @Nullable String getMediaId() { - return mMetadata.getMediaId(); + return mProvider.getMediaId_impl(); } + /** + * Return the {@link DataSourceDesc} + * <p> + * Can be {@code null} if the MediaItem2 came from another process and anonymized + * + * @return data source descriptor + */ public @Nullable DataSourceDesc getDataSourceDesc() { - return mDataSourceDesc; + return mProvider.getDataSourceDesc_impl(); } } diff --git a/media/java/android/media/MediaLibraryService2.java b/media/java/android/media/MediaLibraryService2.java index 94ada135964b..a901c6895d9f 100644 --- a/media/java/android/media/MediaLibraryService2.java +++ b/media/java/android/media/MediaLibraryService2.java @@ -19,6 +19,7 @@ package android.media; import android.annotation.CallbackExecutor; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.SystemApi; import android.app.PendingIntent; import android.content.Context; import android.media.MediaSession2.BuilderBase; @@ -63,26 +64,16 @@ public abstract class MediaLibraryService2 extends MediaSessionService2 { /** * Session for the media library service. */ - public class MediaLibrarySession extends MediaSession2 { + public static class MediaLibrarySession extends MediaSession2 { private final MediaLibrarySessionProvider mProvider; - MediaLibrarySession(Context context, MediaPlayerInterface player, String id, - VolumeProvider volumeProvider, int ratingType, PendingIntent sessionActivity, - Executor callbackExecutor, SessionCallback callback) { - super(context, player, id, volumeProvider, ratingType, sessionActivity, - callbackExecutor, callback); - mProvider = (MediaLibrarySessionProvider) getProvider(); - } - - @Override - MediaSession2Provider createProvider(Context context, MediaPlayerInterface player, - String id, VolumeProvider volumeProvider, int ratingType, - PendingIntent sessionActivity, Executor callbackExecutor, - SessionCallback callback) { - return ApiLoader.getProvider(context) - .createMediaLibraryService2MediaLibrarySession(context, this, player, id, - volumeProvider, ratingType, sessionActivity, - callbackExecutor, (MediaLibrarySessionCallback) callback); + /** + * @hide + */ + @SystemApi + public MediaLibrarySession(MediaLibrarySessionProvider provider) { + super(provider); + mProvider = provider; } /** @@ -110,6 +101,11 @@ public abstract class MediaLibraryService2 extends MediaSessionService2 { } public static class MediaLibrarySessionCallback extends MediaSession2.SessionCallback { + + public MediaLibrarySessionCallback(Context context) { + super(context); + } + /** * Called to get the root information for browsing by a particular client. * <p> @@ -203,31 +199,15 @@ public abstract class MediaLibraryService2 extends MediaSessionService2 { /** * Builder for {@link MediaLibrarySession}. */ - // TODO(jaewan): Move this to updatable. - public class MediaLibrarySessionBuilder - extends BuilderBase<MediaLibrarySessionBuilder, MediaLibrarySessionCallback> { + public class MediaLibrarySessionBuilder extends BuilderBase<MediaLibrarySession, + MediaLibrarySessionBuilder, MediaLibrarySessionCallback> { public MediaLibrarySessionBuilder( @NonNull Context context, @NonNull MediaPlayerInterface player, @NonNull @CallbackExecutor Executor callbackExecutor, @NonNull MediaLibrarySessionCallback callback) { - super(context, player); - setSessionCallback(callbackExecutor, callback); - } - - @Override - public MediaLibrarySessionBuilder setSessionCallback( - @NonNull @CallbackExecutor Executor callbackExecutor, - @NonNull MediaLibrarySessionCallback callback) { - if (callback == null) { - throw new IllegalArgumentException("MediaLibrarySessionCallback cannot be null"); - } - return super.setSessionCallback(callbackExecutor, callback); - } - - @Override - public MediaLibrarySession build() { - return new MediaLibrarySession(mContext, mPlayer, mId, mVolumeProvider, mRatingType, - mSessionActivity, mCallbackExecutor, mCallback); + super((instance) -> ApiLoader.getProvider(context).createMediaLibraryService2Builder( + context, (MediaLibrarySessionBuilder) instance, player, callbackExecutor, + callback)); } } diff --git a/media/java/android/media/MediaMetadata2.java b/media/java/android/media/MediaMetadata2.java index fcdb4f75edcf..54a9057eeb2d 100644 --- a/media/java/android/media/MediaMetadata2.java +++ b/media/java/android/media/MediaMetadata2.java @@ -16,17 +16,16 @@ package android.media; +import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.StringDef; +import android.annotation.SystemApi; +import android.content.Context; import android.graphics.Bitmap; +import android.media.update.ApiLoader; +import android.media.update.MediaMetadata2Provider; import android.net.Uri; -import android.os.Build; import android.os.Bundle; -import android.os.Parcel; -import android.os.Parcelable; -import android.text.TextUtils; -import android.util.Log; -import android.util.ArrayMap; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -37,9 +36,11 @@ import java.util.Set; * * @hide */ -// TODO(jaewan): Move this to updatable public final class MediaMetadata2 { - private static final String TAG = "MediaMetadata2"; + // New version of MediaMetadata that no longer implements Parcelable but added from/toBundle() + // for updatable. + // MediaDescription is deprecated because it was insufficient for controller to display media + // contents. Added getExtra() here to support all the features from the MediaDescription. /** * The title of the media. @@ -365,76 +366,14 @@ public final class MediaMetadata2 { @Retention(RetentionPolicy.SOURCE) public @interface RatingKey {} - static final int METADATA_TYPE_LONG = 0; - static final int METADATA_TYPE_TEXT = 1; - static final int METADATA_TYPE_BITMAP = 2; - static final int METADATA_TYPE_RATING = 3; - static final ArrayMap<String, Integer> METADATA_KEYS_TYPE; - - static { - METADATA_KEYS_TYPE = new ArrayMap<String, Integer>(); - METADATA_KEYS_TYPE.put(METADATA_KEY_TITLE, METADATA_TYPE_TEXT); - METADATA_KEYS_TYPE.put(METADATA_KEY_ARTIST, METADATA_TYPE_TEXT); - METADATA_KEYS_TYPE.put(METADATA_KEY_DURATION, METADATA_TYPE_LONG); - METADATA_KEYS_TYPE.put(METADATA_KEY_ALBUM, METADATA_TYPE_TEXT); - METADATA_KEYS_TYPE.put(METADATA_KEY_AUTHOR, METADATA_TYPE_TEXT); - METADATA_KEYS_TYPE.put(METADATA_KEY_WRITER, METADATA_TYPE_TEXT); - METADATA_KEYS_TYPE.put(METADATA_KEY_COMPOSER, METADATA_TYPE_TEXT); - METADATA_KEYS_TYPE.put(METADATA_KEY_COMPILATION, METADATA_TYPE_TEXT); - METADATA_KEYS_TYPE.put(METADATA_KEY_DATE, METADATA_TYPE_TEXT); - METADATA_KEYS_TYPE.put(METADATA_KEY_YEAR, METADATA_TYPE_LONG); - METADATA_KEYS_TYPE.put(METADATA_KEY_GENRE, METADATA_TYPE_TEXT); - METADATA_KEYS_TYPE.put(METADATA_KEY_TRACK_NUMBER, METADATA_TYPE_LONG); - METADATA_KEYS_TYPE.put(METADATA_KEY_NUM_TRACKS, METADATA_TYPE_LONG); - METADATA_KEYS_TYPE.put(METADATA_KEY_DISC_NUMBER, METADATA_TYPE_LONG); - METADATA_KEYS_TYPE.put(METADATA_KEY_ALBUM_ARTIST, METADATA_TYPE_TEXT); - METADATA_KEYS_TYPE.put(METADATA_KEY_ART, METADATA_TYPE_BITMAP); - METADATA_KEYS_TYPE.put(METADATA_KEY_ART_URI, METADATA_TYPE_TEXT); - METADATA_KEYS_TYPE.put(METADATA_KEY_ALBUM_ART, METADATA_TYPE_BITMAP); - METADATA_KEYS_TYPE.put(METADATA_KEY_ALBUM_ART_URI, METADATA_TYPE_TEXT); - METADATA_KEYS_TYPE.put(METADATA_KEY_USER_RATING, METADATA_TYPE_RATING); - METADATA_KEYS_TYPE.put(METADATA_KEY_RATING, METADATA_TYPE_RATING); - METADATA_KEYS_TYPE.put(METADATA_KEY_DISPLAY_TITLE, METADATA_TYPE_TEXT); - METADATA_KEYS_TYPE.put(METADATA_KEY_DISPLAY_SUBTITLE, METADATA_TYPE_TEXT); - METADATA_KEYS_TYPE.put(METADATA_KEY_DISPLAY_DESCRIPTION, METADATA_TYPE_TEXT); - METADATA_KEYS_TYPE.put(METADATA_KEY_DISPLAY_ICON, METADATA_TYPE_BITMAP); - METADATA_KEYS_TYPE.put(METADATA_KEY_DISPLAY_ICON_URI, METADATA_TYPE_TEXT); - METADATA_KEYS_TYPE.put(METADATA_KEY_MEDIA_ID, METADATA_TYPE_TEXT); - METADATA_KEYS_TYPE.put(METADATA_KEY_BT_FOLDER_TYPE, METADATA_TYPE_LONG); - METADATA_KEYS_TYPE.put(METADATA_KEY_MEDIA_URI, METADATA_TYPE_TEXT); - METADATA_KEYS_TYPE.put(METADATA_KEY_ADVERTISEMENT, METADATA_TYPE_LONG); - METADATA_KEYS_TYPE.put(METADATA_KEY_DOWNLOAD_STATUS, METADATA_TYPE_LONG); - } - - private static final @TextKey String[] PREFERRED_DESCRIPTION_ORDER = { - METADATA_KEY_TITLE, - METADATA_KEY_ARTIST, - METADATA_KEY_ALBUM, - METADATA_KEY_ALBUM_ARTIST, - METADATA_KEY_WRITER, - METADATA_KEY_AUTHOR, - METADATA_KEY_COMPOSER - }; - - private static final @BitmapKey String[] PREFERRED_BITMAP_ORDER = { - METADATA_KEY_DISPLAY_ICON, - METADATA_KEY_ART, - METADATA_KEY_ALBUM_ART - }; - - private static final @TextKey String[] PREFERRED_URI_ORDER = { - METADATA_KEY_DISPLAY_ICON_URI, - METADATA_KEY_ART_URI, - METADATA_KEY_ALBUM_ART_URI - }; - - final Bundle mBundle; + private final MediaMetadata2Provider mProvider; /** * @hide */ - public MediaMetadata2(Bundle bundle) { - mBundle = new Bundle(bundle); + @SystemApi + public MediaMetadata2(MediaMetadata2Provider provider) { + mProvider = provider; } /** @@ -443,8 +382,8 @@ public final class MediaMetadata2 { * @param key a String key * @return true if the key exists in this metadata, false otherwise */ - public boolean containsKey(String key) { - return mBundle.containsKey(key); + public boolean containsKey(@NonNull String key) { + return mProvider.containsKey_impl(key); } /** @@ -455,8 +394,8 @@ public final class MediaMetadata2 { * @param key The key the value is stored under * @return a CharSequence value, or null */ - public CharSequence getText(@TextKey String key) { - return mBundle.getCharSequence(key); + public @Nullable CharSequence getText(@TextKey String key) { + return mProvider.getText_impl(key); } /** @@ -464,11 +403,11 @@ public final class MediaMetadata2 { * the desired type exists for the given key or a null value is explicitly * associated with the key. * - * @ * @return media id. Can be {@code null} + * @see #METADATA_KEY_MEDIA_ID */ public @Nullable String getMediaId() { - return getString(METADATA_KEY_MEDIA_ID); + return mProvider.getMediaId_impl(); } /** @@ -479,12 +418,8 @@ public final class MediaMetadata2 { * @param key The key the value is stored under * @return a String value, or null */ - public String getString(@TextKey String key) { - CharSequence text = mBundle.getCharSequence(key); - if (text != null) { - return text.toString(); - } - return null; + public @Nullable String getString(@NonNull @TextKey String key) { + return mProvider.getString_impl(key); } /** @@ -494,8 +429,8 @@ public final class MediaMetadata2 { * @param key The key the value is stored under * @return a long value */ - public long getLong(@LongKey String key) { - return mBundle.getLong(key, 0); + public long getLong(@NonNull @LongKey String key) { + return mProvider.getLong_impl(key); } /** @@ -503,18 +438,10 @@ public final class MediaMetadata2 { * the given key. * * @param key The key the value is stored under - * @return A {@link Rating2} or null - */ - public Rating2 getRating(@RatingKey String key) { - // TODO(jaewan): Add backward compatibility - Rating2 rating = null; - try { - rating = Rating2.fromBundle(mBundle.getBundle(key)); - } catch (Exception e) { - // ignore, value was not a rating - Log.w(TAG, "Failed to retrieve a key as Rating.", e); - } - return rating; + * @return A {@link Rating2} or {@code null} + */ + public @Nullable Rating2 getRating(@RatingKey String key) { + return mProvider.getRating_impl(key); } /** @@ -525,14 +452,7 @@ public final class MediaMetadata2 { * @return A {@link Bitmap} or null */ public Bitmap getBitmap(@BitmapKey String key) { - Bitmap bmp = null; - try { - bmp = mBundle.getParcelable(key); - } catch (Exception e) { - // ignore, value was not a bitmap - Log.w(TAG, "Failed to retrieve a key as Bitmap.", e); - } - return bmp; + return mProvider.getBitmap_impl(key); } /** @@ -540,14 +460,8 @@ public final class MediaMetadata2 { * * @return A {@link Bundle} or {@code null} */ - public Bundle getExtra() { - try { - return mBundle.getBundle(METADATA_KEY_EXTRA); - } catch (Exception e) { - // ignore, value was not an bundle - Log.w(TAG, "Failed to retrieve an extra"); - } - return null; + public @Nullable Bundle getExtra() { + return mProvider.getExtra_impl(); } /** @@ -556,7 +470,7 @@ public final class MediaMetadata2 { * @return The number of fields in the metadata. */ public int size() { - return mBundle.size(); + return mProvider.size_impl(); } /** @@ -564,8 +478,8 @@ public final class MediaMetadata2 { * * @return a Set of String keys */ - public Set<String> keySet() { - return mBundle.keySet(); + public @NonNull Set<String> keySet() { + return mProvider.keySet_impl(); } /** @@ -574,8 +488,21 @@ public final class MediaMetadata2 { * * @return The Bundle backing this metadata. */ - public Bundle getBundle() { - return mBundle; + public @NonNull Bundle toBundle() { + return mProvider.toBundle_impl(); + } + + /** + * Creates the {@link MediaMetadata2} from the bundle that previously returned by + * {@link #toBundle()}. + * + * @param context context + * @param bundle bundle for the metadata + * @return a new MediaMetadata2 + */ + public static @NonNull MediaMetadata2 fromBundle(@NonNull Context context, + @Nullable Bundle bundle) { + return ApiLoader.getProvider(context).fromBundle_MediaMetadata2(context, bundle); } /** @@ -583,14 +510,15 @@ public final class MediaMetadata2 { * use the appropriate data type. */ public static final class Builder { - private final Bundle mBundle; + private final MediaMetadata2Provider.BuilderProvider mProvider; /** * Create an empty Builder. Any field that should be included in the * {@link MediaMetadata2} must be added. */ - public Builder() { - mBundle = new Bundle(); + public Builder(@NonNull Context context) { + mProvider = ApiLoader.getProvider(context).createMediaMetadata2Builder( + context, this); } /** @@ -600,31 +528,17 @@ public final class MediaMetadata2 { * * @param source */ - public Builder(MediaMetadata2 source) { - mBundle = new Bundle(source.mBundle); + public Builder(@NonNull Context context, @NonNull MediaMetadata2 source) { + mProvider = ApiLoader.getProvider(context).createMediaMetadata2Builder( + context, this, source); } /** - * Create a Builder using a {@link MediaMetadata2} instance to set - * initial values, but replace bitmaps with a scaled down copy if they - * are larger than maxBitmapSize. - * - * @param source The original metadata to copy. - * @param maxBitmapSize The maximum height/width for bitmaps contained - * in the metadata. * @hide */ - public Builder(MediaMetadata2 source, int maxBitmapSize) { - this(source); - for (String key : mBundle.keySet()) { - Object value = mBundle.get(key); - if (value instanceof Bitmap) { - Bitmap bmp = (Bitmap) value; - if (bmp.getHeight() > maxBitmapSize || bmp.getWidth() > maxBitmapSize) { - putBitmap(key, scaleBitmap(bmp, maxBitmapSize)); - } - } - } + @SystemApi + public Builder(@NonNull MediaMetadata2Provider.BuilderProvider provider) { + mProvider = provider; } /** @@ -653,15 +567,8 @@ public final class MediaMetadata2 { * @param value The CharSequence value to store * @return The Builder to allow chaining */ - public Builder putText(@TextKey String key, CharSequence value) { - if (METADATA_KEYS_TYPE.containsKey(key)) { - if (METADATA_KEYS_TYPE.get(key) != METADATA_TYPE_TEXT) { - throw new IllegalArgumentException("The " + key - + " key cannot be used to put a CharSequence"); - } - } - mBundle.putCharSequence(key, value); - return this; + public @NonNull Builder putText(@TextKey String key, @Nullable CharSequence value) { + return mProvider.putText_impl(key, value); } /** @@ -690,15 +597,8 @@ public final class MediaMetadata2 { * @param value The String value to store * @return The Builder to allow chaining */ - public Builder putString(@TextKey String key, String value) { - if (METADATA_KEYS_TYPE.containsKey(key)) { - if (METADATA_KEYS_TYPE.get(key) != METADATA_TYPE_TEXT) { - throw new IllegalArgumentException("The " + key - + " key cannot be used to put a String"); - } - } - mBundle.putCharSequence(key, value); - return this; + public @NonNull Builder putString(@TextKey String key, @Nullable String value) { + return mProvider.putString_impl(key, value); } /** @@ -720,15 +620,8 @@ public final class MediaMetadata2 { * @param value The String value to store * @return The Builder to allow chaining */ - public Builder putLong(@LongKey String key, long value) { - if (METADATA_KEYS_TYPE.containsKey(key)) { - if (METADATA_KEYS_TYPE.get(key) != METADATA_TYPE_LONG) { - throw new IllegalArgumentException("The " + key - + " key cannot be used to put a long"); - } - } - mBundle.putLong(key, value); - return this; + public @NonNull Builder putLong(@NonNull @LongKey String key, long value) { + return mProvider.putLong_impl(key, value); } /** @@ -744,16 +637,8 @@ public final class MediaMetadata2 { * @param value The String value to store * @return The Builder to allow chaining */ - public Builder putRating(@RatingKey String key, Rating2 value) { - if (METADATA_KEYS_TYPE.containsKey(key)) { - if (METADATA_KEYS_TYPE.get(key) != METADATA_TYPE_RATING) { - throw new IllegalArgumentException("The " + key - + " key cannot be used to put a Rating"); - } - } - mBundle.putBundle(key, value.toBundle()); - - return this; + public @NonNull Builder putRating(@NonNull @RatingKey String key, @Nullable Rating2 value) { + return mProvider.putRating_impl(key, value); } /** @@ -774,23 +659,15 @@ public final class MediaMetadata2 { * @param value The Bitmap to store * @return The Builder to allow chaining */ - public Builder putBitmap(@BitmapKey String key, Bitmap value) { - if (METADATA_KEYS_TYPE.containsKey(key)) { - if (METADATA_KEYS_TYPE.get(key) != METADATA_TYPE_BITMAP) { - throw new IllegalArgumentException("The " + key - + " key cannot be used to put a Bitmap"); - } - } - mBundle.putParcelable(key, value); - return this; + public @NonNull Builder putBitmap(@NonNull @BitmapKey String key, @Nullable Bitmap value) { + return mProvider.putBitmap_impl(key, value); } /** * Set an extra {@link Bundle} into the metadata. */ - public Builder setExtra(Bundle bundle) { - mBundle.putBundle(METADATA_KEY_EXTRA, bundle); - return this; + public @NonNull Builder setExtra(@Nullable Bundle bundle) { + return mProvider.setExtra_impl(bundle); } /** @@ -798,18 +675,8 @@ public final class MediaMetadata2 { * * @return The new MediaMetadata2 instance */ - public MediaMetadata2 build() { - return new MediaMetadata2(mBundle); - } - - private Bitmap scaleBitmap(Bitmap bmp, int maxSize) { - float maxSizeF = maxSize; - float widthScale = maxSizeF / bmp.getWidth(); - float heightScale = maxSizeF / bmp.getHeight(); - float scale = Math.min(widthScale, heightScale); - int height = (int) (bmp.getHeight() * scale); - int width = (int) (bmp.getWidth() * scale); - return Bitmap.createScaledBitmap(bmp, width, height, true); + public @NonNull MediaMetadata2 build() { + return mProvider.build_impl(); } } } diff --git a/media/java/android/media/MediaPlayer2.java b/media/java/android/media/MediaPlayer2.java index d36df845cc2e..d84eedf94820 100644 --- a/media/java/android/media/MediaPlayer2.java +++ b/media/java/android/media/MediaPlayer2.java @@ -96,22 +96,13 @@ import java.util.UUID; * {@link #close()} is called, it is in the <em>End</em> state. Between these * two states is the life cycle of the MediaPlayer2 object. * <ul> - * <li>There is a subtle but important difference between a newly constructed - * MediaPlayer2 object and the MediaPlayer2 object after {@link #reset()} - * is called. It is a programming error to invoke methods such + * <li> It is a programming error to invoke methods such * as {@link #getCurrentPosition()}, * {@link #getDuration()}, {@link #getVideoHeight()}, * {@link #getVideoWidth()}, {@link #setAudioAttributes(AudioAttributes)}, * {@link #setVolume(float, float)}, {@link #pause()}, {@link #play()}, * {@link #seekTo(long, int)} or - * {@link #prepareAsync()} in the <em>Idle</em> state for both cases. If any of these - * methods is called right after a MediaPlayer2 object is constructed, - * the user supplied callback method OnErrorListener.onError() won't be - * called by the internal player engine and the object state remains - * unchanged; but if these methods are called right after {@link #reset()}, - * the user supplied callback method OnErrorListener.onError() will be - * invoked by the internal player engine and the object will be - * transfered to the <em>Error</em> state. </li> + * {@link #prepareAsync()} in the <em>Idle</em> state. * <li>It is also recommended that once * a MediaPlayer2 object is no longer being used, call {@link #close()} immediately * so that resources used by the internal player engine associated with the diff --git a/media/java/android/media/MediaPlayer2Impl.java b/media/java/android/media/MediaPlayer2Impl.java index 86a285cccaf9..222c66ea4551 100644 --- a/media/java/android/media/MediaPlayer2Impl.java +++ b/media/java/android/media/MediaPlayer2Impl.java @@ -1960,6 +1960,13 @@ public final class MediaPlayer2Impl extends MediaPlayer2 { mTimeProvider = null; } + synchronized (mEventCbLock) { + mEventCallbackRecords.clear(); + } + synchronized (mDrmEventCbLock) { + mDrmEventCallbackRecords.clear(); + } + stayAwake(false); _reset(); // make sure none of the listeners get called anymore @@ -3049,8 +3056,7 @@ public final class MediaPlayer2Impl extends MediaPlayer2 { stayAwake(false); updateSurfaceScreenOn(); synchronized (mEventCbLock) { - mEventCb = null; - mEventExec = null; + mEventCallbackRecords.clear(); } if (mTimeProvider != null) { mTimeProvider.close(); @@ -3061,8 +3067,7 @@ public final class MediaPlayer2Impl extends MediaPlayer2 { // Modular DRM clean up mOnDrmConfigHelper = null; synchronized (mDrmEventCbLock) { - mDrmEventCb = null; - mDrmEventExec = null; + mDrmEventCallbackRecords.clear(); } resetDrmState(); @@ -3118,18 +3123,8 @@ public final class MediaPlayer2Impl extends MediaPlayer2 { Log.w(TAG, "mediaplayer2 went away with unhandled events"); return; } - final Executor eventExec; - final EventCallback eventCb; - synchronized (mEventCbLock) { - eventExec = mEventExec; - eventCb = mEventCb; - } - final Executor drmEventExec; - final DrmEventCallback drmEventCb; - synchronized (mDrmEventCbLock) { - drmEventExec = mDrmEventExec; - drmEventCb = mDrmEventCb; - } + final int what = msg.arg1; + final int extra = msg.arg2; switch(msg.what) { case MEDIA_PREPARED: try { @@ -3143,33 +3138,36 @@ public final class MediaPlayer2Impl extends MediaPlayer2 { sendMessage(msg2); } - if (eventCb != null && eventExec != null) { - eventExec.execute(() -> eventCb.onInfo( - mMediaPlayer, 0, MEDIA_INFO_PREPARED, 0)); + synchronized (mEventCbLock) { + for (Pair<Executor, EventCallback> cb : mEventCallbackRecords) { + cb.first.execute(() -> cb.second.onInfo( + mMediaPlayer, 0, MEDIA_INFO_PREPARED, 0)); + } } return; case MEDIA_DRM_INFO: - Log.v(TAG, "MEDIA_DRM_INFO " + mDrmEventCb); - if (msg.obj == null) { Log.w(TAG, "MEDIA_DRM_INFO msg.obj=NULL"); } else if (msg.obj instanceof Parcel) { - if (drmEventExec != null && drmEventCb != null) { - // The parcel was parsed already in postEventFromNative - final DrmInfoImpl drmInfo; - - synchronized (mDrmLock) { - if (mDrmInfoImpl != null) { - drmInfo = mDrmInfoImpl.makeCopy(); - } else { - drmInfo = null; - } + // The parcel was parsed already in postEventFromNative + final DrmInfoImpl drmInfo; + + synchronized (mDrmLock) { + if (mDrmInfoImpl != null) { + drmInfo = mDrmInfoImpl.makeCopy(); + } else { + drmInfo = null; } + } - // notifying the client outside the lock - if (drmInfo != null) { - drmEventExec.execute(() -> drmEventCb.onDrmInfo(mMediaPlayer, drmInfo)); + // notifying the client outside the lock + if (drmInfo != null) { + synchronized (mEventCbLock) { + for (Pair<Executor, DrmEventCallback> cb : mDrmEventCallbackRecords) { + cb.first.execute(() -> cb.second.onDrmInfo( + mMediaPlayer, drmInfo)); + } } } } else { @@ -3178,9 +3176,11 @@ public final class MediaPlayer2Impl extends MediaPlayer2 { return; case MEDIA_PLAYBACK_COMPLETE: - if (eventCb != null && eventExec != null) { - eventExec.execute(() -> eventCb.onInfo( - mMediaPlayer, 0, MEDIA_INFO_PLAYBACK_COMPLETE, 0)); + synchronized (mEventCbLock) { + for (Pair<Executor, EventCallback> cb : mEventCallbackRecords) { + cb.first.execute(() -> cb.second.onInfo( + mMediaPlayer, 0, MEDIA_INFO_PLAYBACK_COMPLETE, 0)); + } } stayAwake(false); return; @@ -3205,16 +3205,21 @@ public final class MediaPlayer2Impl extends MediaPlayer2 { break; case MEDIA_BUFFERING_UPDATE: - if (eventCb != null && eventExec != null) { - final int percent = msg.arg1; - eventExec.execute(() -> eventCb.onBufferingUpdate(mMediaPlayer, 0, percent)); + final int percent = msg.arg1; + synchronized (mEventCbLock) { + for (Pair<Executor, EventCallback> cb : mEventCallbackRecords) { + cb.first.execute(() -> cb.second.onBufferingUpdate( + mMediaPlayer, 0, percent)); + } } return; case MEDIA_SEEK_COMPLETE: - if (eventCb != null && eventExec != null) { - eventExec.execute(() -> eventCb.onInfo( - mMediaPlayer, 0, MEDIA_INFO_COMPLETE_CALL_SEEK, 0)); + synchronized (mEventCbLock) { + for (Pair<Executor, EventCallback> cb : mEventCallbackRecords) { + cb.first.execute(() -> cb.second.onInfo( + mMediaPlayer, 0, MEDIA_INFO_COMPLETE_CALL_SEEK, 0)); + } } // fall through @@ -3228,61 +3233,68 @@ public final class MediaPlayer2Impl extends MediaPlayer2 { return; case MEDIA_SET_VIDEO_SIZE: - if (eventCb != null && eventExec != null) { - final int width = msg.arg1; - final int height = msg.arg2; - eventExec.execute(() -> eventCb.onVideoSizeChanged( - mMediaPlayer, 0, width, height)); + final int width = msg.arg1; + final int height = msg.arg2; + synchronized (mEventCbLock) { + for (Pair<Executor, EventCallback> cb : mEventCallbackRecords) { + cb.first.execute(() -> cb.second.onVideoSizeChanged( + mMediaPlayer, 0, width, height)); + } } return; case MEDIA_ERROR: Log.e(TAG, "Error (" + msg.arg1 + "," + msg.arg2 + ")"); - if (eventCb != null && eventExec != null) { - final int what = msg.arg1; - final int extra = msg.arg2; - eventExec.execute(() -> eventCb.onError(mMediaPlayer, 0, what, extra)); - eventExec.execute(() -> eventCb.onInfo( - mMediaPlayer, 0, MEDIA_INFO_PLAYBACK_COMPLETE, 0)); + synchronized (mEventCbLock) { + for (Pair<Executor, EventCallback> cb : mEventCallbackRecords) { + cb.first.execute(() -> cb.second.onError( + mMediaPlayer, 0, what, extra)); + cb.first.execute(() -> cb.second.onInfo( + mMediaPlayer, 0, MEDIA_INFO_PLAYBACK_COMPLETE, 0)); + } } stayAwake(false); return; case MEDIA_INFO: switch (msg.arg1) { - case MEDIA_INFO_VIDEO_TRACK_LAGGING: - Log.i(TAG, "Info (" + msg.arg1 + "," + msg.arg2 + ")"); - break; - case MEDIA_INFO_METADATA_UPDATE: - try { - scanInternalSubtitleTracks(); - } catch (RuntimeException e) { - Message msg2 = obtainMessage( - MEDIA_ERROR, MEDIA_ERROR_UNKNOWN, MEDIA_ERROR_UNSUPPORTED, null); - sendMessage(msg2); - } - // fall through + case MEDIA_INFO_VIDEO_TRACK_LAGGING: + Log.i(TAG, "Info (" + msg.arg1 + "," + msg.arg2 + ")"); + break; - case MEDIA_INFO_EXTERNAL_METADATA_UPDATE: - msg.arg1 = MEDIA_INFO_METADATA_UPDATE; - // update default track selection - if (mSubtitleController != null) { - mSubtitleController.selectDefaultTrack(); - } - break; - case MEDIA_INFO_BUFFERING_START: - case MEDIA_INFO_BUFFERING_END: - TimeProvider timeProvider = mTimeProvider; - if (timeProvider != null) { - timeProvider.onBuffering(msg.arg1 == MEDIA_INFO_BUFFERING_START); - } - break; + case MEDIA_INFO_METADATA_UPDATE: + try { + scanInternalSubtitleTracks(); + } catch (RuntimeException e) { + Message msg2 = obtainMessage( + MEDIA_ERROR, MEDIA_ERROR_UNKNOWN, MEDIA_ERROR_UNSUPPORTED, + null); + sendMessage(msg2); + } + // fall through + + case MEDIA_INFO_EXTERNAL_METADATA_UPDATE: + msg.arg1 = MEDIA_INFO_METADATA_UPDATE; + // update default track selection + if (mSubtitleController != null) { + mSubtitleController.selectDefaultTrack(); + } + break; + + case MEDIA_INFO_BUFFERING_START: + case MEDIA_INFO_BUFFERING_END: + TimeProvider timeProvider = mTimeProvider; + if (timeProvider != null) { + timeProvider.onBuffering(msg.arg1 == MEDIA_INFO_BUFFERING_START); + } + break; } - if (eventCb != null && eventExec != null) { - final int what = msg.arg1; - final int extra = msg.arg2; - eventExec.execute(() -> eventCb.onInfo(mMediaPlayer, 0, what, extra)); + synchronized (mEventCbLock) { + for (Pair<Executor, EventCallback> cb : mEventCallbackRecords) { + cb.first.execute(() -> cb.second.onInfo( + mMediaPlayer, 0, what, extra)); + } } // No real default action so far. return; @@ -3295,17 +3307,18 @@ public final class MediaPlayer2Impl extends MediaPlayer2 { return; case MEDIA_TIMED_TEXT: - if (eventCb == null || eventExec == null) { - return; - } - if (msg.obj == null) { - eventExec.execute(() -> eventCb.onTimedText(mMediaPlayer, 0, null)); + final TimedText text; + if (msg.obj instanceof Parcel) { + Parcel parcel = (Parcel)msg.obj; + text = new TimedText(parcel); + parcel.recycle(); } else { - if (msg.obj instanceof Parcel) { - Parcel parcel = (Parcel)msg.obj; - TimedText text = new TimedText(parcel); - parcel.recycle(); - eventExec.execute(() -> eventCb.onTimedText(mMediaPlayer, 0, text)); + text = null; + } + + synchronized (mEventCbLock) { + for (Pair<Executor, EventCallback> cb : mEventCallbackRecords) { + cb.first.execute(() -> cb.second.onTimedText(mMediaPlayer, 0, text)); } } return; @@ -3324,15 +3337,20 @@ public final class MediaPlayer2Impl extends MediaPlayer2 { return; case MEDIA_META_DATA: - if (eventCb == null || eventExec == null) { - return; - } + final TimedMetaData data; if (msg.obj instanceof Parcel) { Parcel parcel = (Parcel) msg.obj; - TimedMetaData data = TimedMetaData.createTimedMetaDataFromParcel(parcel); + data = TimedMetaData.createTimedMetaDataFromParcel(parcel); parcel.recycle(); - eventExec.execute(() -> eventCb.onTimedMetaDataAvailable( - mMediaPlayer, 0, data)); + } else { + data = null; + } + + synchronized (mEventCbLock) { + for (Pair<Executor, EventCallback> cb : mEventCallbackRecords) { + cb.first.execute(() -> cb.second.onTimedMetaDataAvailable( + mMediaPlayer, 0, data)); + } } return; @@ -3420,9 +3438,9 @@ public final class MediaPlayer2Impl extends MediaPlayer2 { } } - private Executor mEventExec; - private EventCallback mEventCb; private final Object mEventCbLock = new Object(); + private ArrayList<Pair<Executor, EventCallback> > mEventCallbackRecords + = new ArrayList<Pair<Executor, EventCallback> >(); /** * Register a callback to be invoked when the media source is ready @@ -3441,9 +3459,7 @@ public final class MediaPlayer2Impl extends MediaPlayer2 { throw new IllegalArgumentException("Illegal null Executor for the EventCallback"); } synchronized (mEventCbLock) { - // TODO: support multiple callbacks. - mEventExec = executor; - mEventCb = eventCallback; + mEventCallbackRecords.add(new Pair(executor, eventCallback)); } } @@ -3455,9 +3471,10 @@ public final class MediaPlayer2Impl extends MediaPlayer2 { @Override public void unregisterEventCallback(EventCallback callback) { synchronized (mEventCbLock) { - if (callback == mEventCb) { - mEventExec = null; - mEventCb = null; + for (Pair<Executor, EventCallback> cb : mEventCallbackRecords) { + if (cb.second == callback) { + mEventCallbackRecords.remove(cb); + } } } } @@ -3497,9 +3514,9 @@ public final class MediaPlayer2Impl extends MediaPlayer2 { private OnDrmConfigHelper mOnDrmConfigHelper; - private Executor mDrmEventExec; - private DrmEventCallback mDrmEventCb; private final Object mDrmEventCbLock = new Object(); + private ArrayList<Pair<Executor, DrmEventCallback> > mDrmEventCallbackRecords + = new ArrayList<Pair<Executor, DrmEventCallback> >(); /** * Register a callback to be invoked when the media source is ready @@ -3518,9 +3535,7 @@ public final class MediaPlayer2Impl extends MediaPlayer2 { throw new IllegalArgumentException("Illegal null Executor for the EventCallback"); } synchronized (mDrmEventCbLock) { - // TODO: support multiple callbacks. - mDrmEventExec = executor; - mDrmEventCb = eventCallback; + mDrmEventCallbackRecords.add(new Pair(executor, eventCallback)); } } @@ -3532,9 +3547,11 @@ public final class MediaPlayer2Impl extends MediaPlayer2 { @Override public void unregisterDrmEventCallback(DrmEventCallback callback) { synchronized (mDrmEventCbLock) { - if (callback == mDrmEventCb) { - mDrmEventExec = null; - mDrmEventCb = null; + for (Pair<Executor, DrmEventCallback> cb : mDrmEventCallbackRecords) { + if (cb.second == callback) { + mDrmEventCallbackRecords.remove(cb); + break; + } } } } @@ -3733,15 +3750,11 @@ public final class MediaPlayer2Impl extends MediaPlayer2 { // if finished successfully without provisioning, call the callback outside the lock if (allDoneWithoutProvisioning) { - final Executor drmEventExec; - final DrmEventCallback drmEventCb; synchronized (mDrmEventCbLock) { - drmEventExec = mDrmEventExec; - drmEventCb = mDrmEventCb; - } - if (drmEventExec != null && drmEventCb != null) { - drmEventExec.execute(() -> drmEventCb.onDrmPrepared( - this, PREPARE_DRM_STATUS_SUCCESS)); + for (Pair<Executor, DrmEventCallback> cb : mDrmEventCallbackRecords) { + cb.first.execute(() -> cb.second.onDrmPrepared( + this, PREPARE_DRM_STATUS_SUCCESS)); + } } } @@ -4324,14 +4337,12 @@ public final class MediaPlayer2Impl extends MediaPlayer2 { boolean succeeded = false; - final Executor drmEventExec; - final DrmEventCallback drmEventCb; + boolean hasCallback = false; synchronized (mDrmEventCbLock) { - drmEventExec = mDrmEventExec; - drmEventCb = mDrmEventCb; + hasCallback = !mDrmEventCallbackRecords.isEmpty(); } // non-blocking mode needs the lock - if (drmEventExec != null && drmEventCb != null) { + if (hasCallback) { synchronized (drmLock) { // continuing with prepareDrm @@ -4349,7 +4360,11 @@ public final class MediaPlayer2Impl extends MediaPlayer2 { } // synchronized // calling the callback outside the lock - drmEventExec.execute(() -> drmEventCb.onDrmPrepared(mediaPlayer, status)); + synchronized (mDrmEventCbLock) { + for (Pair<Executor, DrmEventCallback> cb : mDrmEventCallbackRecords) { + cb.first.execute(() -> cb.second.onDrmPrepared(mediaPlayer, status)); + } + } } else { // blocking mode already has the lock // continuing with prepareDrm @@ -4397,13 +4412,11 @@ public final class MediaPlayer2Impl extends MediaPlayer2 { int result; // non-blocking: this is not the final result - final Executor drmEventExec; - final DrmEventCallback drmEventCb; + boolean hasCallback = false; synchronized (mDrmEventCbLock) { - drmEventExec = mDrmEventExec; - drmEventCb = mDrmEventCb; + hasCallback = !mDrmEventCallbackRecords.isEmpty(); } - if (drmEventCb != null && drmEventExec != null) { + if (hasCallback) { result = PREPARE_DRM_STATUS_SUCCESS; } else { // if blocking mode, wait till provisioning is done diff --git a/media/java/android/media/MediaPlayerInterface.java b/media/java/android/media/MediaPlayerInterface.java index 46518d561062..78e2391455ac 100644 --- a/media/java/android/media/MediaPlayerInterface.java +++ b/media/java/android/media/MediaPlayerInterface.java @@ -64,10 +64,10 @@ public interface MediaPlayerInterface { @Nullable AudioAttributes getAudioAttributes(); - void setPlaylist(List<MediaItem2> list, PlaylistParams param); void addPlaylistItem(int index, MediaItem2 item); void removePlaylistItem(MediaItem2 item); + void setPlaylist(List<MediaItem2> playlist); List<MediaItem2> getPlaylist(); void setCurrentPlaylistItem(int index); diff --git a/media/java/android/media/MediaSession2.java b/media/java/android/media/MediaSession2.java index 245ba3b5f65d..acf9615404d7 100644 --- a/media/java/android/media/MediaSession2.java +++ b/media/java/android/media/MediaSession2.java @@ -30,18 +30,20 @@ import android.media.session.MediaSession.Callback; import android.media.session.PlaybackState; import android.media.update.ApiLoader; import android.media.update.MediaSession2Provider; +import android.media.update.MediaSession2Provider.BuilderBaseProvider; +import android.media.update.MediaSession2Provider.CommandGroupProvider; +import android.media.update.MediaSession2Provider.CommandProvider; import android.media.update.MediaSession2Provider.ControllerInfoProvider; +import android.media.update.ProviderCreator; import android.net.Uri; import android.os.Bundle; import android.os.Handler; -import android.os.Parcelable; +import android.os.IInterface; import android.os.ResultReceiver; import android.text.TextUtils; -import android.util.ArraySet; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; -import java.util.ArrayList; import java.util.List; import java.util.concurrent.Executor; @@ -81,32 +83,178 @@ import java.util.concurrent.Executor; public class MediaSession2 implements AutoCloseable { private final MediaSession2Provider mProvider; - // Note: Do not define IntDef because subclass can add more command code on top of these. + // TODO(jaewan): Should we define IntDef? Currently we don't have to allow subclass to add more. // TODO(jaewan): Shouldn't we pull out? - // TODO(jaewan): Should we also protect getPlaybackState()? + // TODO(jaewan): Should we also protect getters not related with metadata? + // Getters are getRatingType(), getPlaybackState(), getSessionActivity(), + // getPlaylistParams()) + // Next ID: 23 + /** + * Command code for the custom command which can be defined by string action in the + * {@link Command}. + */ public static final int COMMAND_CODE_CUSTOM = 0; - public static final int COMMAND_CODE_PLAYBACK_START = 1; + + /** + * Command code for {@link MediaController2#play()}. + * <p> + * Command would be sent directly to the player if the session doesn't reject the request + * through the {@link SessionCallback#onCommandRequest(ControllerInfo, Command)}. + */ + public static final int COMMAND_CODE_PLAYBACK_PLAY = 1; + + /** + * Command code for {@link MediaController2#pause()}. + * <p> + * Command would be sent directly to the player if the session doesn't reject the request + * through the {@link SessionCallback#onCommandRequest(ControllerInfo, Command)}. + */ public static final int COMMAND_CODE_PLAYBACK_PAUSE = 2; + + /** + * Command code for {@link MediaController2#stop()}. + * <p> + * Command would be sent directly to the player if the session doesn't reject the request + * through the {@link SessionCallback#onCommandRequest(ControllerInfo, Command)}. + */ public static final int COMMAND_CODE_PLAYBACK_STOP = 3; + + /** + * Command code for {@link MediaController2#skipToNext()} ()}. + * <p> + * Command would be sent directly to the player if the session doesn't reject the request + * through the {@link SessionCallback#onCommandRequest(ControllerInfo, Command)}. + */ public static final int COMMAND_CODE_PLAYBACK_SKIP_NEXT_ITEM = 4; + + /** + * Command code for {@link MediaController2#skipToPrevious()} ()}. + * <p> + * Command would be sent directly to the player if the session doesn't reject the request + * through the {@link SessionCallback#onCommandRequest(ControllerInfo, Command)}. + */ public static final int COMMAND_CODE_PLAYBACK_SKIP_PREV_ITEM = 5; + + /** + * Command code for {@link MediaController2#prepare()}. + * <p> + * Command would be sent directly to the player if the session doesn't reject the request + * through the {@link SessionCallback#onCommandRequest(ControllerInfo, Command)}. + */ public static final int COMMAND_CODE_PLAYBACK_PREPARE = 6; + + /** + * Command code for {@link MediaController2#fastForward()} ()}. + * <p> + * This is transport control command. Command would be sent directly to the player if the + * session doesn't reject the request through the + * {@link SessionCallback#onCommandRequest(ControllerInfo, Command)}. + */ public static final int COMMAND_CODE_PLAYBACK_FAST_FORWARD = 7; + + /** + * Command code for {@link MediaController2#rewind()}. + * <p> + * Command would be sent directly to the player if the session doesn't reject the request + * through the {@link SessionCallback#onCommandRequest(ControllerInfo, Command)}. + */ public static final int COMMAND_CODE_PLAYBACK_REWIND = 8; + + /** + * Command code for {@link MediaController2#seekTo(long)} ()}. + * <p> + * Command would be sent directly to the player if the session doesn't reject the request + * through the {@link SessionCallback#onCommandRequest(ControllerInfo, Command)}. + */ public static final int COMMAND_CODE_PLAYBACK_SEEK_TO = 9; + /** + * Command code for {@link MediaController2#setCurrentPlaylistItem(int)} ()}. + * <p> + * Command would be sent directly to the player if the session doesn't reject the request + * through the {@link SessionCallback#onCommandRequest(ControllerInfo, Command)}. + */ public static final int COMMAND_CODE_PLAYBACK_SET_CURRENT_PLAYLIST_ITEM = 10; - public static final int COMMAND_CODE_PLAYLIST_GET = 11; + /** + * Command code for {@link MediaController2#setPlaylistParams(PlaylistParams)} ()}. + * <p> + * Command would be sent directly to the player if the session doesn't reject the request + * through the {@link SessionCallback#onCommandRequest(ControllerInfo, Command)}. + */ + public static final int COMMAND_CODE_PLAYBACK_SET_PLAYLIST_PARAMS = 11; + + /** + * Command code for {@link MediaController2#addPlaylistItem(int, MediaItem2)}. + * <p> + * Command would be sent directly to the player if the session doesn't reject the request + * through the {@link SessionCallback#onCommandRequest(ControllerInfo, Command)}. + */ public static final int COMMAND_CODE_PLAYLIST_ADD = 12; + + /** + * Command code for {@link MediaController2#addPlaylistItem(int, MediaItem2)}. + * <p> + * Command would be sent directly to the player if the session doesn't reject the request + * through the {@link SessionCallback#onCommandRequest(ControllerInfo, Command)}. + */ public static final int COMMAND_CODE_PLAYLIST_REMOVE = 13; - public static final int COMMAND_CODE_PLAY_FROM_MEDIA_ID = 14; - public static final int COMMAND_CODE_PLAY_FROM_URI = 15; - public static final int COMMAND_CODE_PLAY_FROM_SEARCH = 16; + /** + * Command code for {@link MediaController2#getPlaylist()}. + * <p> + * Command would be sent directly to the player if the session doesn't reject the request + * through the {@link SessionCallback#onCommandRequest(ControllerInfo, Command)}. + */ + public static final int COMMAND_CODE_PLAYLIST_GET = 14; + + /** + * Command code for both {@link MediaController2#setVolumeTo(int, int)} and + * {@link MediaController2#adjustVolume(int, int)}. + * <p> + * Command would adjust the volume or sent to the volume provider directly if the session + * doesn't reject the request through the + * {@link SessionCallback#onCommandRequest(ControllerInfo, Command)}. + */ + public static final int COMMAND_CODE_SET_VOLUME = 15; + + /** + * Command code for {@link MediaController2#playFromMediaId(String, Bundle)}. + */ + public static final int COMMAND_CODE_PLAY_FROM_MEDIA_ID = 16; + + /** + * Command code for {@link MediaController2#playFromUri(String, Bundle)}. + */ + public static final int COMMAND_CODE_PLAY_FROM_URI = 17; + + /** + * Command code for {@link MediaController2#playFromSearch(String, Bundle)}. + */ + public static final int COMMAND_CODE_PLAY_FROM_SEARCH = 18; + + /** + * Command code for {@link MediaController2#prepareFromMediaId(String, Bundle)}. + */ + public static final int COMMAND_CODE_PREPARE_FROM_MEDIA_ID = 19; + + /** + * Command code for {@link MediaController2#prepareFromUri(Uri, Bundle)}. + */ + public static final int COMMAND_CODE_PREPARE_FROM_URI = 20; - public static final int COMMAND_CODE_PREPARE_FROM_MEDIA_ID = 17; - public static final int COMMAND_CODE_PREPARE_FROM_URI = 18; - public static final int COMMAND_CODE_PREPARE_FROM_SEARCH = 19; + /** + * Command code for {@link MediaController2#prepareFromSearch(String, Bundle)}. + */ + public static final int COMMAND_CODE_PREPARE_FROM_SEARCH = 21; + + /** + * Command code for {@link MediaBrowser2} specific functions that allows navigation and search + * from the {@link MediaLibraryService2}. This would be ignored if a {@link MediaSession2}, + * not {@link android.media.MediaLibraryService2.MediaLibrarySession}, specify this. + * + * @see MediaBrowser2 + */ + public static final int COMMAND_CODE_BROWSER = 22; /** * Define a command that a {@link MediaController2} can send to a {@link MediaSession2}. @@ -117,43 +265,31 @@ public class MediaSession2 implements AutoCloseable { */ // TODO(jaewan): Move this into the updatable. public static final class Command { - private static final String KEY_COMMAND_CODE - = "android.media.media_session2.command.command_code"; - private static final String KEY_COMMAND_CUSTOM_COMMAND - = "android.media.media_session2.command.custom_command"; - private static final String KEY_COMMAND_EXTRA - = "android.media.media_session2.command.extra"; - - private final int mCommandCode; - // Nonnull if it's custom command - private final String mCustomCommand; - private final Bundle mExtra; - - public Command(int commandCode) { - mCommandCode = commandCode; - mCustomCommand = null; - mExtra = null; + private final CommandProvider mProvider; + + public Command(@NonNull Context context, int commandCode) { + mProvider = ApiLoader.getProvider(context) + .createMediaSession2Command(this, commandCode, null, null); } - public Command(@NonNull String action, @Nullable Bundle extra) { + public Command(@NonNull Context context, @NonNull String action, @Nullable Bundle extra) { if (action == null) { throw new IllegalArgumentException("action shouldn't be null"); } - mCommandCode = COMMAND_CODE_CUSTOM; - mCustomCommand = action; - mExtra = extra; + mProvider = ApiLoader.getProvider(context) + .createMediaSession2Command(this, COMMAND_CODE_CUSTOM, action, extra); } public int getCommandCode() { - return mCommandCode; + return mProvider.getCommandCode_impl(); } public @Nullable String getCustomCommand() { - return mCustomCommand; + return mProvider.getCustomCommand_impl(); } public @Nullable Bundle getExtra() { - return mExtra; + return mProvider.getExtra_impl(); } /** @@ -161,28 +297,7 @@ public class MediaSession2 implements AutoCloseable { * @hide */ public Bundle toBundle() { - Bundle bundle = new Bundle(); - bundle.putInt(KEY_COMMAND_CODE, mCommandCode); - bundle.putString(KEY_COMMAND_CUSTOM_COMMAND, mCustomCommand); - bundle.putBundle(KEY_COMMAND_EXTRA, mExtra); - return bundle; - } - - /** - * @return a new Command instance from the Bundle - * @hide - */ - public static Command fromBundle(Bundle command) { - int code = command.getInt(KEY_COMMAND_CODE); - if (code != COMMAND_CODE_CUSTOM) { - return new Command(code); - } else { - String customCommand = command.getString(KEY_COMMAND_CUSTOM_COMMAND); - if (customCommand == null) { - return null; - } - return new Command(customCommand, command.getBundle(KEY_COMMAND_EXTRA)); - } + return mProvider.toBundle_impl(); } @Override @@ -190,73 +305,65 @@ public class MediaSession2 implements AutoCloseable { if (!(obj instanceof Command)) { return false; } - Command other = (Command) obj; - // TODO(jaewan): Should we also compare contents in bundle? - // It may not be possible if the bundle contains private class. - return mCommandCode == other.mCommandCode - && TextUtils.equals(mCustomCommand, other.mCustomCommand); + return mProvider.equals_impl(((Command) obj).mProvider); } @Override public int hashCode() { - final int prime = 31; - return ((mCustomCommand != null) - ? mCustomCommand.hashCode() : 0) * prime + mCommandCode; + return mProvider.hashCode_impl(); + } + + /** + * @return a new Command instance from the Bundle + * @hide + */ + public static Command fromBundle(@NonNull Context context, Bundle command) { + return ApiLoader.getProvider(context).fromBundle_MediaSession2Command(context, command); } } /** * Represent set of {@link Command}. */ - // TODO(jaewan): Move this to updatable public static class CommandGroup { - private static final String KEY_COMMANDS = - "android.media.mediasession2.commandgroup.commands"; - private ArraySet<Command> mCommands = new ArraySet<>(); + private final CommandGroupProvider mProvider; - public CommandGroup() { + public CommandGroup(Context context) { + mProvider = ApiLoader.getProvider(context) + .createMediaSession2CommandGroup(context, this, null); } - public CommandGroup(CommandGroup others) { - mCommands.addAll(others.mCommands); + public CommandGroup(Context context, CommandGroup others) { + mProvider = ApiLoader.getProvider(context) + .createMediaSession2CommandGroup(context, this, others); } public void addCommand(Command command) { - mCommands.add(command); + mProvider.addCommand_impl(command); } public void addAllPredefinedCommands() { - // TODO(jaewan): Is there any better way than this? - mCommands.add(new Command(COMMAND_CODE_PLAYBACK_START)); - mCommands.add(new Command(COMMAND_CODE_PLAYBACK_PAUSE)); - mCommands.add(new Command(COMMAND_CODE_PLAYBACK_STOP)); - mCommands.add(new Command(COMMAND_CODE_PLAYBACK_SKIP_NEXT_ITEM)); - mCommands.add(new Command(COMMAND_CODE_PLAYBACK_SKIP_PREV_ITEM)); - mCommands.add(new Command(COMMAND_CODE_PLAYBACK_PREPARE)); - mCommands.add(new Command(COMMAND_CODE_PLAYBACK_FAST_FORWARD)); - mCommands.add(new Command(COMMAND_CODE_PLAYBACK_REWIND)); - mCommands.add(new Command(COMMAND_CODE_PLAYBACK_SEEK_TO)); - mCommands.add(new Command(COMMAND_CODE_PLAYBACK_SET_CURRENT_PLAYLIST_ITEM)); + mProvider.addAllPredefinedCommands_impl(); } public void removeCommand(Command command) { - mCommands.remove(command); + mProvider.removeCommand_impl(command); } public boolean hasCommand(Command command) { - return mCommands.contains(command); + return mProvider.hasCommand_impl(command); } public boolean hasCommand(int code) { - if (code == COMMAND_CODE_CUSTOM) { - throw new IllegalArgumentException("Use hasCommand(Command) for custom command"); - } - for (int i = 0; i < mCommands.size(); i++) { - if (mCommands.valueAt(i).getCommandCode() == code) { - return true; - } - } - return false; + return mProvider.hasCommand_impl(code); + } + + /** + * @hide + */ + @SystemApi + public CommandGroupProvider getProvider() { + return mProvider; } /** @@ -264,40 +371,16 @@ public class MediaSession2 implements AutoCloseable { * @hide */ public Bundle toBundle() { - ArrayList<Bundle> list = new ArrayList<>(); - for (int i = 0; i < mCommands.size(); i++) { - list.add(mCommands.valueAt(i).toBundle()); - } - Bundle bundle = new Bundle(); - bundle.putParcelableArrayList(KEY_COMMANDS, list); - return bundle; + return mProvider.toBundle_impl(); } /** * @return new instance of CommandGroup from the bundle * @hide */ - public static @Nullable CommandGroup fromBundle(Bundle commands) { - if (commands == null) { - return null; - } - List<Parcelable> list = commands.getParcelableArrayList(KEY_COMMANDS); - if (list == null) { - return null; - } - CommandGroup commandGroup = new CommandGroup(); - for (int i = 0; i < list.size(); i++) { - Parcelable parcelable = list.get(i); - if (!(parcelable instanceof Bundle)) { - continue; - } - Bundle commandBundle = (Bundle) parcelable; - Command command = Command.fromBundle(commandBundle); - if (command != null) { - commandGroup.addCommand(command); - } - } - return commandGroup; + public static @Nullable CommandGroup fromBundle(Context context, Bundle commands) { + return ApiLoader.getProvider(context) + .fromBundle_MediaSession2CommandGroup(context, commands); } } @@ -309,6 +392,12 @@ public class MediaSession2 implements AutoCloseable { */ // TODO(jaewan): Can we move this inside of the updatable for default implementation. public static class SessionCallback { + private final Context mContext; + + public SessionCallback(Context context) { + mContext = context; + } + /** * Called when a controller is created for this session. Return allowed commands for * controller. By default it allows all connection requests and commands. @@ -321,7 +410,7 @@ public class MediaSession2 implements AutoCloseable { */ // TODO(jaewan): Change return type. Once we do, null is for reject. public @Nullable CommandGroup onConnect(@NonNull ControllerInfo controller) { - CommandGroup commands = new CommandGroup(); + CommandGroup commands = new CommandGroup(mContext); commands.addAllPredefinedCommands(); return commands; } @@ -334,22 +423,36 @@ public class MediaSession2 implements AutoCloseable { public void onDisconnected(@NonNull ControllerInfo controller) { } /** - * Called when a controller sent a command to the session, and the command will be sent to - * the player directly unless you reject the request by {@code false}. + * Called when a controller sent a command that will be sent directly to the player. Return + * {@code false} here to reject the request and stop sending command to the player. * * @param controller controller information. * @param command a command. This method will be called for every single command. * @return {@code true} if you want to accept incoming command. {@code false} otherwise. + * @see #COMMAND_CODE_PLAYBACK_PLAY + * @see #COMMAND_CODE_PLAYBACK_PAUSE + * @see #COMMAND_CODE_PLAYBACK_STOP + * @see #COMMAND_CODE_PLAYBACK_SKIP_NEXT_ITEM + * @see #COMMAND_CODE_PLAYBACK_SKIP_PREV_ITEM + * @see #COMMAND_CODE_PLAYBACK_PREPARE + * @see #COMMAND_CODE_PLAYBACK_FAST_FORWARD + * @see #COMMAND_CODE_PLAYBACK_REWIND + * @see #COMMAND_CODE_PLAYBACK_SEEK_TO + * @see #COMMAND_CODE_PLAYBACK_SET_CURRENT_PLAYLIST_ITEM + * @see #COMMAND_CODE_PLAYBACK_SET_PLAYLIST_PARAMS + * @see #COMMAND_CODE_PLAYLIST_ADD + * @see #COMMAND_CODE_PLAYLIST_REMOVE + * @see #COMMAND_CODE_PLAYLIST_GET + * @see #COMMAND_CODE_SET_VOLUME */ - // TODO(jaewan): Add more documentations (or make it clear) which commands can be filtered - // with this. public boolean onCommandRequest(@NonNull ControllerInfo controller, @NonNull Command command) { return true; } /** - * Called when a controller set rating on the currently playing contents. + * Called when a controller set rating on the currently playing contents by + * {@link MediaController2#setRating(Rating2)}. * * @param controller controller information * @param rating new rating from the controller @@ -357,7 +460,8 @@ public class MediaSession2 implements AutoCloseable { public void onSetRating(@NonNull ControllerInfo controller, @NonNull Rating2 rating) { } /** - * Called when a controller sent a custom command. + * Called when a controller sent a custom command through + * {@link MediaController2#sendCustomCommand(Command, Bundle, ResultReceiver)}. * * @param controller controller information * @param customCommand custom command. @@ -369,7 +473,48 @@ public class MediaSession2 implements AutoCloseable { @Nullable ResultReceiver cb) { } /** - * Override to handle requests to prepare for playing a specific mediaId. + * Called when a controller requested to play a specific mediaId through + * {@link MediaController2#playFromMediaId(String, Bundle)}. + * + * @param controller controller information + * @param mediaId media id + * @param extras optional extra bundle + * @see #COMMAND_CODE_PLAY_FROM_MEDIA_ID + */ + public void onPlayFromMediaId(@NonNull ControllerInfo controller, + @NonNull String mediaId, @Nullable Bundle extras) { } + + /** + * Called when a controller requested to begin playback from a search query through + * {@link MediaController2#playFromSearch(String, Bundle)} + * <p> + * An empty query indicates that the app may play any music. The implementation should + * attempt to make a smart choice about what to play. + * + * @param controller controller information + * @param query query string. Can be empty to indicate any suggested media + * @param extras optional extra bundle + * @see #COMMAND_CODE_PLAY_FROM_SEARCH + */ + public void onPlayFromSearch(@NonNull ControllerInfo controller, + @NonNull String query, @Nullable Bundle extras) { } + + /** + * Called when a controller requested to play a specific media item represented by a URI + * through {@link MediaController2#playFromUri(String, Bundle)} + * + * @param controller controller information + * @param uri uri + * @param extras optional extra bundle + * @see #COMMAND_CODE_PLAY_FROM_URI + */ + public void onPlayFromUri(@NonNull ControllerInfo controller, + @NonNull String uri, @Nullable Bundle extras) { } + + /** + * Called when a controller requested to prepare for playing a specific mediaId through + * {@link MediaController2#prepareFromMediaId(String, Bundle)}. + * <p> * During the preparation, a session should not hold audio focus in order to allow other * sessions play seamlessly. The state of playback should be updated to * {@link PlaybackState#STATE_PAUSED} after the preparation is done. @@ -379,28 +524,41 @@ public class MediaSession2 implements AutoCloseable { * <p> * Override {@link #onPlayFromMediaId} to handle requests for starting * playback without preparation. + * + * @param controller controller information + * @param mediaId media id to prepare + * @param extras optional extra bundle + * @see #COMMAND_CODE_PREPARE_FROM_MEDIA_ID */ - public void onPlayFromMediaId(@NonNull ControllerInfo controller, + public void onPrepareFromMediaId(@NonNull ControllerInfo controller, @NonNull String mediaId, @Nullable Bundle extras) { } /** - * Override to handle requests to prepare playback from a search query. An empty query - * indicates that the app may prepare any music. The implementation should attempt to make a - * smart choice about what to play. During the preparation, a session should not hold audio - * focus in order to allow other sessions play seamlessly. The state of playback should be - * updated to {@link PlaybackState#STATE_PAUSED} after the preparation is done. + * Called when a controller requested to prepare playback from a search query through + * {@link MediaController2#prepareFromSearch(String, Bundle)}. * <p> - * The playback of the prepared content should start in the later calls of - * {@link MediaSession2#play()}. + * An empty query indicates that the app may prepare any music. The implementation should + * attempt to make a smart choice about what to play. + * <p> + * The state of playback should be updated to {@link PlaybackState#STATE_PAUSED} after the + * preparation is done. The playback of the prepared content should start in the later + * calls of {@link MediaSession2#play()}. * <p> * Override {@link #onPlayFromSearch} to handle requests for starting playback without * preparation. + * + * @param controller controller information + * @param query query string. Can be empty to indicate any suggested media + * @param extras optional extra bundle + * @see #COMMAND_CODE_PREPARE_FROM_SEARCH */ - public void onPlayFromSearch(@NonNull ControllerInfo controller, + public void onPrepareFromSearch(@NonNull ControllerInfo controller, @NonNull String query, @Nullable Bundle extras) { } /** - * Override to handle requests to prepare a specific media item represented by a URI. + * Called when a controller requested to prepare a specific media item represented by a URI + * through {@link MediaController2#prepareFromUri(Uri, Bundle)}. + * <p></p> * During the preparation, a session should not hold audio focus in order to allow * other sessions play seamlessly. The state of playback should be updated to * {@link PlaybackState#STATE_PAUSED} after the preparation is done. @@ -410,51 +568,14 @@ public class MediaSession2 implements AutoCloseable { * <p> * Override {@link #onPlayFromUri} to handle requests for starting playback without * preparation. - */ - public void onPlayFromUri(@NonNull ControllerInfo controller, - @NonNull String uri, @Nullable Bundle extras) { } - - /** - * Override to handle requests to play a specific mediaId. - */ - public void onPrepareFromMediaId(@NonNull ControllerInfo controller, - @NonNull String mediaId, @Nullable Bundle extras) { } - - /** - * Override to handle requests to begin playback from a search query. An - * empty query indicates that the app may play any music. The - * implementation should attempt to make a smart choice about what to - * play. - */ - public void onPrepareFromSearch(@NonNull ControllerInfo controller, - @NonNull String query, @Nullable Bundle extras) { } - - /** - * Override to handle requests to play a specific media item represented by a URI. + * + * @param controller controller information + * @param uri uri + * @param extras optional extra bundle + * @see #COMMAND_CODE_PREPARE_FROM_URI */ public void onPrepareFromUri(@NonNull ControllerInfo controller, @NonNull Uri uri, @Nullable Bundle extras) { } - - /** - * Called when a controller wants to add a {@link MediaItem2} at the specified position - * in the play queue. - * <p> - * The item from the media controller wouldn't have valid data source descriptor because - * it would have been anonymized when it's sent to the remote process. - * - * @param item The media item to be inserted. - * @param index The index at which the item is to be inserted. - */ - public void onAddPlaylistItem(@NonNull ControllerInfo controller, - @NonNull MediaItem2 item, int index) { } - - /** - * Called when a controller wants to remove the {@link MediaItem2} - * - * @param item - */ - // Can we do this automatically? - public void onRemovePlaylistItem(@NonNull MediaItem2 item) { } }; /** @@ -463,36 +584,11 @@ public class MediaSession2 implements AutoCloseable { * @hide */ static abstract class BuilderBase - <T extends MediaSession2.BuilderBase<T, C>, C extends SessionCallback> { - final Context mContext; - final MediaPlayerInterface mPlayer; - String mId; - Executor mCallbackExecutor; - C mCallback; - VolumeProvider mVolumeProvider; - int mRatingType; - PendingIntent mSessionActivity; + <T extends MediaSession2, U extends BuilderBase<T, U, C>, C extends SessionCallback> { + private final BuilderBaseProvider<T, C> mProvider; - /** - * Constructor. - * - * @param context a context - * @param player a player to handle incoming command from any controller. - * @throws IllegalArgumentException if any parameter is null, or the player is a - * {@link MediaSession2} or {@link MediaController2}. - */ - // TODO(jaewan): Also need executor - public BuilderBase(@NonNull Context context, @NonNull MediaPlayerInterface player) { - if (context == null) { - throw new IllegalArgumentException("context shouldn't be null"); - } - if (player == null) { - throw new IllegalArgumentException("player shouldn't be null"); - } - mContext = context; - mPlayer = player; - // Ensure non-null - mId = ""; + BuilderBase(ProviderCreator<BuilderBase<T, U, C>, BuilderBaseProvider<T, C>> creator) { + mProvider = creator.createProvider(this); } /** @@ -504,9 +600,9 @@ public class MediaSession2 implements AutoCloseable { * * @param volumeProvider The provider that will handle volume changes. Can be {@code null} */ - public T setVolumeProvider(@Nullable VolumeProvider volumeProvider) { - mVolumeProvider = volumeProvider; - return (T) this; + public U setVolumeProvider(@Nullable VolumeProvider volumeProvider) { + mProvider.setVolumeProvider_impl(volumeProvider); + return (U) this; } /** @@ -522,9 +618,9 @@ public class MediaSession2 implements AutoCloseable { * <li>{@link Rating2#RATING_THUMB_UP_DOWN}</li> * </ul> */ - public T setRatingType(@Rating2.Style int type) { - mRatingType = type; - return (T) this; + public U setRatingType(@Rating2.Style int type) { + mProvider.setRatingType_impl(type); + return (U) this; } /** @@ -534,9 +630,9 @@ public class MediaSession2 implements AutoCloseable { * * @param pi The intent to launch to show UI for this session. */ - public T setSessionActivity(@Nullable PendingIntent pi) { - mSessionActivity = pi; - return (T) this; + public U setSessionActivity(@Nullable PendingIntent pi) { + mProvider.setSessionActivity_impl(pi); + return (U) this; } /** @@ -549,12 +645,9 @@ public class MediaSession2 implements AutoCloseable { * @throws IllegalArgumentException if id is {@code null} * @return */ - public T setId(@NonNull String id) { - if (id == null) { - throw new IllegalArgumentException("id shouldn't be null"); - } - mId = id; - return (T) this; + public U setId(@NonNull String id) { + mProvider.setId_impl(id); + return (U) this; } /** @@ -564,17 +657,10 @@ public class MediaSession2 implements AutoCloseable { * @param callback session callback. * @return */ - public T setSessionCallback(@NonNull @CallbackExecutor Executor executor, + public U setSessionCallback(@NonNull @CallbackExecutor Executor executor, @NonNull C callback) { - if (executor == null) { - throw new IllegalArgumentException("executor shouldn't be null"); - } - if (callback == null) { - throw new IllegalArgumentException("callback shouldn't be null"); - } - mCallbackExecutor = executor; - mCallback = callback; - return (T) this; + mProvider.setSessionCallback_impl(executor, callback); + return (U) this; } /** @@ -584,7 +670,9 @@ public class MediaSession2 implements AutoCloseable { * @throws IllegalStateException if the session with the same id is already exists for the * package. */ - public abstract MediaSession2 build(); + public T build() { + return mProvider.build_impl(); + } } /** @@ -593,31 +681,18 @@ public class MediaSession2 implements AutoCloseable { * Any incoming event from the {@link MediaController2} will be handled on the thread * that created session with the {@link Builder#build()}. */ - // TODO(jaewan): Move this to updatable // TODO(jaewan): Add setRatingType() // TODO(jaewan): Add setSessionActivity() - public static final class Builder extends BuilderBase<Builder, SessionCallback> { + public static final class Builder extends BuilderBase<MediaSession2, Builder, SessionCallback> { public Builder(Context context, @NonNull MediaPlayerInterface player) { - super(context, player); - } - - @Override - public MediaSession2 build() { - if (mCallbackExecutor == null) { - mCallbackExecutor = mContext.getMainExecutor(); - } - if (mCallback == null) { - mCallback = new SessionCallback(); - } - return new MediaSession2(mContext, mPlayer, mId, mVolumeProvider, mRatingType, - mSessionActivity, mCallbackExecutor, mCallback); + super((instance) -> ApiLoader.getProvider(context).createMediaSession2Builder( + context, (Builder) instance, player)); } } /** * Information of a controller. */ - // TODO(jaewan): Move implementation to the updatable. public static final class ControllerInfo { private final ControllerInfoProvider mProvider; @@ -627,9 +702,9 @@ public class MediaSession2 implements AutoCloseable { // TODO(jaewan): SystemApi // TODO(jaewan): Also accept componentName to check notificaiton listener. public ControllerInfo(Context context, int uid, int pid, String packageName, - IMediaSession2Callback callback) { + IInterface callback) { mProvider = ApiLoader.getProvider(context) - .createMediaSession2ControllerInfoProvider( + .createMediaSession2ControllerInfo( context, this, uid, pid, packageName, callback); } @@ -658,6 +733,9 @@ public class MediaSession2 implements AutoCloseable { return mProvider.isTrusted_impl(); } + /** + * @hide + */ @SystemApi public ControllerInfoProvider getProvider() { return mProvider; @@ -784,9 +862,9 @@ public class MediaSession2 implements AutoCloseable { * @hide */ // TODO(jaewan): @SystemApi - public static @Nullable CommandButton fromBundle(Bundle bundle) { + public static @Nullable CommandButton fromBundle(Context context, Bundle bundle) { Builder builder = new Builder(); - builder.setCommand(Command.fromBundle(bundle.getBundle(KEY_COMMAND))); + builder.setCommand(Command.fromBundle(context, bundle.getBundle(KEY_COMMAND))); builder.setIconResId(bundle.getInt(KEY_ICON_RES_ID, 0)); builder.setDisplayName(bundle.getString(KEY_DISPLAY_NAME)); builder.setExtra(bundle.getBundle(KEY_EXTRA)); @@ -856,7 +934,7 @@ public class MediaSession2 implements AutoCloseable { /** * Parameter for the playlist. */ - public static class PlaylistParams { + public final static class PlaylistParams { /** * @hide */ @@ -911,79 +989,71 @@ public class MediaSession2 implements AutoCloseable { */ public static final int SHUFFLE_MODE_GROUP = 2; + + private final MediaSession2Provider.PlaylistParamsProvider mProvider; + /** - * Keys used for converting a PlaylistParams object to a bundle object and vice versa. + * Instantiate {@link PlaylistParams} + * + * @param context context + * @param repeatMode repeat mode + * @param shuffleMode shuffle mode + * @param playlistMetadata metadata for the list */ - private static final String KEY_REPEAT_MODE = - "android.media.session2.playlistparams2.repeat_mode"; - private static final String KEY_SHUFFLE_MODE = - "android.media.session2.playlistparams2.shuffle_mode"; - private static final String KEY_MEDIA_METADATA2_BUNDLE = - "android.media.session2.playlistparams2.metadata2_bundle"; - - private @RepeatMode int mRepeatMode; - private @ShuffleMode int mShuffleMode; - - private MediaMetadata2 mPlaylistMetadata; - - public PlaylistParams(@RepeatMode int repeatMode, @ShuffleMode int shuffleMode, - @Nullable MediaMetadata2 playlistMetadata) { - mRepeatMode = repeatMode; - mShuffleMode = shuffleMode; - mPlaylistMetadata = playlistMetadata; + public PlaylistParams(@NonNull Context context, @RepeatMode int repeatMode, + @ShuffleMode int shuffleMode, @Nullable MediaMetadata2 playlistMetadata) { + mProvider = ApiLoader.getProvider(context).createMediaSession2PlaylistParams( + context, this, repeatMode, shuffleMode, playlistMetadata); } - public @RepeatMode int getRepeatMode() { - return mRepeatMode; + /** + * Create a new bundle for this object. + * + * @return + */ + public @NonNull Bundle toBundle() { + return mProvider.toBundle_impl(); } - public @ShuffleMode int getShuffleMode() { - return mShuffleMode; + /** + * Create a new playlist params from the bundle that was previously returned by + * {@link #toBundle}. + * + * @param context context + * @return a new playlist params. Can be {@code null} for error. + */ + public static @Nullable PlaylistParams fromBundle( + @NonNull Context context, @Nullable Bundle bundle) { + return ApiLoader.getProvider(context).fromBundle_PlaylistParams(context, bundle); } - public MediaMetadata2 getPlaylistMetadata() { - return mPlaylistMetadata; + /** + * Get repeat mode + * + * @return repeat mode + * @see #REPEAT_MODE_NONE, #REPEAT_MODE_ONE, #REPEAT_MODE_ALL, #REPEAT_MODE_GROUP + */ + public @RepeatMode int getRepeatMode() { + return mProvider.getRepeatMode_impl(); } /** - * Returns this object as a bundle to share between processes. + * Get shuffle mode * - * @hide + * @return shuffle mode + * @see #SHUFFLE_MODE_NONE, #SHUFFLE_MODE_ALL, #SHUFFLE_MODE_GROUP */ - public Bundle toBundle() { - Bundle bundle = new Bundle(); - bundle.putInt(KEY_REPEAT_MODE, mRepeatMode); - bundle.putInt(KEY_SHUFFLE_MODE, mShuffleMode); - if (mPlaylistMetadata != null) { - bundle.putBundle(KEY_MEDIA_METADATA2_BUNDLE, mPlaylistMetadata.getBundle()); - } - return bundle; + public @ShuffleMode int getShuffleMode() { + return mProvider.getShuffleMode_impl(); } /** - * Creates an instance from a bundle which is previously created by {@link #toBundle()}. + * Get metadata for the playlist * - * @param bundle A bundle created by {@link #toBundle()}. - * @return A new {@link PlaylistParams} instance. Returns {@code null} if the given - * {@param bundle} is null, or if the {@param bundle} has no playlist parameters. - * @hide + * @return metadata. Can be {@code null} */ - public static PlaylistParams fromBundle(Bundle bundle) { - if (bundle == null) { - return null; - } - if (!bundle.containsKey(KEY_REPEAT_MODE) || !bundle.containsKey(KEY_SHUFFLE_MODE)) { - return null; - } - - Bundle metadataBundle = bundle.getBundle(KEY_MEDIA_METADATA2_BUNDLE); - MediaMetadata2 metadata = - metadataBundle == null ? null : new MediaMetadata2(metadataBundle); - - return new PlaylistParams( - bundle.getInt(KEY_REPEAT_MODE), - bundle.getInt(KEY_SHUFFLE_MODE), - metadata); + public @Nullable MediaMetadata2 getPlaylistMetadata() { + return mProvider.getPlaylistMetadata_impl(); } } @@ -1001,23 +1071,15 @@ public class MediaSession2 implements AutoCloseable { * framework had to add heuristics to figure out if an app is * @hide */ - MediaSession2(Context context, MediaPlayerInterface player, String id, - VolumeProvider volumeProvider, int ratingType, PendingIntent sessionActivity, - Executor callbackExecutor, SessionCallback callback) { + @SystemApi + public MediaSession2(MediaSession2Provider provider) { super(); - mProvider = createProvider(context, player, id, volumeProvider, ratingType, sessionActivity, - callbackExecutor, callback); - mProvider.initialize(); - } - - MediaSession2Provider createProvider(Context context, MediaPlayerInterface player, String id, - VolumeProvider volumeProvider, int ratingType, PendingIntent sessionActivity, - Executor callbackExecutor, SessionCallback callback) { - return ApiLoader.getProvider(context) - .createMediaSession2(context, this, player, id, volumeProvider, ratingType, - sessionActivity, callbackExecutor, callback); + mProvider = provider; } + /** + * @hide + */ @SystemApi public MediaSession2Provider getProvider() { return mProvider; @@ -1258,10 +1320,19 @@ public class MediaSession2 implements AutoCloseable { // To match with KEYCODE_MEDIA_SKIP_BACKWARD } - public void setPlaylist(@NonNull List<MediaItem2> playlist, @NonNull PlaylistParams param) { - mProvider.setPlaylist_impl(playlist, param); + /** + * Sets a list of {@link MediaItem2} as the current play list. + * + * @param playlist A list of {@link MediaItem2} objects to set as a play list. + * @throws IllegalArgumentException if given {@param playlist} is null. + */ + public void setPlaylist(@NonNull List<MediaItem2> playlist) { + mProvider.setPlaylist_impl(playlist); } + /** + * Returns the playlist which is lastly set. + */ public List<MediaItem2> getPlaylist() { return mProvider.getPlaylist_impl(); } diff --git a/media/java/android/media/PlaybackState2.java b/media/java/android/media/PlaybackState2.java index da776ebefa38..627974a87137 100644 --- a/media/java/android/media/PlaybackState2.java +++ b/media/java/android/media/PlaybackState2.java @@ -17,6 +17,11 @@ package android.media; import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.Context; +import android.media.update.ApiLoader; +import android.media.update.PlaybackState2Provider; import android.os.Bundle; import java.lang.annotation.Retention; @@ -29,14 +34,51 @@ import java.lang.annotation.RetentionPolicy; * @hide */ public final class PlaybackState2 { - private static final String TAG = "PlaybackState2"; - + // Similar to the PlaybackState2 with following changes + // - Not implement Parcelable and added from/toBundle() + // - Removed playback state that doesn't match with the MediaPlayer2 + // Full list should be finalized when the MediaPlayer2 has getter for the playback state. + // Here's table for the MP2 state and PlaybackState2.State. + // +----------------------------------------+----------------------------------------+ + // | MediaPlayer2 state | Matching PlaybackState2.State | + // | (Names are from MP2' Javadoc) | | + // +----------------------------------------+----------------------------------------+ + // | Idle: Just finished creating MP2 | STATE_NONE | + // | or reset() is called | | + // +----------------------------------------+----------------------------------------+ + // | Initialized: setDataSource/Playlist | N/A (Session/Controller don't | + // | | differentiate with Prepared) | + // +----------------------------------------+----------------------------------------+ + // | Prepared: Prepared after initialized | STATE_PAUSED | + // +----------------------------------------+----------------------------------------+ + // | Started: Started playback | STATE_PLAYING | + // +----------------------------------------+----------------------------------------+ + // | Paused: Paused playback | STATE_PAUSED | + // +----------------------------------------+----------------------------------------+ + // | PlaybackCompleted: Playback is done | STATE_PAUSED | + // +----------------------------------------+----------------------------------------+ + // | Stopped: MP2.stop() is called. | STATE_STOPPED | + // | prepare() is needed to play again | | + // | (Seemingly the same as initialized | | + // | because cannot set data source | | + // | after this) | | + // +----------------------------------------+----------------------------------------+ + // | Error: an API is called in a state | STATE_ERROR | + // | that the API isn't supported | | + // +----------------------------------------+----------------------------------------+ + // | End: MP2.close() is called to release | N/A (MediaSession will be gone) | + // | MP2. Cannot be reused anymore | | + // +----------------------------------------+----------------------------------------+ + // | Started, but | STATE_BUFFERING | + // | EventCallback.onBufferingUpdate() | | + // +----------------------------------------+----------------------------------------+ + // - Removed actions and custom actions. + // - Repeat mode / shuffle mode is now in the PlaylistParams // TODO(jaewan): Replace states from MediaPlayer2 /** * @hide */ - @IntDef({STATE_NONE, STATE_STOPPED, STATE_PREPARED, STATE_PAUSED, STATE_PLAYING, - STATE_FINISH, STATE_BUFFERING, STATE_ERROR}) + @IntDef({STATE_NONE, STATE_STOPPED, STATE_PAUSED, STATE_PLAYING, STATE_BUFFERING, STATE_ERROR}) @Retention(RetentionPolicy.SOURCE) public @interface State {} @@ -52,88 +94,46 @@ public final class PlaybackState2 { public final static int STATE_STOPPED = 1; /** - * State indicating this item is currently prepared - */ - public final static int STATE_PREPARED = 2; - - /** * State indicating this item is currently paused. */ - public final static int STATE_PAUSED = 3; + public final static int STATE_PAUSED = 2; /** * State indicating this item is currently playing. */ - public final static int STATE_PLAYING = 4; - - /** - * State indicating the playback reaches the end of the item. - */ - public final static int STATE_FINISH = 5; + public final static int STATE_PLAYING = 3; /** * State indicating this item is currently buffering and will begin playing * when enough data has buffered. */ - public final static int STATE_BUFFERING = 6; + public final static int STATE_BUFFERING = 4; /** * State indicating this item is currently in an error state. The error * message should also be set when entering this state. */ - public final static int STATE_ERROR = 7; + public final static int STATE_ERROR = 5; /** * Use this value for the position to indicate the position is not known. */ public final static long PLAYBACK_POSITION_UNKNOWN = -1; - /** - * Keys used for converting a PlaybackState2 to a bundle object and vice versa. - */ - private static final String KEY_STATE = "android.media.playbackstate2.state"; - private static final String KEY_POSITION = "android.media.playbackstate2.position"; - private static final String KEY_BUFFERED_POSITION = - "android.media.playbackstate2.buffered_position"; - private static final String KEY_SPEED = "android.media.playbackstate2.speed"; - private static final String KEY_ERROR_MESSAGE = "android.media.playbackstate2.error_message"; - private static final String KEY_UPDATE_TIME = "android.media.playbackstate2.update_time"; - private static final String KEY_ACTIVE_ITEM_ID = "android.media.playbackstate2.active_item_id"; - - private final int mState; - private final long mPosition; - private final long mUpdateTime; - private final float mSpeed; - private final long mBufferedPosition; - private final long mActiveItemId; - private final CharSequence mErrorMessage; + private final PlaybackState2Provider mProvider; // TODO(jaewan): Better error handling? // E.g. media item at #2 has issue, but continue playing #3 // login error. fire intent xxx to login - public PlaybackState2(int state, long position, long updateTime, float speed, - long bufferedPosition, long activeItemId, CharSequence error) { - mState = state; - mPosition = position; - mSpeed = speed; - mUpdateTime = updateTime; - mBufferedPosition = bufferedPosition; - mActiveItemId = activeItemId; - mErrorMessage = error; + public PlaybackState2(@NonNull Context context, int state, long position, long updateTime, + float speed, long bufferedPosition, long activeItemId, CharSequence error) { + mProvider = ApiLoader.getProvider(context).createPlaybackState2(context, this, state, + position, updateTime, speed, bufferedPosition, activeItemId, error); } @Override public String toString() { - StringBuilder bob = new StringBuilder("PlaybackState {"); - bob.append("state=").append(mState); - bob.append(", position=").append(mPosition); - bob.append(", buffered position=").append(mBufferedPosition); - bob.append(", speed=").append(mSpeed); - bob.append(", updated=").append(mUpdateTime); - bob.append(", active item id=").append(mActiveItemId); - bob.append(", error=").append(mErrorMessage); - bob.append("}"); - return bob.toString(); + return mProvider.toString_impl(); } /** @@ -141,22 +141,24 @@ public final class PlaybackState2 { * <ul> * <li> {@link PlaybackState2#STATE_NONE}</li> * <li> {@link PlaybackState2#STATE_STOPPED}</li> - * <li> {@link PlaybackState2#STATE_PLAYING}</li> + * <li> {@link PlaybackState2#STATE_PREPARED}</li> * <li> {@link PlaybackState2#STATE_PAUSED}</li> + * <li> {@link PlaybackState2#STATE_PLAYING}</li> + * <li> {@link PlaybackState2#STATE_FINISH}</li> * <li> {@link PlaybackState2#STATE_BUFFERING}</li> * <li> {@link PlaybackState2#STATE_ERROR}</li> * </ul> */ @State public int getState() { - return mState; + return mProvider.getState_impl(); } /** * Get the current playback position in ms. */ public long getPosition() { - return mPosition; + return mProvider.getPosition_impl(); } /** @@ -165,7 +167,7 @@ public final class PlaybackState2 { * content. */ public long getBufferedPosition() { - return mBufferedPosition; + return mProvider.getBufferedPosition_impl(); } /** @@ -176,7 +178,7 @@ public final class PlaybackState2 { * @return The current speed of playback. */ public float getPlaybackSpeed() { - return mSpeed; + return mProvider.getPlaybackSpeed_impl(); } /** @@ -184,7 +186,7 @@ public final class PlaybackState2 { * {@link PlaybackState2#STATE_ERROR}. */ public CharSequence getErrorMessage() { - return mErrorMessage; + return mProvider.getErrorMessage_impl(); } /** @@ -194,7 +196,7 @@ public final class PlaybackState2 { * @return The last time the position was updated. */ public long getLastPositionUpdateTime() { - return mUpdateTime; + return mProvider.getLastPositionUpdateTime_impl(); } /** @@ -203,53 +205,26 @@ public final class PlaybackState2 { * @return The id of the currently active item in the queue */ public long getCurrentPlaylistItemIndex() { - return mActiveItemId; + return mProvider.getCurrentPlaylistItemIndex_impl(); } /** * Returns this object as a bundle to share between processes. */ - public Bundle toBundle() { - Bundle bundle = new Bundle(); - bundle.putInt(KEY_STATE, mState); - bundle.putLong(KEY_POSITION, mPosition); - bundle.putLong(KEY_UPDATE_TIME, mUpdateTime); - bundle.putFloat(KEY_SPEED, mSpeed); - bundle.putLong(KEY_BUFFERED_POSITION, mBufferedPosition); - bundle.putLong(KEY_ACTIVE_ITEM_ID, mActiveItemId); - bundle.putCharSequence(KEY_ERROR_MESSAGE, mErrorMessage); - return bundle; + public @NonNull Bundle toBundle() { + return mProvider.toBundle_impl(); } /** * Creates an instance from a bundle which is previously created by {@link #toBundle()}. * + * @param context context * @param bundle A bundle created by {@link #toBundle()}. * @return A new {@link PlaybackState2} instance. Returns {@code null} if the given * {@param bundle} is null, or if the {@param bundle} has no playback state parameters. */ - public static PlaybackState2 fromBundle(Bundle bundle) { - if (bundle == null) { - return null; - } - - if (!bundle.containsKey(KEY_STATE) - || !bundle.containsKey(KEY_POSITION) - || !bundle.containsKey(KEY_UPDATE_TIME) - || !bundle.containsKey(KEY_SPEED) - || !bundle.containsKey(KEY_BUFFERED_POSITION) - || !bundle.containsKey(KEY_ACTIVE_ITEM_ID) - || !bundle.containsKey(KEY_ERROR_MESSAGE)) { - return null; - } - - return new PlaybackState2( - bundle.getInt(KEY_STATE), - bundle.getLong(KEY_POSITION), - bundle.getLong(KEY_UPDATE_TIME), - bundle.getFloat(KEY_SPEED), - bundle.getLong(KEY_BUFFERED_POSITION), - bundle.getLong(KEY_ACTIVE_ITEM_ID), - bundle.getCharSequence(KEY_ERROR_MESSAGE)); + public @Nullable static PlaybackState2 fromBundle(@NonNull Context context, + @Nullable Bundle bundle) { + return ApiLoader.getProvider(context).fromBundle_PlaybackState2(context, bundle); } }
\ No newline at end of file diff --git a/media/java/android/media/Rating2.java b/media/java/android/media/Rating2.java index 93aea6f9c1d7..4f77ecd58149 100644 --- a/media/java/android/media/Rating2.java +++ b/media/java/android/media/Rating2.java @@ -16,7 +16,13 @@ package android.media; +import android.annotation.NonNull; +import android.annotation.Nullable; import android.annotation.IntDef; +import android.annotation.SystemApi; +import android.content.Context; +import android.media.update.ApiLoader; +import android.media.update.Rating2Provider; import android.os.Bundle; import android.util.Log; @@ -33,10 +39,8 @@ import java.lang.annotation.RetentionPolicy; * @hide */ public final class Rating2 { - private static final String TAG = "Rating2"; - - private static final String KEY_STYLE = "android.media.rating2.style"; - private static final String KEY_VALUE = "android.media.rating2.value"; + // Mostly same as the android.media.Rating, but it's no longer implements Parcelable for + // updatable support. /** * @hide @@ -91,31 +95,48 @@ public final class Rating2 { */ public final static int RATING_PERCENTAGE = 6; - private final static float RATING_NOT_RATED = -1.0f; + private final Rating2Provider mProvider; - private final int mRatingStyle; + /** + * @hide + */ + @SystemApi + public Rating2(@NonNull Rating2Provider provider) { + mProvider = provider; + } - private final float mRatingValue; + @Override + public String toString() { + return mProvider.toString_impl(); + } - private Rating2(@Style int ratingStyle, float rating) { - mRatingStyle = ratingStyle; - mRatingValue = rating; + /** + * @hide + */ + @SystemApi + public Rating2Provider getProvider() { + return mProvider; } @Override - public String toString() { - return "Rating2:style=" + mRatingStyle + " rating=" - + (mRatingValue < 0.0f ? "unrated" : String.valueOf(mRatingValue)); + public boolean equals(Object obj) { + return mProvider.equals_impl(obj); + } + + @Override + public int hashCode() { + return mProvider.hashCode_impl(); } /** * Create an instance from bundle object, previoulsy created by {@link #toBundle()} * + * @param context context * @param bundle bundle - * @return new Rating2 instance + * @return new Rating2 instance or {@code null} for error */ - public static Rating2 fromBundle(Bundle bundle) { - return new Rating2(bundle.getInt(KEY_STYLE), bundle.getFloat(KEY_VALUE)); + public static Rating2 fromBundle(@NonNull Context context, @Nullable Bundle bundle) { + return ApiLoader.getProvider(context).fromBundle_Rating2(context, bundle); } /** @@ -123,55 +144,45 @@ public final class Rating2 { * @return bundle of this object */ public Bundle toBundle() { - Bundle bundle = new Bundle(); - bundle.putInt(KEY_STYLE, mRatingStyle); - bundle.putFloat(KEY_VALUE, mRatingValue); - return bundle; + return mProvider.toBundle_impl(); } /** * Return a Rating2 instance with no rating. * Create and return a new Rating2 instance with no rating known for the given * rating style. + * @param context context * @param ratingStyle one of {@link #RATING_HEART}, {@link #RATING_THUMB_UP_DOWN}, * {@link #RATING_3_STARS}, {@link #RATING_4_STARS}, {@link #RATING_5_STARS}, * or {@link #RATING_PERCENTAGE}. * @return null if an invalid rating style is passed, a new Rating2 instance otherwise. */ - public static Rating2 newUnratedRating(@Style int ratingStyle) { - switch(ratingStyle) { - case RATING_HEART: - case RATING_THUMB_UP_DOWN: - case RATING_3_STARS: - case RATING_4_STARS: - case RATING_5_STARS: - case RATING_PERCENTAGE: - return new Rating2(ratingStyle, RATING_NOT_RATED); - default: - return null; - } + public static @Nullable Rating2 newUnratedRating(@NonNull Context context, @Style int ratingStyle) { + return ApiLoader.getProvider(context).newUnratedRating_Rating2(context, ratingStyle); } /** * Return a Rating2 instance with a heart-based rating. * Create and return a new Rating2 instance with a rating style of {@link #RATING_HEART}, * and a heart-based rating. + * @param context context * @param hasHeart true for a "heart selected" rating, false for "heart unselected". * @return a new Rating2 instance. */ - public static Rating2 newHeartRating(boolean hasHeart) { - return new Rating2(RATING_HEART, hasHeart ? 1.0f : 0.0f); + public static @Nullable Rating2 newHeartRating(@NonNull Context context, boolean hasHeart) { + return ApiLoader.getProvider(context).newHeartRating_Rating2(context, hasHeart); } /** * Return a Rating2 instance with a thumb-based rating. * Create and return a new Rating2 instance with a {@link #RATING_THUMB_UP_DOWN} * rating style, and a "thumb up" or "thumb down" rating. + * @param context context * @param thumbIsUp true for a "thumb up" rating, false for "thumb down". * @return a new Rating2 instance. */ - public static Rating2 newThumbRating(boolean thumbIsUp) { - return new Rating2(RATING_THUMB_UP_DOWN, thumbIsUp ? 1.0f : 0.0f); + public static @Nullable Rating2 newThumbRating(@NonNull Context context, boolean thumbIsUp) { + return ApiLoader.getProvider(context).newThumbRating_Rating2(context, thumbIsUp); } /** @@ -179,6 +190,7 @@ public final class Rating2 { * Create and return a new Rating2 instance with one of the star-base rating styles * and the given integer or fractional number of stars. Non integer values can for instance * be used to represent an average rating value, which might not be an integer number of stars. + * @param context context * @param starRatingStyle one of {@link #RATING_3_STARS}, {@link #RATING_4_STARS}, * {@link #RATING_5_STARS}. * @param starRating a number ranging from 0.0f to 3.0f, 4.0f or 5.0f according to @@ -186,51 +198,30 @@ public final class Rating2 { * @return null if the rating style is invalid, or the rating is out of range, * a new Rating2 instance otherwise. */ - public static Rating2 newStarRating(@StarStyle int starRatingStyle, float starRating) { - float maxRating = -1.0f; - switch(starRatingStyle) { - case RATING_3_STARS: - maxRating = 3.0f; - break; - case RATING_4_STARS: - maxRating = 4.0f; - break; - case RATING_5_STARS: - maxRating = 5.0f; - break; - default: - Log.e(TAG, "Invalid rating style (" + starRatingStyle + ") for a star rating"); - return null; - } - if ((starRating < 0.0f) || (starRating > maxRating)) { - Log.e(TAG, "Trying to set out of range star-based rating"); - return null; - } - return new Rating2(starRatingStyle, starRating); + public static @Nullable Rating2 newStarRating(@NonNull Context context, + @StarStyle int starRatingStyle, float starRating) { + return ApiLoader.getProvider(context).newStarRating_Rating2( + context, starRatingStyle, starRating); } /** * Return a Rating2 instance with a percentage-based rating. * Create and return a new Rating2 instance with a {@link #RATING_PERCENTAGE} * rating style, and a rating of the given percentage. + * @param context context * @param percent the value of the rating * @return null if the rating is out of range, a new Rating2 instance otherwise. */ - public static Rating2 newPercentageRating(float percent) { - if ((percent < 0.0f) || (percent > 100.0f)) { - Log.e(TAG, "Invalid percentage-based rating value"); - return null; - } else { - return new Rating2(RATING_PERCENTAGE, percent); - } + public static @Nullable Rating2 newPercentageRating(@NonNull Context context, float percent) { + return ApiLoader.getProvider(context).newPercentageRating_Rating2(context, percent); } /** * Return whether there is a rating value available. - * @return true if the instance was not created with {@link #newUnratedRating(int)}. + * @return true if the instance was not created with {@link #newUnratedRating(Context, int)}. */ public boolean isRated() { - return mRatingValue >= 0.0f; + return mProvider.isRated_impl(); } /** @@ -241,7 +232,7 @@ public final class Rating2 { */ @Style public int getRatingStyle() { - return mRatingStyle; + return mProvider.getRatingStyle_impl(); } /** @@ -250,11 +241,7 @@ public final class Rating2 { * if the rating style is not {@link #RATING_HEART} or if it is unrated. */ public boolean hasHeart() { - if (mRatingStyle != RATING_HEART) { - return false; - } else { - return (mRatingValue == 1.0f); - } + return mProvider.hasHeart_impl(); } /** @@ -263,11 +250,7 @@ public final class Rating2 { * if the rating style is not {@link #RATING_THUMB_UP_DOWN} or if it is unrated. */ public boolean isThumbUp() { - if (mRatingStyle != RATING_THUMB_UP_DOWN) { - return false; - } else { - return (mRatingValue == 1.0f); - } + return mProvider.isThumbUp_impl(); } /** @@ -276,16 +259,7 @@ public final class Rating2 { * not star-based, or if it is unrated. */ public float getStarRating() { - switch (mRatingStyle) { - case RATING_3_STARS: - case RATING_4_STARS: - case RATING_5_STARS: - if (isRated()) { - return mRatingValue; - } - default: - return -1.0f; - } + return mProvider.getStarRating_impl(); } /** @@ -294,10 +268,6 @@ public final class Rating2 { * not percentage-based, or if it is unrated. */ public float getPercentRating() { - if ((mRatingStyle != RATING_PERCENTAGE) || !isRated()) { - return -1.0f; - } else { - return mRatingValue; - } + return mProvider.getPercentRating_impl(); } } diff --git a/media/java/android/media/SessionPlayer2.java b/media/java/android/media/SessionPlayer2.java index 2b5a0dc29aab..60acf1683bc3 100644 --- a/media/java/android/media/SessionPlayer2.java +++ b/media/java/android/media/SessionPlayer2.java @@ -97,8 +97,8 @@ public class SessionPlayer2 implements MediaPlayerInterface { } @Override - public void setPlaylist(List<MediaItem2> list, PlaylistParams param) { - mProvider.setPlaylist_impl(list, param); + public void setPlaylist(List<MediaItem2> playlist) { + mProvider.setPlaylist_impl(playlist); } @Override diff --git a/media/java/android/media/SessionToken2.java b/media/java/android/media/SessionToken2.java index 7591eb3a25be..2c2090ceb785 100644 --- a/media/java/android/media/SessionToken2.java +++ b/media/java/android/media/SessionToken2.java @@ -18,14 +18,12 @@ package android.media; import android.annotation.IntDef; import android.annotation.NonNull; -import android.annotation.Nullable; import android.annotation.SystemApi; import android.content.Context; import android.media.session.MediaSessionManager; import android.media.update.ApiLoader; import android.media.update.SessionToken2Provider; import android.os.Bundle; -import android.os.IInterface; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -52,38 +50,45 @@ public final class SessionToken2 { private final SessionToken2Provider mProvider; + // From the return value of android.os.Process.getUidForName(String) when error + private static final int UID_UNKNOWN = -1; + /** * Constructor for the token. You can only create token for session service or library service * to use by {@link MediaController2} or {@link MediaBrowser2}. * * @param context context - * @param type type * @param packageName package name * @param serviceName name of service. Can be {@code null} if it's not an service. */ - public SessionToken2(@NonNull Context context, @TokenType int type, @NonNull String packageName, + public SessionToken2(@NonNull Context context, @NonNull String packageName, @NonNull String serviceName) { - this(context, -1, type, packageName, serviceName, null, null); + this(context, packageName, serviceName, UID_UNKNOWN); } /** - * Constructor for the token. + * Constructor for the token. You can only create token for session service or library service + * to use by {@link MediaController2} or {@link MediaBrowser2}. * * @param context context - * @param uid uid - * @param type type * @param packageName package name * @param serviceName name of service. Can be {@code null} if it's not an service. - * @param id id. Can be {@code null} if serviceName is specified. - * @param sessionBinderInterface sessionBinder. Required for the session. + * @param uid uid of the app. + * @hide + */ + public SessionToken2(@NonNull Context context, @NonNull String packageName, + @NonNull String serviceName, int uid) { + mProvider = ApiLoader.getProvider(context).createSessionToken2( + context, this, packageName, serviceName, uid); + } + + /** + * Constructor for the token. + * @hide */ @SystemApi - public SessionToken2(@NonNull Context context, int uid, @TokenType int type, - @NonNull String packageName, @Nullable String serviceName, @Nullable String id, - @Nullable IInterface sessionBinderInterface) { - mProvider = ApiLoader.getProvider(context) - .createSessionToken2(context, this, uid, type, packageName, - serviceName, id, sessionBinderInterface); + public SessionToken2(@NonNull SessionToken2Provider provider) { + mProvider = provider; } @Override diff --git a/media/java/android/media/VolumeProvider2.java b/media/java/android/media/VolumeProvider2.java new file mode 100644 index 000000000000..00746e25c247 --- /dev/null +++ b/media/java/android/media/VolumeProvider2.java @@ -0,0 +1,151 @@ +/* + * Copyright 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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.media; + +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.SystemApi; +import android.content.Context; +import android.media.update.ApiLoader; +import android.media.update.VolumeProvider2Provider; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * Handles requests to adjust or set the volume on a session. This is also used + * to push volume updates back to the session. The provider must call + * {@link #setCurrentVolume(int)} each time the volume being provided changes. + * <p> + * You can set a volume provider on a session by calling + * {@link MediaSession2#setPlayer(MediaPlayerInterface, VolumeProvider)}. + * + * @hide + */ +public abstract class VolumeProvider2 { + + /** + * @hide + */ + @IntDef({VOLUME_CONTROL_FIXED, VOLUME_CONTROL_RELATIVE, VOLUME_CONTROL_ABSOLUTE}) + @Retention(RetentionPolicy.SOURCE) + public @interface ControlType {} + + /** + * The volume is fixed and can not be modified. Requests to change volume + * should be ignored. + */ + public static final int VOLUME_CONTROL_FIXED = 0; + + /** + * The volume control uses relative adjustment via + * {@link #onAdjustVolume(int)}. Attempts to set the volume to a specific + * value should be ignored. + */ + public static final int VOLUME_CONTROL_RELATIVE = 1; + + /** + * The volume control uses an absolute value. It may be adjusted using + * {@link #onAdjustVolume(int)} or set directly using + * {@link #onSetVolumeTo(int)}. + */ + public static final int VOLUME_CONTROL_ABSOLUTE = 2; + + private final VolumeProvider2Provider mProvider; + + /** + * Create a new volume provider for handling volume events. You must specify + * the type of volume control, the maximum volume that can be used, and the + * current volume on the output. + * + * @param controlType The method for controlling volume that is used by this provider. + * @param maxVolume The maximum allowed volume. + * @param currentVolume The current volume on the output. + */ + public VolumeProvider2(@NonNull Context context, @ControlType int controlType, + int maxVolume, int currentVolume) { + mProvider = ApiLoader.getProvider(context).createVolumeProvider2( + context, this, controlType, maxVolume, currentVolume); + } + + /** + * @hide + */ + @SystemApi + public VolumeProvider2Provider getProvider() { + return mProvider; + } + + /** + * Get the volume control type that this volume provider uses. + * + * @return The volume control type for this volume provider + */ + @ControlType + public final int getControlType() { + return mProvider.getControlType_impl(); + } + + /** + * Get the maximum volume this provider allows. + * + * @return The max allowed volume. + */ + public final int getMaxVolume() { + return mProvider.getMaxVolume_impl(); + } + + /** + * Gets the current volume. This will be the last value set by + * {@link #setCurrentVolume(int)}. + * + * @return The current volume. + */ + public final int getCurrentVolume() { + return mProvider.getCurrentVolume_impl(); + } + + /** + * Notify the system that the current volume has been changed. This must be + * called every time the volume changes to ensure it is displayed properly. + * + * @param currentVolume The current volume on the output. + */ + public final void setCurrentVolume(int currentVolume) { + mProvider.setCurrentVolume_impl(currentVolume); + } + + /** + * Override to handle requests to set the volume of the current output. + * After the volume has been modified {@link #setCurrentVolume} must be + * called to notify the system. + * + * @param volume The volume to set the output to. + */ + public void onSetVolumeTo(int volume) { } + + /** + * Override to handle requests to adjust the volume of the current output. + * Direction will be one of {@link AudioManager#ADJUST_LOWER}, + * {@link AudioManager#ADJUST_RAISE}, {@link AudioManager#ADJUST_SAME}. + * After the volume has been modified {@link #setCurrentVolume} must be + * called to notify the system. + * + * @param direction The direction to change the volume in. + */ + public void onAdjustVolume(int direction) { } +} diff --git a/media/java/android/media/audiopolicy/AudioPolicy.java b/media/java/android/media/audiopolicy/AudioPolicy.java index 7e88c277cc23..4de731a9a8a3 100644 --- a/media/java/android/media/audiopolicy/AudioPolicy.java +++ b/media/java/android/media/audiopolicy/AudioPolicy.java @@ -89,6 +89,8 @@ public class AudioPolicy { private AudioPolicyFocusListener mFocusListener; + private final AudioPolicyVolumeCallback mVolCb; + private Context mContext; private AudioPolicyConfig mConfig; @@ -99,12 +101,15 @@ public class AudioPolicy { public boolean hasFocusListener() { return mFocusListener != null; } /** @hide */ public boolean isFocusPolicy() { return mIsFocusPolicy; } + /** @hide */ + public boolean isVolumeController() { return mVolCb != null; } /** * The parameter is guaranteed non-null through the Builder */ private AudioPolicy(AudioPolicyConfig config, Context context, Looper looper, - AudioPolicyFocusListener fl, AudioPolicyStatusListener sl, boolean isFocusPolicy) { + AudioPolicyFocusListener fl, AudioPolicyStatusListener sl, boolean isFocusPolicy, + AudioPolicyVolumeCallback vc) { mConfig = config; mStatus = POLICY_STATUS_UNREGISTERED; mContext = context; @@ -120,6 +125,7 @@ public class AudioPolicy { mFocusListener = fl; mStatusListener = sl; mIsFocusPolicy = isFocusPolicy; + mVolCb = vc; } /** @@ -134,6 +140,7 @@ public class AudioPolicy { private AudioPolicyFocusListener mFocusListener; private AudioPolicyStatusListener mStatusListener; private boolean mIsFocusPolicy = false; + private AudioPolicyVolumeCallback mVolCb; /** * Constructs a new Builder with no audio mixes. @@ -208,6 +215,22 @@ public class AudioPolicy { mStatusListener = l; } + @SystemApi + /** + * Sets the callback to receive all volume key-related events. + * The callback will only be called if the device is configured to handle volume events + * in the PhoneWindowManager (see config_handleVolumeKeysInWindowManager) + * @param vc + * @return the same Builder instance. + */ + public Builder setAudioPolicyVolumeCallback(@NonNull AudioPolicyVolumeCallback vc) { + if (vc == null) { + throw new IllegalArgumentException("Invalid null volume callback"); + } + mVolCb = vc; + return this; + } + /** * Combines all of the attributes that have been set on this {@code Builder} and returns a * new {@link AudioPolicy} object. @@ -229,7 +252,7 @@ public class AudioPolicy { + "an AudioPolicyFocusListener"); } return new AudioPolicy(new AudioPolicyConfig(mMixes), mContext, mLooper, - mFocusListener, mStatusListener, mIsFocusPolicy); + mFocusListener, mStatusListener, mIsFocusPolicy, mVolCb); } } @@ -455,6 +478,23 @@ public class AudioPolicy { public void onAudioFocusAbandon(AudioFocusInfo afi) {} } + @SystemApi + /** + * Callback class to receive volume change-related events. + * See {@link #Builder.setAudioPolicyVolumeCallback(AudioPolicyCallback)} to configure the + * {@link AudioPolicy} to receive those events. + * + */ + public static abstract class AudioPolicyVolumeCallback { + /** @hide */ + public AudioPolicyVolumeCallback() {} + /** + * Called when volume key-related changes are triggered, on the key down event. + * @param adjustment the type of volume adjustment for the key. + */ + public void onVolumeAdjustment(@AudioManager.VolumeAdjustment int adjustment) {} + } + private void onPolicyStatusChange() { AudioPolicyStatusListener l; synchronized (mLock) { @@ -517,6 +557,13 @@ public class AudioPolicy { } } } + + public void notifyVolumeAdjust(int adjustment) { + sendMsg(MSG_VOL_ADJUST, null /* ignored */, adjustment); + if (DEBUG) { + Log.v(TAG, "notifyVolumeAdjust: " + adjustment); + } + } }; //================================================== @@ -528,6 +575,7 @@ public class AudioPolicy { private final static int MSG_MIX_STATE_UPDATE = 3; private final static int MSG_FOCUS_REQUEST = 4; private final static int MSG_FOCUS_ABANDON = 5; + private final static int MSG_VOL_ADJUST = 6; private class EventHandler extends Handler { public EventHandler(AudioPolicy ap, Looper looper) { @@ -571,6 +619,13 @@ public class AudioPolicy { Log.e(TAG, "Invalid null focus listener for focus abandon event"); } break; + case MSG_VOL_ADJUST: + if (mVolCb != null) { + mVolCb.onVolumeAdjustment(msg.arg1); + } else { // should never be null, but don't crash + Log.e(TAG, "Invalid null volume event"); + } + break; default: Log.e(TAG, "Unknown event " + msg.what); } diff --git a/media/java/android/media/audiopolicy/IAudioPolicyCallback.aidl b/media/java/android/media/audiopolicy/IAudioPolicyCallback.aidl index 86abbb4dc8d9..107e7cd59ca2 100644 --- a/media/java/android/media/audiopolicy/IAudioPolicyCallback.aidl +++ b/media/java/android/media/audiopolicy/IAudioPolicyCallback.aidl @@ -31,4 +31,7 @@ oneway interface IAudioPolicyCallback { // callback for mix activity status update void notifyMixStateUpdate(in String regId, int state); + + // callback for volume events + void notifyVolumeAdjust(int adjustment); } diff --git a/media/java/android/media/session/ISessionManager.aidl b/media/java/android/media/session/ISessionManager.aidl index 37c46cb9f633..2d365d072899 100644 --- a/media/java/android/media/session/ISessionManager.aidl +++ b/media/java/android/media/session/ISessionManager.aidl @@ -17,7 +17,6 @@ package android.media.session; import android.content.ComponentName; import android.media.IRemoteVolumeController; -import android.media.IMediaSession2; import android.media.ISessionTokensListener; import android.media.session.IActiveSessionsListener; import android.media.session.ICallback; diff --git a/media/java/android/media/update/FrameLayoutHelper.java b/media/java/android/media/update/FrameLayoutHelper.java new file mode 100644 index 000000000000..983dc703a9b5 --- /dev/null +++ b/media/java/android/media/update/FrameLayoutHelper.java @@ -0,0 +1,128 @@ +/* + * Copyright 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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.media.update; + +import android.content.Context; +import android.graphics.Canvas; +import android.util.AttributeSet; +import android.view.MotionEvent; +import android.widget.FrameLayout; + +/** + * Helper class for connecting the public API to an updatable implementation. + * + * @see ViewProvider + * + * @hide + */ +public abstract class FrameLayoutHelper<T extends ViewProvider> extends FrameLayout { + /** @hide */ + final public T mProvider; + + /** @hide */ + public FrameLayoutHelper(ProviderCreator<T> creator, + Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + + mProvider = creator.createProvider(this, new SuperProvider()); + } + + /** @hide */ + // TODO @SystemApi + public T getProvider() { + return mProvider; + } + + @Override + public CharSequence getAccessibilityClassName() { + return mProvider.getAccessibilityClassName_impl(); + } + + @Override + public boolean onTouchEvent(MotionEvent ev) { + return mProvider.onTouchEvent_impl(ev); + } + + @Override + public boolean onTrackballEvent(MotionEvent ev) { + return mProvider.onTrackballEvent_impl(ev); + } + + @Override + public void onFinishInflate() { + mProvider.onFinishInflate_impl(); + } + + @Override + public void setEnabled(boolean enabled) { + mProvider.setEnabled_impl(enabled); + } + + @Override + protected void onAttachedToWindow() { + mProvider.onAttachedToWindow_impl(); + } + + @Override + protected void onDetachedFromWindow() { + mProvider.onDetachedFromWindow_impl(); + } + + /** @hide */ + public class SuperProvider implements ViewProvider { + @Override + public CharSequence getAccessibilityClassName_impl() { + return FrameLayoutHelper.super.getAccessibilityClassName(); + } + + @Override + public boolean onTouchEvent_impl(MotionEvent ev) { + return FrameLayoutHelper.super.onTouchEvent(ev); + } + + @Override + public boolean onTrackballEvent_impl(MotionEvent ev) { + return FrameLayoutHelper.super.onTrackballEvent(ev); + } + + @Override + public void onFinishInflate_impl() { + FrameLayoutHelper.super.onFinishInflate(); + } + + @Override + public void setEnabled_impl(boolean enabled) { + FrameLayoutHelper.super.setEnabled(enabled); + } + + @Override + public void onAttachedToWindow_impl() { + FrameLayoutHelper.super.onAttachedToWindow(); + } + + @Override + public void onDetachedFromWindow_impl() { + FrameLayoutHelper.super.onDetachedFromWindow(); + } + } + + /** @hide */ + @FunctionalInterface + public interface ProviderCreator<U extends ViewProvider> { + U createProvider(FrameLayoutHelper<U> instance, ViewProvider superProvider); + } +} diff --git a/media/java/android/media/update/MediaControlView2Provider.java b/media/java/android/media/update/MediaControlView2Provider.java index 6b38c9265cfc..95fe36317164 100644 --- a/media/java/android/media/update/MediaControlView2Provider.java +++ b/media/java/android/media/update/MediaControlView2Provider.java @@ -36,12 +36,10 @@ import android.view.View; // TODO @SystemApi public interface MediaControlView2Provider extends ViewProvider { void setController_impl(MediaController controller); - void show_impl(); - void show_impl(int timeout); boolean isShowing_impl(); - void hide_impl(); - void showSubtitle_impl(); - void hideSubtitle_impl(); - void setPrevNextListeners_impl(View.OnClickListener next, View.OnClickListener prev); - void setButtonVisibility_impl(int button, boolean visible); + void setButtonVisibility_impl(int button, int visibility); + void requestPlayButtonFocus_impl(); + void onVisibilityAggregated_impl(boolean isVisible); + void setTimeout_impl(long timeout); + long getTimeout_impl(); } diff --git a/media/java/android/media/update/MediaController2Provider.java b/media/java/android/media/update/MediaController2Provider.java index b72fda94c688..05790c113804 100644 --- a/media/java/android/media/update/MediaController2Provider.java +++ b/media/java/android/media/update/MediaController2Provider.java @@ -62,7 +62,7 @@ public interface MediaController2Provider extends TransportControlProvider { void removePlaylistItem_impl(MediaItem2 index); void addPlaylistItem_impl(int index, MediaItem2 item); - PlaylistParams getPlaylistParam_impl(); + PlaylistParams getPlaylistParams_impl(); void setPlaylistParams_impl(PlaylistParams params); PlaybackState2 getPlaybackState_impl(); } diff --git a/media/java/android/media/update/MediaItem2Provider.java b/media/java/android/media/update/MediaItem2Provider.java new file mode 100644 index 000000000000..2970f0ec41f8 --- /dev/null +++ b/media/java/android/media/update/MediaItem2Provider.java @@ -0,0 +1,37 @@ +/* + * Copyright 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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.media.update; + +import android.media.DataSourceDesc; +import android.media.MediaMetadata2; +import android.os.Bundle; + +/** + * @hide + */ +// TODO(jaewan): SystemApi +public interface MediaItem2Provider { + Bundle toBundle_impl(); + String toString_impl(); + int getFlags_impl(); + boolean isBrowsable_impl(); + boolean isPlayable_impl(); + void setMetadata_impl(MediaMetadata2 metadata); + MediaMetadata2 getMetadata_impl(); + String getMediaId_impl(); + DataSourceDesc getDataSourceDesc_impl(); +} diff --git a/media/java/android/media/update/MediaLibraryService2Provider.java b/media/java/android/media/update/MediaLibraryService2Provider.java index a56883910805..87f509a932c4 100644 --- a/media/java/android/media/update/MediaLibraryService2Provider.java +++ b/media/java/android/media/update/MediaLibraryService2Provider.java @@ -17,12 +17,15 @@ package android.media.update; import android.annotation.SystemApi; +import android.media.MediaLibraryService2.MediaLibrarySession; +import android.media.MediaLibraryService2.MediaLibrarySessionCallback; import android.media.MediaSession2.ControllerInfo; import android.os.Bundle; /** * @hide */ +// TODO: @SystemApi public interface MediaLibraryService2Provider extends MediaSessionService2Provider { // Nothing new for now diff --git a/media/java/android/media/update/MediaMetadata2Provider.java b/media/java/android/media/update/MediaMetadata2Provider.java new file mode 100644 index 000000000000..55ac43d797d4 --- /dev/null +++ b/media/java/android/media/update/MediaMetadata2Provider.java @@ -0,0 +1,37 @@ +package android.media.update; + +import android.graphics.Bitmap; +import android.media.MediaMetadata2; +import android.media.MediaMetadata2.Builder; +import android.media.Rating2; +import android.os.Bundle; + +import java.util.Set; + +/** + * @hide + */ +// TODO(jaewan): SystemApi +public interface MediaMetadata2Provider { + boolean containsKey_impl(String key); + CharSequence getText_impl(String key); + String getMediaId_impl(); + String getString_impl(String key); + long getLong_impl(String key); + Rating2 getRating_impl(String key); + Bundle toBundle_impl(); + Set<String> keySet_impl(); + int size_impl(); + Bitmap getBitmap_impl(String key); + Bundle getExtra_impl(); + + interface BuilderProvider { + Builder putText_impl(String key, CharSequence value); + Builder putString_impl(String key, String value); + Builder putLong_impl(String key, long value); + Builder putRating_impl(String key, Rating2 value); + Builder putBitmap_impl(String key, Bitmap value); + Builder setExtra_impl(Bundle bundle); + MediaMetadata2 build_impl(); + } +} diff --git a/media/java/android/media/update/MediaSession2Provider.java b/media/java/android/media/update/MediaSession2Provider.java index abe3cae789c9..da4d0c74b30a 100644 --- a/media/java/android/media/update/MediaSession2Provider.java +++ b/media/java/android/media/update/MediaSession2Provider.java @@ -16,15 +16,18 @@ package android.media.update; -import android.media.AudioAttributes; +import android.app.PendingIntent; import android.media.MediaItem2; +import android.media.MediaMetadata2; import android.media.MediaPlayerInterface; import android.media.MediaPlayerInterface.PlaybackListener; +import android.media.MediaSession2; import android.media.MediaSession2.Command; import android.media.MediaSession2.CommandButton; import android.media.MediaSession2.CommandGroup; import android.media.MediaSession2.ControllerInfo; import android.media.MediaSession2.PlaylistParams; +import android.media.MediaSession2.SessionCallback; import android.media.SessionToken2; import android.media.VolumeProvider; import android.os.Bundle; @@ -36,9 +39,8 @@ import java.util.concurrent.Executor; /** * @hide */ +// TODO: @SystemApi public interface MediaSession2Provider extends TransportControlProvider { - void initialize(); - void close_impl(); void setPlayer_impl(MediaPlayerInterface player); void setPlayer_impl(MediaPlayerInterface player, VolumeProvider volumeProvider); @@ -53,7 +55,7 @@ public interface MediaSession2Provider extends TransportControlProvider { void sendCustomCommand_impl(ControllerInfo controller, Command command, Bundle args, ResultReceiver receiver); void sendCustomCommand_impl(Command command, Bundle args); - void setPlaylist_impl(List<MediaItem2> playlist, PlaylistParams param); + void setPlaylist_impl(List<MediaItem2> playlist); List<MediaItem2> getPlaylist_impl(); void setPlaylistParams_impl(PlaylistParams params); PlaylistParams getPlaylistParams_impl(); @@ -61,6 +63,25 @@ public interface MediaSession2Provider extends TransportControlProvider { void addPlaybackListener_impl(Executor executor, PlaybackListener listener); void removePlaybackListener_impl(PlaybackListener listener); + interface CommandProvider { + int getCommandCode_impl(); + String getCustomCommand_impl(); + Bundle getExtra_impl(); + Bundle toBundle_impl(); + + boolean equals_impl(Object ob); + int hashCode_impl(); + } + + interface CommandGroupProvider { + void addCommand_impl(Command command); + void addAllPredefinedCommands_impl(); + void removeCommand_impl(Command command); + boolean hasCommand_impl(Command command); + boolean hasCommand_impl(int code); + Bundle toBundle_impl(); + } + interface ControllerInfoProvider { String getPackageName_impl(); int getUid_impl(); @@ -68,4 +89,20 @@ public interface MediaSession2Provider extends TransportControlProvider { int hashCode_impl(); boolean equals_impl(ControllerInfoProvider obj); } + + interface PlaylistParamsProvider { + int getRepeatMode_impl(); + int getShuffleMode_impl(); + MediaMetadata2 getPlaylistMetadata_impl(); + Bundle toBundle_impl(); + } + + interface BuilderBaseProvider<T extends MediaSession2, C extends SessionCallback> { + void setVolumeProvider_impl(VolumeProvider volumeProvider); + void setRatingType_impl(int type); + void setSessionActivity_impl(PendingIntent pi); + void setId_impl(String id); + void setSessionCallback_impl(Executor executor, C callback); + T build_impl(); + } } diff --git a/media/java/android/media/update/PlaybackInfoProvider.java b/media/java/android/media/update/PlaybackInfoProvider.java new file mode 100644 index 000000000000..36eb58a97df9 --- /dev/null +++ b/media/java/android/media/update/PlaybackInfoProvider.java @@ -0,0 +1,31 @@ +/* + * Copyright 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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.media.update; + +import android.media.AudioAttributes; + +/** + * @hide + */ +// TODO(jaewan): @SystemApi +public interface PlaybackInfoProvider { + int getPlaybackType_impl(); + AudioAttributes getAudioAttributes_impl(); + int getControlType_impl(); + int getMaxVolume_impl(); + int getCurrentVolume_impl(); +} diff --git a/media/java/android/media/update/PlaybackState2Provider.java b/media/java/android/media/update/PlaybackState2Provider.java new file mode 100644 index 000000000000..2875e98bb8a4 --- /dev/null +++ b/media/java/android/media/update/PlaybackState2Provider.java @@ -0,0 +1,43 @@ +/* + * Copyright 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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.media.update; + +import android.os.Bundle; + +/** + * @hide + */ +// TODO(jaewan): @SystemApi +public interface PlaybackState2Provider { + String toString_impl(); + + int getState_impl(); + + long getPosition_impl(); + + long getBufferedPosition_impl(); + + float getPlaybackSpeed_impl(); + + CharSequence getErrorMessage_impl(); + + long getLastPositionUpdateTime_impl(); + + long getCurrentPlaylistItemIndex_impl(); + + Bundle toBundle_impl(); +} diff --git a/media/java/android/media/update/ProviderCreator.java b/media/java/android/media/update/ProviderCreator.java new file mode 100644 index 000000000000..f5f3e470812f --- /dev/null +++ b/media/java/android/media/update/ProviderCreator.java @@ -0,0 +1,23 @@ +/* + * Copyright 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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.media.update; + +/** @hide */ +@FunctionalInterface +public interface ProviderCreator<T, U> { + U createProvider(T instance); +} diff --git a/media/java/android/media/update/Rating2Provider.java b/media/java/android/media/update/Rating2Provider.java new file mode 100644 index 000000000000..8966196890c7 --- /dev/null +++ b/media/java/android/media/update/Rating2Provider.java @@ -0,0 +1,37 @@ +/* + * Copyright 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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.media.update; + +import android.annotation.SystemApi; +import android.os.Bundle; + +/** + * @hide + */ +// TODO(jaewan): @SystemApi +public interface Rating2Provider { + String toString_impl(); + boolean equals_impl(Object obj); + int hashCode_impl(); + Bundle toBundle_impl(); + boolean isRated_impl(); + int getRatingStyle_impl(); + boolean hasHeart_impl(); + boolean isThumbUp_impl(); + float getStarRating_impl(); + float getPercentRating_impl(); +}
\ No newline at end of file diff --git a/media/java/android/media/update/SessionPlayer2Provider.java b/media/java/android/media/update/SessionPlayer2Provider.java index 96fa5e17a03b..e068c211f6d1 100644 --- a/media/java/android/media/update/SessionPlayer2Provider.java +++ b/media/java/android/media/update/SessionPlayer2Provider.java @@ -42,9 +42,9 @@ public interface SessionPlayer2Provider { PlaybackState2 getPlaybackState_impl(); void setAudioAttributes_impl(AudioAttributes attributes); AudioAttributes getAudioAttributes_impl(); - void setPlaylist_impl(List<MediaItem2> list, PlaylistParams param); void addPlaylistItem_impl(int index, MediaItem2 item); void removePlaylistItem_impl(MediaItem2 item); + void setPlaylist_impl(List<MediaItem2> playlist); List<MediaItem2> getPlaylist_impl(); void setCurrentPlaylistItem_impl(int index); void setPlaylistParams_impl(PlaylistParams params); diff --git a/media/java/android/media/update/StaticProvider.java b/media/java/android/media/update/StaticProvider.java index 963bc7475ceb..922b452a2edb 100644 --- a/media/java/android/media/update/StaticProvider.java +++ b/media/java/android/media/update/StaticProvider.java @@ -17,24 +17,33 @@ package android.media.update; import android.annotation.Nullable; -import android.app.PendingIntent; import android.content.Context; +import android.media.DataSourceDesc; import android.media.MediaBrowser2; import android.media.MediaBrowser2.BrowserCallback; import android.media.MediaController2; import android.media.MediaController2.ControllerCallback; +import android.media.MediaItem2; import android.media.MediaLibraryService2; import android.media.MediaLibraryService2.MediaLibrarySession; +import android.media.MediaLibraryService2.MediaLibrarySessionBuilder; import android.media.MediaLibraryService2.MediaLibrarySessionCallback; +import android.media.MediaMetadata2; import android.media.MediaPlayerInterface; import android.media.MediaSession2; +import android.media.MediaSession2.PlaylistParams; import android.media.MediaSession2.SessionCallback; import android.media.MediaSessionService2; +import android.media.PlaybackState2; +import android.media.Rating2; import android.media.SessionPlayer2; import android.media.SessionToken2; -import android.media.VolumeProvider; -import android.media.update.MediaLibraryService2Provider.MediaLibrarySessionProvider; +import android.media.VolumeProvider2; +import android.media.update.MediaSession2Provider.BuilderBaseProvider; +import android.media.update.MediaSession2Provider.CommandGroupProvider; +import android.media.update.MediaSession2Provider.CommandProvider; import android.media.update.MediaSession2Provider.ControllerInfoProvider; +import android.media.update.MediaSession2Provider.PlaylistParamsProvider; import android.os.Bundle; import android.os.IInterface; import android.util.AttributeSet; @@ -57,26 +66,64 @@ public interface StaticProvider { VideoView2 instance, ViewProvider superProvider, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes); - MediaSession2Provider createMediaSession2(Context context, MediaSession2 instance, - MediaPlayerInterface player, String id, VolumeProvider volumeProvider, int ratingType, - PendingIntent sessionActivity, Executor executor, SessionCallback callback); - ControllerInfoProvider createMediaSession2ControllerInfoProvider(Context context, + CommandProvider createMediaSession2Command(MediaSession2.Command instance, + int commandCode, String action, Bundle extra); + MediaSession2.Command fromBundle_MediaSession2Command(Context context, Bundle bundle); + CommandGroupProvider createMediaSession2CommandGroup(Context context, + MediaSession2.CommandGroup instance, MediaSession2.CommandGroup others); + MediaSession2.CommandGroup fromBundle_MediaSession2CommandGroup(Context context, Bundle bundle); + ControllerInfoProvider createMediaSession2ControllerInfo(Context context, MediaSession2.ControllerInfo instance, int uid, int pid, String packageName, IInterface callback); + PlaylistParamsProvider createMediaSession2PlaylistParams(Context context, + PlaylistParams playlistParams, int repeatMode, int shuffleMode, + MediaMetadata2 playlistMetadata); + PlaylistParams fromBundle_PlaylistParams(Context context, Bundle bundle); + BuilderBaseProvider<MediaSession2, SessionCallback> createMediaSession2Builder( + Context context, MediaSession2.Builder instance, MediaPlayerInterface player); + MediaController2Provider createMediaController2(Context context, MediaController2 instance, SessionToken2 token, Executor executor, ControllerCallback callback); + MediaBrowser2Provider createMediaBrowser2(Context context, MediaBrowser2 instance, SessionToken2 token, Executor executor, BrowserCallback callback); + MediaSessionService2Provider createMediaSessionService2(MediaSessionService2 instance); + MediaSessionService2Provider createMediaLibraryService2(MediaLibraryService2 instance); - MediaLibrarySessionProvider createMediaLibraryService2MediaLibrarySession(Context context, - MediaLibrarySession instance, MediaPlayerInterface player, String id, - VolumeProvider volumeProvider, int ratingType, PendingIntent sessionActivity, - Executor executor, MediaLibrarySessionCallback callback); + BuilderBaseProvider<MediaLibrarySession, MediaLibrarySessionCallback> + createMediaLibraryService2Builder( + Context context, MediaLibrarySessionBuilder instance, MediaPlayerInterface player, + Executor callbackExecutor, MediaLibrarySessionCallback callback); + SessionToken2Provider createSessionToken2(Context context, SessionToken2 instance, - int uid, int type, String packageName, String serviceName, String id, - IInterface sessionBinderInterface); + String packageName, String serviceName, int uid); SessionToken2 SessionToken2_fromBundle(Context context, Bundle bundle); SessionPlayer2Provider createSessionPlayer2(Context context, SessionPlayer2 instance); + + MediaItem2Provider createMediaItem2(Context context, MediaItem2 mediaItem2, + String mediaId, DataSourceDesc dsd, MediaMetadata2 metadata, int flags); + MediaItem2 fromBundle_MediaItem2(Context context, Bundle bundle); + + VolumeProvider2Provider createVolumeProvider2(Context context, VolumeProvider2 instance, + int controlType, int maxVolume, int currentVolume); + + MediaMetadata2 fromBundle_MediaMetadata2(Context context, Bundle bundle); + MediaMetadata2Provider.BuilderProvider createMediaMetadata2Builder( + Context context, MediaMetadata2.Builder builder); + MediaMetadata2Provider.BuilderProvider createMediaMetadata2Builder( + Context context, MediaMetadata2.Builder builder, MediaMetadata2 source); + + Rating2 newUnratedRating_Rating2(Context context, int ratingStyle); + Rating2 fromBundle_Rating2(Context context, Bundle bundle); + Rating2 newHeartRating_Rating2(Context context, boolean hasHeart); + Rating2 newThumbRating_Rating2(Context context, boolean thumbIsUp); + Rating2 newStarRating_Rating2(Context context, int starRatingStyle, float starRating); + Rating2 newPercentageRating_Rating2(Context context, float percent); + + PlaybackState2Provider createPlaybackState2(Context context, PlaybackState2 instance, int state, + long position, long updateTime, float speed, long bufferedPosition, long activeItemId, + CharSequence error); + PlaybackState2 fromBundle_PlaybackState2(Context context, Bundle bundle); } diff --git a/media/java/android/media/update/VideoView2Provider.java b/media/java/android/media/update/VideoView2Provider.java index c5b58f7bb611..10f03d223d9f 100644 --- a/media/java/android/media/update/VideoView2Provider.java +++ b/media/java/android/media/update/VideoView2Provider.java @@ -47,8 +47,7 @@ public interface VideoView2Provider extends ViewProvider { void setMediaControlView2_impl(MediaControlView2 mediaControlView); MediaController getMediaController_impl(); MediaControlView2 getMediaControlView2_impl(); - void showSubtitle_impl(); - void hideSubtitle_impl(); + void showSubtitle_impl(boolean show); // TODO: remove setSpeed_impl once MediaController2 is ready. void setSpeed_impl(float speed); void setAudioFocusRequest_impl(int focusGain); diff --git a/media/java/android/media/update/VolumeProvider2Provider.java b/media/java/android/media/update/VolumeProvider2Provider.java new file mode 100644 index 000000000000..5657af60eb6b --- /dev/null +++ b/media/java/android/media/update/VolumeProvider2Provider.java @@ -0,0 +1,27 @@ +/* + * Copyright 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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.media.update; + +/** + * @hide + */ +// TODO(jaewan): @SystemApi +public interface VolumeProvider2Provider { + int getControlType_impl(); + int getMaxVolume_impl(); + int getCurrentVolume_impl(); + void setCurrentVolume_impl(int currentVolume); +} diff --git a/media/jni/Android.bp b/media/jni/Android.bp index 051c802dcbd4..7e5f581dd1c6 100644 --- a/media/jni/Android.bp +++ b/media/jni/Android.bp @@ -34,6 +34,7 @@ cc_library_shared { "libutils", "libbinder", "libmedia", + "libmediaextractor", "libmedia_omx", "libmediametrics", "libmediadrm", diff --git a/media/jni/android_media_MediaDrm.cpp b/media/jni/android_media_MediaDrm.cpp index 95b07f1a4efe..4f06caaea703 100644 --- a/media/jni/android_media_MediaDrm.cpp +++ b/media/jni/android_media_MediaDrm.cpp @@ -1134,6 +1134,27 @@ static jobject android_media_MediaDrm_getSecureStops( return ListOfVectorsToArrayListOfByteArray(env, secureStops); } +static jobject android_media_MediaDrm_getSecureStopIds( + JNIEnv *env, jobject thiz) { + sp<IDrm> drm = GetDrm(env, thiz); + + if (drm == NULL) { + jniThrowException(env, "java/lang/IllegalStateException", + "MediaDrm obj is null"); + return NULL; + } + + List<Vector<uint8_t> > secureStopIds; + + status_t err = drm->getSecureStopIds(secureStopIds); + + if (throwExceptionAsNecessary(env, err, "Failed to get secure stop Ids")) { + return NULL; + } + + return ListOfVectorsToArrayListOfByteArray(env, secureStopIds); +} + static jbyteArray android_media_MediaDrm_getSecureStop( JNIEnv *env, jobject thiz, jbyteArray ssid) { sp<IDrm> drm = GetDrm(env, thiz); @@ -1168,7 +1189,22 @@ static void android_media_MediaDrm_releaseSecureStops( throwExceptionAsNecessary(env, err, "Failed to release secure stops"); } -static void android_media_MediaDrm_releaseAllSecureStops( +static void android_media_MediaDrm_removeSecureStop( + JNIEnv *env, jobject thiz, jbyteArray ssid) { + sp<IDrm> drm = GetDrm(env, thiz); + + if (drm == NULL) { + jniThrowException(env, "java/lang/IllegalStateException", + "MediaDrm obj is null"); + return; + } + + status_t err = drm->removeSecureStop(JByteArrayToVector(env, ssid)); + + throwExceptionAsNecessary(env, err, "Failed to remove secure stop"); +} + +static void android_media_MediaDrm_removeAllSecureStops( JNIEnv *env, jobject thiz) { sp<IDrm> drm = GetDrm(env, thiz); @@ -1176,9 +1212,9 @@ static void android_media_MediaDrm_releaseAllSecureStops( return; } - status_t err = drm->releaseAllSecureStops(); + status_t err = drm->removeAllSecureStops(); - throwExceptionAsNecessary(env, err, "Failed to release all secure stops"); + throwExceptionAsNecessary(env, err, "Failed to remove all secure stops"); } @@ -1719,14 +1755,20 @@ static const JNINativeMethod gMethods[] = { { "getSecureStops", "()Ljava/util/List;", (void *)android_media_MediaDrm_getSecureStops }, + { "getSecureStopIds", "()Ljava/util/List;", + (void *)android_media_MediaDrm_getSecureStopIds }, + { "getSecureStop", "([B)[B", (void *)android_media_MediaDrm_getSecureStop }, { "releaseSecureStops", "([B)V", (void *)android_media_MediaDrm_releaseSecureStops }, - { "releaseAllSecureStops", "()V", - (void *)android_media_MediaDrm_releaseAllSecureStops }, + { "removeSecureStop", "([B)V", + (void *)android_media_MediaDrm_removeSecureStop }, + + { "removeAllSecureStops", "()V", + (void *)android_media_MediaDrm_removeAllSecureStops }, { "getConnectedHdcpLevel", "()I", (void *)android_media_MediaDrm_getConnectedHdcpLevel }, diff --git a/media/jni/android_media_MediaMetadataRetriever.cpp b/media/jni/android_media_MediaMetadataRetriever.cpp index 42ebf6ab8cfc..ff854c51c437 100644 --- a/media/jni/android_media_MediaMetadataRetriever.cpp +++ b/media/jni/android_media_MediaMetadataRetriever.cpp @@ -68,17 +68,26 @@ static void process_media_retriever_call(JNIEnv *env, status_t opStatus, const c } } -static MediaMetadataRetriever* getRetriever(JNIEnv* env, jobject thiz) +static sp<MediaMetadataRetriever> getRetriever(JNIEnv* env, jobject thiz) { // No lock is needed, since it is called internally by other methods that are protected MediaMetadataRetriever* retriever = (MediaMetadataRetriever*) env->GetLongField(thiz, fields.context); return retriever; } -static void setRetriever(JNIEnv* env, jobject thiz, MediaMetadataRetriever* retriever) +static void setRetriever(JNIEnv* env, jobject thiz, const sp<MediaMetadataRetriever> &retriever) { // No lock is needed, since it is called internally by other methods that are protected - env->SetLongField(thiz, fields.context, (jlong) retriever); + + if (retriever != NULL) { + retriever->incStrong(thiz); + } + sp<MediaMetadataRetriever> old = getRetriever(env, thiz); + if (old != NULL) { + old->decStrong(thiz); + } + + env->SetLongField(thiz, fields.context, (jlong) retriever.get()); } static void @@ -87,7 +96,7 @@ android_media_MediaMetadataRetriever_setDataSourceAndHeaders( jobjectArray keys, jobjectArray values) { ALOGV("setDataSource"); - MediaMetadataRetriever* retriever = getRetriever(env, thiz); + sp<MediaMetadataRetriever> retriever = getRetriever(env, thiz); if (retriever == 0) { jniThrowException( env, @@ -146,7 +155,7 @@ android_media_MediaMetadataRetriever_setDataSourceAndHeaders( static void android_media_MediaMetadataRetriever_setDataSourceFD(JNIEnv *env, jobject thiz, jobject fileDescriptor, jlong offset, jlong length) { ALOGV("setDataSource"); - MediaMetadataRetriever* retriever = getRetriever(env, thiz); + sp<MediaMetadataRetriever> retriever = getRetriever(env, thiz); if (retriever == 0) { jniThrowException(env, "java/lang/IllegalStateException", "No retriever available"); return; @@ -175,7 +184,7 @@ static void android_media_MediaMetadataRetriever_setDataSourceFD(JNIEnv *env, jo static void android_media_MediaMetadataRetriever_setDataSourceCallback(JNIEnv *env, jobject thiz, jobject dataSource) { ALOGV("setDataSourceCallback"); - MediaMetadataRetriever* retriever = getRetriever(env, thiz); + sp<MediaMetadataRetriever> retriever = getRetriever(env, thiz); if (retriever == 0) { jniThrowException(env, "java/lang/IllegalStateException", "No retriever available"); return; @@ -325,7 +334,7 @@ static jobject android_media_MediaMetadataRetriever_getFrameAtTime( { ALOGV("getFrameAtTime: %lld us option: %d dst width: %d heigh: %d", (long long)timeUs, option, dst_width, dst_height); - MediaMetadataRetriever* retriever = getRetriever(env, thiz); + sp<MediaMetadataRetriever> retriever = getRetriever(env, thiz); if (retriever == 0) { jniThrowException(env, "java/lang/IllegalStateException", "No retriever available"); return NULL; @@ -349,7 +358,7 @@ static jobject android_media_MediaMetadataRetriever_getImageAtIndex( JNIEnv *env, jobject thiz, jint index) { ALOGV("getImageAtIndex: index %d", index); - MediaMetadataRetriever* retriever = getRetriever(env, thiz); + sp<MediaMetadataRetriever> retriever = getRetriever(env, thiz); if (retriever == 0) { jniThrowException(env, "java/lang/IllegalStateException", "No retriever available"); return NULL; @@ -373,7 +382,7 @@ static jobjectArray android_media_MediaMetadataRetriever_getFrameAtIndex( JNIEnv *env, jobject thiz, jint frameIndex, jint numFrames) { ALOGV("getFrameAtIndex: frameIndex %d, numFrames %d", frameIndex, numFrames); - MediaMetadataRetriever* retriever = getRetriever(env, thiz); + sp<MediaMetadataRetriever> retriever = getRetriever(env, thiz); if (retriever == 0) { jniThrowException(env, "java/lang/IllegalStateException", "No retriever available"); @@ -411,7 +420,7 @@ static jbyteArray android_media_MediaMetadataRetriever_getEmbeddedPicture( JNIEnv *env, jobject thiz, jint pictureType) { ALOGV("getEmbeddedPicture: %d", pictureType); - MediaMetadataRetriever* retriever = getRetriever(env, thiz); + sp<MediaMetadataRetriever> retriever = getRetriever(env, thiz); if (retriever == 0) { jniThrowException(env, "java/lang/IllegalStateException", "No retriever available"); return NULL; @@ -446,7 +455,7 @@ static jbyteArray android_media_MediaMetadataRetriever_getEmbeddedPicture( static jobject android_media_MediaMetadataRetriever_extractMetadata(JNIEnv *env, jobject thiz, jint keyCode) { ALOGV("extractMetadata"); - MediaMetadataRetriever* retriever = getRetriever(env, thiz); + sp<MediaMetadataRetriever> retriever = getRetriever(env, thiz); if (retriever == 0) { jniThrowException(env, "java/lang/IllegalStateException", "No retriever available"); return NULL; @@ -464,9 +473,7 @@ static void android_media_MediaMetadataRetriever_release(JNIEnv *env, jobject th { ALOGV("release"); Mutex::Autolock lock(sLock); - MediaMetadataRetriever* retriever = getRetriever(env, thiz); - delete retriever; - setRetriever(env, thiz, (MediaMetadataRetriever*) 0); + setRetriever(env, thiz, NULL); } static void android_media_MediaMetadataRetriever_native_finalize(JNIEnv *env, jobject thiz) @@ -533,7 +540,7 @@ static void android_media_MediaMetadataRetriever_native_init(JNIEnv *env) static void android_media_MediaMetadataRetriever_native_setup(JNIEnv *env, jobject thiz) { ALOGV("native_setup"); - MediaMetadataRetriever* retriever = new MediaMetadataRetriever(); + sp<MediaMetadataRetriever> retriever = new MediaMetadataRetriever(); if (retriever == 0) { jniThrowException(env, "java/lang/RuntimeException", "Out of memory"); return; diff --git a/media/lib/remotedisplay/Android.mk b/media/lib/remotedisplay/Android.mk index ea1ac2b1b08d..e88c0f1a8dc8 100644 --- a/media/lib/remotedisplay/Android.mk +++ b/media/lib/remotedisplay/Android.mk @@ -22,9 +22,7 @@ include $(CLEAR_VARS) LOCAL_MODULE:= com.android.media.remotedisplay LOCAL_MODULE_TAGS := optional -LOCAL_SRC_FILES := \ - $(call all-java-files-under, java) \ - $(call all-aidl-files-under, java) +LOCAL_SRC_FILES := $(call all-java-files-under, java) include $(BUILD_JAVA_LIBRARY) diff --git a/media/lib/signer/Android.mk b/media/lib/signer/Android.mk index b0d317713c98..69ca4d2fc8c5 100644 --- a/media/lib/signer/Android.mk +++ b/media/lib/signer/Android.mk @@ -22,9 +22,7 @@ include $(CLEAR_VARS) LOCAL_MODULE:= com.android.mediadrm.signer LOCAL_MODULE_TAGS := optional -LOCAL_SRC_FILES := \ - $(call all-java-files-under, java) \ - $(call all-aidl-files-under, java) +LOCAL_SRC_FILES := $(call all-java-files-under, java) include $(BUILD_JAVA_LIBRARY) diff --git a/media/lib/tvremote/Android.mk b/media/lib/tvremote/Android.mk index 06838c2dbf97..1ffdd6241f6e 100644 --- a/media/lib/tvremote/Android.mk +++ b/media/lib/tvremote/Android.mk @@ -22,9 +22,7 @@ include $(CLEAR_VARS) LOCAL_MODULE:= com.android.media.tv.remoteprovider LOCAL_MODULE_TAGS := optional -LOCAL_SRC_FILES := \ - $(call all-java-files-under, java) \ - $(call all-aidl-files-under, java) +LOCAL_SRC_FILES := $(call all-java-files-under, java) LOCAL_DEX_PREOPT := false @@ -45,4 +43,4 @@ LOCAL_MODULE_PATH := $(TARGET_OUT_ETC)/permissions LOCAL_SRC_FILES := $(LOCAL_MODULE) -include $(BUILD_PREBUILT)
\ No newline at end of file +include $(BUILD_PREBUILT) diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/helpers/StaticMetadata.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/helpers/StaticMetadata.java index 5680d9f9e4c3..b3f443b30a70 100644 --- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/helpers/StaticMetadata.java +++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/helpers/StaticMetadata.java @@ -1860,9 +1860,6 @@ public class StaticMetadata { return new ArrayList<Integer>(); } - checkArrayValuesInRange(key, availableCaps, - CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_BACKWARD_COMPATIBLE, - CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_CONSTRAINED_HIGH_SPEED_VIDEO); capList = Arrays.asList(CameraTestUtils.toObject(availableCaps)); return capList; } diff --git a/packages/BackupRestoreConfirmation/AndroidManifest.xml b/packages/BackupRestoreConfirmation/AndroidManifest.xml index 8141fa72e56f..e67b3be43ea1 100644 --- a/packages/BackupRestoreConfirmation/AndroidManifest.xml +++ b/packages/BackupRestoreConfirmation/AndroidManifest.xml @@ -30,6 +30,7 @@ android:title="" android:windowSoftInputMode="stateAlwaysHidden" android:excludeFromRecents="true" + android:launchMode="singleTop" android:exported="true" > </activity> </application> diff --git a/packages/BackupRestoreConfirmation/src/com/android/backupconfirm/BackupRestoreConfirmation.java b/packages/BackupRestoreConfirmation/src/com/android/backupconfirm/BackupRestoreConfirmation.java index 9fa7a6646c9c..d6b6bf8d1e56 100644 --- a/packages/BackupRestoreConfirmation/src/com/android/backupconfirm/BackupRestoreConfirmation.java +++ b/packages/BackupRestoreConfirmation/src/com/android/backupconfirm/BackupRestoreConfirmation.java @@ -52,7 +52,9 @@ public class BackupRestoreConfirmation extends Activity { static final String TAG = "BackupRestoreConfirmation"; static final boolean DEBUG = true; - static final String DID_ACKNOWLEDGE = "did_acknowledge"; + static final String KEY_DID_ACKNOWLEDGE = "did_acknowledge"; + static final String KEY_TOKEN = "token"; + static final String KEY_ACTION = "action"; static final int MSG_START_BACKUP = 1; static final int MSG_BACKUP_PACKAGE = 2; @@ -69,6 +71,7 @@ public class BackupRestoreConfirmation extends Activity { int mToken; boolean mIsEncrypted; boolean mDidAcknowledge; + String mAction; TextView mStatusView; TextView mCurPassword; @@ -134,26 +137,9 @@ public class BackupRestoreConfirmation extends Activity { super.onCreate(icicle); final Intent intent = getIntent(); - final String action = intent.getAction(); - final int layoutId; - final int titleId; - if (action.equals(FullBackup.FULL_BACKUP_INTENT_ACTION)) { - layoutId = R.layout.confirm_backup; - titleId = R.string.backup_confirm_title; - } else if (action.equals(FullBackup.FULL_RESTORE_INTENT_ACTION)) { - layoutId = R.layout.confirm_restore; - titleId = R.string.restore_confirm_title; - } else { - Slog.w(TAG, "Backup/restore confirmation activity launched with invalid action!"); - finish(); - return; - } - - mToken = intent.getIntExtra(FullBackup.CONF_TOKEN_INTENT_EXTRA, -1); - if (mToken < 0) { - Slog.e(TAG, "Backup/restore confirmation requested but no token passed!"); - finish(); + boolean tokenValid = setTokenOrFinish(intent, icicle); + if (!tokenValid) { // already called finish() return; } @@ -169,6 +155,61 @@ public class BackupRestoreConfirmation extends Activity { mObserver.setHandler(mHandler); } + setViews(intent, icicle); + } + + @Override + public void onNewIntent(Intent intent) { + super.onNewIntent(intent); + setIntent(intent); + + boolean tokenValid = setTokenOrFinish(intent, null); + if (!tokenValid) { // already called finish() + return; + } + + setViews(intent, null); + } + + private boolean setTokenOrFinish(Intent intent, Bundle icicle) { + mToken = intent.getIntExtra(FullBackup.CONF_TOKEN_INTENT_EXTRA, -1); + + // for relaunch, we try to use the last token before exit + if (icicle != null) { + mToken = icicle.getInt(KEY_TOKEN, mToken); + } + + if (mToken < 0) { + Slog.e(TAG, "Backup/restore confirmation requested but no token passed!"); + finish(); + return false; + } + + return true; + } + + private void setViews(Intent intent, Bundle icicle) { + mAction = intent.getAction(); + + // for relaunch, we try to use the last action before exit + if (icicle != null) { + mAction = icicle.getString(KEY_ACTION, mAction); + } + + final int layoutId; + final int titleId; + if (mAction.equals(FullBackup.FULL_BACKUP_INTENT_ACTION)) { + layoutId = R.layout.confirm_backup; + titleId = R.string.backup_confirm_title; + } else if (mAction.equals(FullBackup.FULL_RESTORE_INTENT_ACTION)) { + layoutId = R.layout.confirm_restore; + titleId = R.string.restore_confirm_title; + } else { + Slog.w(TAG, "Backup/restore confirmation activity launched with invalid action!"); + finish(); + return; + } + setTitle(titleId); setContentView(layoutId); @@ -202,7 +243,7 @@ public class BackupRestoreConfirmation extends Activity { // if we're a relaunch we may need to adjust button enable state if (icicle != null) { - mDidAcknowledge = icicle.getBoolean(DID_ACKNOWLEDGE, false); + mDidAcknowledge = icicle.getBoolean(KEY_DID_ACKNOWLEDGE, false); mAllowButton.setEnabled(!mDidAcknowledge); mDenyButton.setEnabled(!mDidAcknowledge); } @@ -249,7 +290,9 @@ public class BackupRestoreConfirmation extends Activity { @Override protected void onSaveInstanceState(Bundle outState) { - outState.putBoolean(DID_ACKNOWLEDGE, mDidAcknowledge); + outState.putBoolean(KEY_DID_ACKNOWLEDGE, mDidAcknowledge); + outState.putInt(KEY_TOKEN, mToken); + outState.putString(KEY_ACTION, mAction); } void sendAcknowledgement(int token, boolean allow, IFullBackupRestoreObserver observer) { diff --git a/packages/PrintSpooler/src/com/android/printspooler/model/PrintSpoolerService.java b/packages/PrintSpooler/src/com/android/printspooler/model/PrintSpoolerService.java index 6c7441802d63..f6a259dbf982 100644 --- a/packages/PrintSpooler/src/com/android/printspooler/model/PrintSpoolerService.java +++ b/packages/PrintSpooler/src/com/android/printspooler/model/PrintSpoolerService.java @@ -889,7 +889,7 @@ public final class PrintSpoolerService extends Service { private PersistenceManager() { mStatePersistFile = new AtomicFile(new File(getFilesDir(), - PERSIST_FILE_NAME)); + PERSIST_FILE_NAME), "print-spooler"); } public void writeStateLocked() { diff --git a/packages/PrintSpooler/src/com/android/printspooler/ui/FusedPrintersProvider.java b/packages/PrintSpooler/src/com/android/printspooler/ui/FusedPrintersProvider.java index 793544082af9..5be0176cf4cc 100644 --- a/packages/PrintSpooler/src/com/android/printspooler/ui/FusedPrintersProvider.java +++ b/packages/PrintSpooler/src/com/android/printspooler/ui/FusedPrintersProvider.java @@ -547,7 +547,7 @@ public final class FusedPrintersProvider extends Loader<List<PrinterInfo>> private PersistenceManager(final Activity activity, final int internalLoaderId) { mStatePersistFile = new AtomicFile(new File(activity.getFilesDir(), - PERSIST_FILE_NAME)); + PERSIST_FILE_NAME), "printer-history"); // Initialize enabled services to make sure they are set are the read task might be done // before the loader updated the services the first time. diff --git a/packages/SettingsLib/Android.mk b/packages/SettingsLib/Android.mk index 69287e858532..4338d26d502b 100644 --- a/packages/SettingsLib/Android.mk +++ b/packages/SettingsLib/Android.mk @@ -14,10 +14,10 @@ LOCAL_SHARED_ANDROID_LIBRARIES := \ android-support-v7-preference \ android-support-v7-appcompat \ android-support-v14-preference \ - apptoolkit-lifecycle-runtime + android-arch-lifecycle-runtime LOCAL_SHARED_JAVA_LIBRARIES := \ - apptoolkit-lifecycle-common + android-arch-lifecycle-common LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res diff --git a/packages/SettingsLib/common.mk b/packages/SettingsLib/common.mk index 14f06254009e..3c2ca2d31888 100644 --- a/packages/SettingsLib/common.mk +++ b/packages/SettingsLib/common.mk @@ -64,7 +64,7 @@ LOCAL_AAPT_FLAGS += --auto-add-overlay --extra-packages com.android.settingslib LOCAL_STATIC_JAVA_LIBRARIES += \ android-support-annotations \ android-support-v4 \ - apptoolkit-lifecycle-runtime \ - apptoolkit-lifecycle-common \ + android-arch-lifecycle-runtime \ + android-arch-lifecycle-common \ SettingsLib endif diff --git a/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtils.java b/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtils.java index fce5dd9cb828..7728f667ea51 100644 --- a/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtils.java +++ b/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtils.java @@ -363,6 +363,26 @@ public class RestrictedLockUtils { } /** + * Check if {@param packageName} is restricted by the profile or device owner from using + * metered data. + * + * @return EnforcedAdmin object containing the enforced admin component and admin user details, + * or {@code null} if the {@param packageName} is not restricted. + */ + public static EnforcedAdmin checkIfMeteredDataRestricted(Context context, + String packageName, int userId) { + final EnforcedAdmin enforcedAdmin = getProfileOrDeviceOwner(context, userId); + if (enforcedAdmin == null) { + return null; + } + + final DevicePolicyManager dpm = (DevicePolicyManager) context.getSystemService( + Context.DEVICE_POLICY_SERVICE); + return dpm.isMeteredDataDisabledForUser(enforcedAdmin.component, packageName, userId) + ? enforcedAdmin : null; + } + + /** * Checks if {@link android.app.admin.DevicePolicyManager#setAutoTimeRequired} is enforced * on the device. * diff --git a/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java b/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java index 5c73d5485e2a..3a0ae9f532bb 100644 --- a/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java +++ b/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java @@ -48,6 +48,7 @@ import android.os.RemoteException; import android.os.SystemClock; import android.os.UserHandle; import android.os.UserManager; +import android.support.annotation.VisibleForTesting; import android.text.format.Formatter; import android.util.IconDrawableFactory; import android.util.Log; @@ -1282,7 +1283,8 @@ public class ApplicationsState { // A location where extra info can be placed to be used by custom filters. public Object extraInfo; - AppEntry(Context context, ApplicationInfo info, long id) { + @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE) + public AppEntry(Context context, ApplicationInfo info, long id) { apkFile = new File(info.sourceDir); this.id = id; this.info = info; diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java index 754b88117613..e11017ce3449 100644 --- a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java +++ b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java @@ -32,7 +32,6 @@ import android.net.NetworkKey; import android.net.NetworkScoreManager; import android.net.NetworkScorerAppData; import android.net.ScoredNetwork; -import android.net.WifiKey; import android.net.wifi.IWifiManager; import android.net.wifi.ScanResult; import android.net.wifi.WifiConfiguration; @@ -43,6 +42,7 @@ import android.net.wifi.WifiManager; import android.net.wifi.WifiNetworkScoreCache; import android.net.wifi.hotspot2.PasspointConfiguration; import android.os.Bundle; +import android.os.Parcelable; import android.os.RemoteException; import android.os.ServiceManager; import android.os.SystemClock; @@ -52,6 +52,7 @@ import android.text.Spannable; import android.text.SpannableString; import android.text.TextUtils; import android.text.style.TtsSpan; +import android.util.ArraySet; import android.util.Log; import com.android.internal.annotations.VisibleForTesting; @@ -60,11 +61,11 @@ import com.android.settingslib.R; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; +import java.util.Collection; import java.util.HashMap; import java.util.Iterator; import java.util.Map; -import java.util.Objects; -import java.util.concurrent.ConcurrentHashMap; +import java.util.Set; import java.util.concurrent.atomic.AtomicInteger; @@ -91,6 +92,9 @@ public class AccessPoint implements Comparable<AccessPoint> { */ public static final int HIGHER_FREQ_5GHZ = 5900; + /** The key which identifies this AccessPoint grouping. */ + private String mKey; + @IntDef({Speed.NONE, Speed.SLOW, Speed.MODERATE, Speed.FAST, Speed.VERY_FAST}) @Retention(RetentionPolicy.SOURCE) public @interface Speed { @@ -116,14 +120,8 @@ public class AccessPoint implements Comparable<AccessPoint> { int VERY_FAST = 30; } - /** - * Experimental: we should be able to show the user the list of BSSIDs and bands - * for that SSID. - * For now this data is used only with Verbose Logging so as to show the band and number - * of BSSIDs on which that network is seen. - */ - private final ConcurrentHashMap<String, ScanResult> mScanResultCache = - new ConcurrentHashMap<String, ScanResult>(32); + /** The underlying set of scan results comprising this AccessPoint. */ + private final ArraySet<ScanResult> mScanResults = new ArraySet<>(); /** * Map of BSSIDs to scored networks for individual bssids. @@ -133,17 +131,13 @@ public class AccessPoint implements Comparable<AccessPoint> { */ private final Map<String, TimestampedScoredNetwork> mScoredNetworkCache = new HashMap<>(); - /** Maximum age of scan results to hold onto while actively scanning. **/ - private static final long MAX_SCAN_RESULT_AGE_MILLIS = 25000; - static final String KEY_NETWORKINFO = "key_networkinfo"; static final String KEY_WIFIINFO = "key_wifiinfo"; - static final String KEY_SCANRESULT = "key_scanresult"; static final String KEY_SSID = "key_ssid"; static final String KEY_SECURITY = "key_security"; static final String KEY_SPEED = "key_speed"; static final String KEY_PSKTYPE = "key_psktype"; - static final String KEY_SCANRESULTCACHE = "key_scanresultcache"; + static final String KEY_SCANRESULTS = "key_scanresults"; static final String KEY_SCOREDNETWORKCACHE = "key_scorednetworkcache"; static final String KEY_CONFIG = "key_config"; static final String KEY_FQDN = "key_fqdn"; @@ -216,7 +210,10 @@ public class AccessPoint implements Comparable<AccessPoint> { public AccessPoint(Context context, Bundle savedState) { mContext = context; - mConfig = savedState.getParcelable(KEY_CONFIG); + + if (savedState.containsKey(KEY_CONFIG)) { + mConfig = savedState.getParcelable(KEY_CONFIG); + } if (mConfig != null) { loadConfig(mConfig); } @@ -236,12 +233,11 @@ public class AccessPoint implements Comparable<AccessPoint> { if (savedState.containsKey(KEY_NETWORKINFO)) { mNetworkInfo = savedState.getParcelable(KEY_NETWORKINFO); } - if (savedState.containsKey(KEY_SCANRESULTCACHE)) { - ArrayList<ScanResult> scanResultArrayList = - savedState.getParcelableArrayList(KEY_SCANRESULTCACHE); - mScanResultCache.clear(); - for (ScanResult result : scanResultArrayList) { - mScanResultCache.put(result.BSSID, result); + if (savedState.containsKey(KEY_SCANRESULTS)) { + Parcelable[] scanResults = savedState.getParcelableArray(KEY_SCANRESULTS); + mScanResults.clear(); + for (Parcelable result : scanResults) { + mScanResults.add((ScanResult) result); } } if (savedState.containsKey(KEY_SCOREDNETWORKCACHE)) { @@ -268,8 +264,10 @@ public class AccessPoint implements Comparable<AccessPoint> { } update(mConfig, mInfo, mNetworkInfo); - // Do not evict old scan results on initial creation + // Calculate required fields + updateKey(); updateRssi(); + mId = sLastId.incrementAndGet(); } @@ -295,30 +293,75 @@ public class AccessPoint implements Comparable<AccessPoint> { copyFrom(other); } - AccessPoint(Context context, ScanResult result) { + AccessPoint(Context context, Collection<ScanResult> results) { mContext = context; - initWithScanResult(result); + + if (results.isEmpty()) { + throw new IllegalArgumentException("Cannot construct with an empty ScanResult list"); + } + mScanResults.addAll(results); + + // Information derived from scan results + ScanResult firstResult = results.iterator().next(); + ssid = firstResult.SSID; + bssid = firstResult.BSSID; + security = getSecurity(firstResult); + if (security == SECURITY_PSK) { + pskType = getPskType(firstResult); + } + updateKey(); + updateRssi(); + + // Passpoint Info + mIsCarrierAp = firstResult.isCarrierAp; + mCarrierApEapType = firstResult.carrierApEapType; + mCarrierName = firstResult.carrierName; + mId = sLastId.incrementAndGet(); } + @VisibleForTesting void loadConfig(WifiConfiguration config) { + ssid = (config.SSID == null ? "" : removeDoubleQuotes(config.SSID)); + bssid = config.BSSID; + security = getSecurity(config); + updateKey(); + networkId = config.networkId; + mConfig = config; + } + + /** Updates {@link #mKey} and should only called upon object creation/initialization. */ + private void updateKey() { + // TODO(sghuman): Consolidate Key logic on ScanResultMatchInfo + + StringBuilder builder = new StringBuilder(); + + if (TextUtils.isEmpty(getSsidStr())) { + builder.append(getBssid()); + } else { + builder.append(getSsidStr()); + } + + builder.append(',').append(getSecurity()); + mKey = builder.toString(); + } + /** * Copy accesspoint information. NOTE: We do not copy tag information because that is never * set on the internal copy. - * @param that */ void copyFrom(AccessPoint that) { - that.evictOldScanResults(); 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.mScanResultCache.clear(); - this.mScanResultCache.putAll(that.mScanResultCache); + this.mScanResults.clear(); + this.mScanResults.addAll(that.mScanResults); this.mScoredNetworkCache.clear(); this.mScoredNetworkCache.putAll(that.mScoredNetworkCache); this.mId = that.mId; @@ -426,7 +469,7 @@ public class AccessPoint implements Comparable<AccessPoint> { if (WifiTracker.sVerboseLogging) { builder.append(",rssi=").append(mRssi); - builder.append(",scan cache size=").append(mScanResultCache.size()); + builder.append(",scan cache size=").append(mScanResults.size()); } return builder.append(')').toString(); @@ -468,7 +511,7 @@ public class AccessPoint implements Comparable<AccessPoint> { */ private boolean updateScores(WifiNetworkScoreCache scoreCache, long maxScoreCacheAgeMillis) { long nowMillis = SystemClock.elapsedRealtime(); - for (ScanResult result : mScanResultCache.values()) { + for (ScanResult result : mScanResults) { ScoredNetwork score = scoreCache.getScoredNetwork(result); if (score == null) { continue; @@ -555,7 +598,7 @@ public class AccessPoint implements Comparable<AccessPoint> { mIsScoredNetworkMetered |= score.meteredHint; } } else { - for (ScanResult result : mScanResultCache.values()) { + for (ScanResult result : mScanResults) { ScoredNetwork score = scoreCache.getScoredNetwork(result); if (score == null) { continue; @@ -566,19 +609,21 @@ public class AccessPoint implements Comparable<AccessPoint> { return oldMetering == mIsScoredNetworkMetered; } - private void evictOldScanResults() { - long nowMs = SystemClock.elapsedRealtime(); - for (Iterator<ScanResult> iter = mScanResultCache.values().iterator(); iter.hasNext(); ) { - ScanResult result = iter.next(); - // result timestamp is in microseconds - if (nowMs - result.timestamp / 1000 > MAX_SCAN_RESULT_AGE_MILLIS) { - iter.remove(); - } + public static String getKey(ScanResult result) { + StringBuilder builder = new StringBuilder(); + + if (TextUtils.isEmpty(result.SSID)) { + builder.append(result.BSSID); + } else { + builder.append(result.SSID); } + + builder.append(',').append(getSecurity(result)); + return builder.toString(); } - public boolean matches(ScanResult result) { - return ssid.equals(result.SSID) && security == getSecurity(result); + public String getKey() { + return mKey; } public boolean matches(WifiConfiguration config) { @@ -622,9 +667,12 @@ public class AccessPoint implements Comparable<AccessPoint> { return mRssi; } - public ConcurrentHashMap<String, ScanResult> getScanResults() { - return mScanResultCache; - } + /** + * Returns the underlying scan result set. + * + * <p>Callers should not modify this set. + */ + public Set<ScanResult> getScanResults() { return mScanResults; } public Map<String, TimestampedScoredNetwork> getScoredNetworkCache() { return mScoredNetworkCache; @@ -645,7 +693,7 @@ public class AccessPoint implements Comparable<AccessPoint> { } int rssi = UNREACHABLE_RSSI; - for (ScanResult result : mScanResultCache.values()) { + for (ScanResult result : mScanResults) { if (result.level > rssi) { rssi = result.level; } @@ -853,7 +901,6 @@ public class AccessPoint implements Comparable<AccessPoint> { } if (WifiTracker.sVerboseLogging) { - evictOldScanResults(); summary.append(WifiUtils.buildLoggingSummary(this, config)); } @@ -950,28 +997,6 @@ public class AccessPoint implements Comparable<AccessPoint> { mConfig.allowedKeyManagement.set(KeyMgmt.NONE); } - void loadConfig(WifiConfiguration config) { - ssid = (config.SSID == null ? "" : removeDoubleQuotes(config.SSID)); - bssid = config.BSSID; - security = getSecurity(config); - networkId = config.networkId; - mConfig = config; - } - - private void initWithScanResult(ScanResult result) { - ssid = result.SSID; - bssid = result.BSSID; - security = getSecurity(result); - if (security == SECURITY_PSK) - pskType = getPskType(result); - - mScanResultCache.put(result.BSSID, result); - updateRssi(); - mIsCarrierAp = result.isCarrierAp; - mCarrierApEapType = result.carrierApEapType; - mCarrierName = result.carrierName; - } - public void saveWifiState(Bundle savedState) { if (ssid != null) savedState.putString(KEY_SSID, getSsidStr()); savedState.putInt(KEY_SECURITY, security); @@ -979,9 +1004,8 @@ public class AccessPoint implements Comparable<AccessPoint> { savedState.putInt(KEY_PSKTYPE, pskType); if (mConfig != null) savedState.putParcelable(KEY_CONFIG, mConfig); savedState.putParcelable(KEY_WIFIINFO, mInfo); - evictOldScanResults(); - savedState.putParcelableArrayList(KEY_SCANRESULTCACHE, - new ArrayList<ScanResult>(mScanResultCache.values())); + savedState.putParcelableArray(KEY_SCANRESULTS, + mScanResults.toArray(new Parcelable[mScanResults.size()])); savedState.putParcelableArrayList(KEY_SCOREDNETWORKCACHE, new ArrayList<>(mScoredNetworkCache.values())); if (mNetworkInfo != null) { @@ -1003,49 +1027,58 @@ public class AccessPoint implements Comparable<AccessPoint> { } /** - * Update the AP with the given scan result. + * Sets {@link #mScanResults} to the given collection. * - * @param result the ScanResult to add to the AccessPoint scan cache - * @param evictOldScanResults whether stale scan results should be removed - * from the cache during this update process - * @return true if the scan result update caused a change in state which would impact ranking - * or AccessPoint rendering (e.g. wifi level, security) + * @param scanResults a collection of scan results to add to the internal set + * @throws IllegalArgumentException if any of the given ScanResults did not belong to this AP */ - boolean update(ScanResult result, boolean evictOldScanResults) { - if (matches(result)) { - int oldLevel = getLevel(); - - /* Add or update the scan result for the BSSID */ - mScanResultCache.put(result.BSSID, result); - if (evictOldScanResults) evictOldScanResults(); - updateRssi(); - int newLevel = getLevel(); - - if (newLevel > 0 && newLevel != oldLevel) { - // Only update labels on visible rssi changes - updateSpeed(); - if (mAccessPointListener != null) { - mAccessPointListener.onLevelChanged(this); - } + void setScanResults(Collection<ScanResult> scanResults) { + + // Validate scan results are for current AP only + String key = getKey(); + for (ScanResult result : scanResults) { + String scanResultKey = AccessPoint.getKey(result); + if (!mKey.equals(scanResultKey)) { + throw new IllegalArgumentException( + String.format("ScanResult %s\nkey of %s did not match current AP key %s", + result, scanResultKey, key)); } + } + + + int oldLevel = getLevel(); + mScanResults.clear(); + mScanResults.addAll(scanResults); + updateRssi(); + int newLevel = getLevel(); + + // If newLevel is 0, there will be no displayed Preference since the AP is unreachable + if (newLevel > 0 && newLevel != oldLevel) { + // Only update labels on visible rssi changes + updateSpeed(); + if (mAccessPointListener != null) { + mAccessPointListener.onLevelChanged(this); + } + } + + if (mAccessPointListener != null) { + mAccessPointListener.onAccessPointChanged(this); + } + + if (!scanResults.isEmpty()) { + ScanResult result = scanResults.iterator().next(); + // This flag only comes from scans, is not easily saved in config if (security == SECURITY_PSK) { pskType = getPskType(result); } - if (mAccessPointListener != null) { - mAccessPointListener.onAccessPointChanged(this); - } - // The carrier info in the ScanResult is set by the platform based on the SSID and will // always be the same for all matching scan results. mIsCarrierAp = result.isCarrierAp; mCarrierApEapType = result.carrierApEapType; mCarrierName = result.carrierName; - - return true; } - return false; } /** Attempt to update the AccessPoint and return true if an update occurred. */ diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPointPreference.java b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPointPreference.java index dd55188e390f..109eb97ed2d8 100644 --- a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPointPreference.java +++ b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPointPreference.java @@ -77,19 +77,6 @@ public class AccessPointPreference extends TwoTargetPreference { private int mDefaultIconResId; private int mWifiSpeed = Speed.NONE; - public static String generatePreferenceKey(AccessPoint accessPoint) { - StringBuilder builder = new StringBuilder(); - - if (TextUtils.isEmpty(accessPoint.getSsidStr())) { - builder.append(accessPoint.getBssid()); - } else { - builder.append(accessPoint.getSsidStr()); - } - - builder.append(',').append(accessPoint.getSecurity()); - return builder.toString(); - } - @Nullable private static StateListDrawable getFrictionStateListDrawable(Context context) { TypedArray frictionSld; diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/TestAccessPointBuilder.java b/packages/SettingsLib/src/com/android/settingslib/wifi/TestAccessPointBuilder.java index 3dec1d382026..2993a0de0658 100644 --- a/packages/SettingsLib/src/com/android/settingslib/wifi/TestAccessPointBuilder.java +++ b/packages/SettingsLib/src/com/android/settingslib/wifi/TestAccessPointBuilder.java @@ -23,6 +23,7 @@ import android.net.wifi.ScanResult; import android.net.wifi.WifiConfiguration; import android.net.wifi.WifiInfo; import android.os.Bundle; +import android.os.Parcelable; import android.support.annotation.Keep; import com.android.settingslib.wifi.AccessPoint.Speed; @@ -58,7 +59,7 @@ public class TestAccessPointBuilder { private String mCarrierName = null; Context mContext; - private ArrayList<ScanResult> mScanResultCache; + private ArrayList<ScanResult> mScanResults; private ArrayList<TimestampedScoredNetwork> mScoredNetworkCache; @Keep @@ -84,8 +85,9 @@ public class TestAccessPointBuilder { if (mProviderFriendlyName != null) { bundle.putString(AccessPoint.KEY_PROVIDER_FRIENDLY_NAME, mProviderFriendlyName); } - if (mScanResultCache != null) { - bundle.putParcelableArrayList(AccessPoint.KEY_SCANRESULTCACHE, mScanResultCache); + if (mScanResults != null) { + bundle.putParcelableArray(AccessPoint.KEY_SCANRESULTS, + mScanResults.toArray(new Parcelable[mScanResults.size()])); } if (mScoredNetworkCache != null) { bundle.putParcelableArrayList(AccessPoint.KEY_SCOREDNETWORKCACHE, mScoredNetworkCache); @@ -229,8 +231,8 @@ public class TestAccessPointBuilder { return this; } - public TestAccessPointBuilder setScanResultCache(ArrayList<ScanResult> scanResultCache) { - mScanResultCache = scanResultCache; + public TestAccessPointBuilder setScanResults(ArrayList<ScanResult> scanResults) { + mScanResults = scanResults; return this; } diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java index c56e1da14921..1ac56a9de98f 100644 --- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java +++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java @@ -39,11 +39,13 @@ import android.os.HandlerThread; import android.os.Looper; import android.os.Message; import android.os.Process; +import android.os.SystemClock; import android.provider.Settings; import android.support.annotation.GuardedBy; import android.support.annotation.NonNull; import android.support.annotation.VisibleForTesting; import android.text.format.DateUtils; +import android.util.ArrayMap; import android.util.ArraySet; import android.util.Log; import android.util.SparseArray; @@ -78,6 +80,9 @@ public class WifiTracker implements LifecycleObserver, OnStart, OnStop, OnDestro */ private static final long DEFAULT_MAX_CACHED_SCORE_AGE_MILLIS = 20 * DateUtils.MINUTE_IN_MILLIS; + /** Maximum age of scan results to hold onto while actively scanning. **/ + private static final long MAX_SCAN_RESULT_AGE_MILLIS = 25000; + private static final String TAG = "WifiTracker"; private static final boolean DBG() { return Log.isLoggable(TAG, Log.DEBUG); @@ -142,6 +147,8 @@ public class WifiTracker implements LifecycleObserver, OnStart, OnStop, OnDestro = new AccessPointListenerAdapter(); private final HashMap<String, Integer> mSeenBssids = new HashMap<>(); + + // TODO(sghuman): Change this to be keyed on AccessPoint.getKey private final HashMap<String, ScanResult> mScanResultCache = new HashMap<>(); private Integer mScanId = 0; @@ -455,34 +462,42 @@ public class WifiTracker implements LifecycleObserver, OnStart, OnStop, OnDestro } private Collection<ScanResult> updateScanResultCache(final List<ScanResult> newResults) { - mScanId++; + // TODO(sghuman): Delete this and replace it with the Map of Ap Keys to ScanResults for (ScanResult newResult : newResults) { if (newResult.SSID == null || newResult.SSID.isEmpty()) { continue; } mScanResultCache.put(newResult.BSSID, newResult); - mSeenBssids.put(newResult.BSSID, mScanId); } - if (mScanId > NUM_SCANS_TO_CONFIRM_AP_LOSS) { - if (DBG()) Log.d(TAG, "------ Dumping SSIDs that were expired on this scan ------"); - Integer threshold = mScanId - NUM_SCANS_TO_CONFIRM_AP_LOSS; - for (Iterator<Map.Entry<String, Integer>> it = mSeenBssids.entrySet().iterator(); - it.hasNext(); /* nothing */) { - Map.Entry<String, Integer> e = it.next(); - if (e.getValue() < threshold) { - ScanResult result = mScanResultCache.get(e.getKey()); - if (DBG()) Log.d(TAG, "Removing " + e.getKey() + ":(" + result.SSID + ")"); - mScanResultCache.remove(e.getKey()); - it.remove(); - } - } - if (DBG()) Log.d(TAG, "---- Done Dumping SSIDs that were expired on this scan ----"); + // Don't evict old results if no new scan results + if (!mStaleScanResults) { + evictOldScans(); } + // TODO(sghuman): Update a Map<ApKey, List<ScanResults>> variable to be reused later after + // double threads have been removed. + return mScanResultCache.values(); } + /** + * Remove old scan results from the cache. + * + * <p>Should only ever be invoked from {@link #updateScanResultCache(List)} when + * {@link #mStaleScanResults} is false. + */ + private void evictOldScans() { + long nowMs = SystemClock.elapsedRealtime(); + for (Iterator<ScanResult> iter = mScanResultCache.values().iterator(); iter.hasNext(); ) { + ScanResult result = iter.next(); + // result timestamp is in microseconds + if (nowMs - result.timestamp / 1000 > MAX_SCAN_RESULT_AGE_MILLIS) { + iter.remove(); + } + } + } + private WifiConfiguration getWifiConfigurationForNetworkId( int networkId, final List<WifiConfiguration> configs) { if (configs != null) { @@ -541,10 +556,12 @@ public class WifiTracker implements LifecycleObserver, OnStart, OnStop, OnDestro /* Lookup table to more quickly update AccessPoints by only considering objects with the * correct SSID. Maps SSID -> List of AccessPoints with the given SSID. */ - Multimap<String, AccessPoint> apMap = new Multimap<String, AccessPoint>(); + Multimap<String, AccessPoint> existingApMap = new Multimap<String, AccessPoint>(); final Collection<ScanResult> results = updateScanResultCache(newScanResults); + // TODO(sghuman): This entire block only exists to populate the WifiConfiguration for + // APs, remove and refactor if (configs != null) { for (WifiConfiguration config : configs) { if (config.selfAdded && config.numAssociation == 0) { @@ -568,7 +585,7 @@ public class WifiTracker implements LifecycleObserver, OnStart, OnStop, OnDestro accessPoint.setUnreachable(); } accessPoints.add(accessPoint); - apMap.put(accessPoint.getSsidStr(), accessPoint); + existingApMap.put(accessPoint.getSsidStr(), accessPoint); } else { // If we aren't using saved networks, drop them into the cache so that // we have access to their saved info. @@ -579,6 +596,9 @@ public class WifiTracker implements LifecycleObserver, OnStart, OnStop, OnDestro final List<NetworkKey> scoresToRequest = new ArrayList<>(); if (results != null) { + // TODO(sghuman): Move this loop to updateScanResultCache and make instance variable + // after double handlers are removed. + ArrayMap<String, List<ScanResult>> scanResultsByApKey = new ArrayMap<>(); for (ScanResult result : results) { // Ignore hidden and ad-hoc networks. if (result.SSID == null || result.SSID.length() == 0 || @@ -591,27 +611,45 @@ public class WifiTracker implements LifecycleObserver, OnStart, OnStop, OnDestro scoresToRequest.add(key); } + String apKey = AccessPoint.getKey(result); + List<ScanResult> resultList; + if (scanResultsByApKey.containsKey(apKey)) { + resultList = scanResultsByApKey.get(apKey); + } else { + resultList = new ArrayList<>(); + scanResultsByApKey.put(apKey, resultList); + } + + resultList.add(result); + } + + for (Map.Entry<String, List<ScanResult>> entry : scanResultsByApKey.entrySet()) { + // List can not be empty as it is dynamically constructed on each iteration + ScanResult firstResult = entry.getValue().get(0); boolean found = false; - for (AccessPoint accessPoint : apMap.getAll(result.SSID)) { - // We want to evict old scan results if are current results are not stale - if (accessPoint.update(result, !mStaleScanResults)) { - found = true; - break; - } + for (AccessPoint accessPoint : existingApMap.getAll(firstResult.SSID)) { + accessPoint.setScanResults(entry.getValue()); + found = true; + break; } - if (!found && mIncludeScans) { - AccessPoint accessPoint = getCachedOrCreate(result, cachedAccessPoints); + + // Only create a new AP / add to the list if it wasn't already in the saved configs + if (!found) { + AccessPoint accessPoint = + getCachedOrCreate(entry.getValue(), cachedAccessPoints); if (mLastInfo != null && mLastNetworkInfo != null) { accessPoint.update(connectionConfig, mLastInfo, mLastNetworkInfo); } - if (result.isPasspointNetwork()) { + // TODO(sghuman): Move isPasspointNetwork logic into AccessPoint.java + if (firstResult.isPasspointNetwork()) { // Retrieve a WifiConfiguration for a Passpoint provider that matches // the given ScanResult. This is used for showing that a given AP // (ScanResult) is available via a Passpoint provider (provider friendly // name). try { - WifiConfiguration config = mWifiManager.getMatchingWifiConfig(result); + WifiConfiguration config = + mWifiManager.getMatchingWifiConfig(firstResult); if (config != null) { accessPoint.update(config); } @@ -621,7 +659,6 @@ public class WifiTracker implements LifecycleObserver, OnStart, OnStop, OnDestro } accessPoints.add(accessPoint); - apMap.put(accessPoint.getSsidStr(), accessPoint); } } } @@ -662,17 +699,18 @@ public class WifiTracker implements LifecycleObserver, OnStart, OnStop, OnDestro } @VisibleForTesting - AccessPoint getCachedOrCreate(ScanResult result, List<AccessPoint> cache) { + AccessPoint getCachedOrCreate( + List<ScanResult> scanResults, + List<AccessPoint> cache) { final int N = cache.size(); for (int i = 0; i < N; i++) { - if (cache.get(i).matches(result)) { + if (cache.get(i).getKey().equals(AccessPoint.getKey(scanResults.get(0)))) { AccessPoint ret = cache.remove(i); - // evict old scan results only if we have fresh results - ret.update(result, !mStaleScanResults); + ret.setScanResults(scanResults); return ret; } } - final AccessPoint accessPoint = new AccessPoint(mContext, result); + final AccessPoint accessPoint = new AccessPoint(mContext, scanResults); accessPoint.setListener(mAccessPointListenerAdapter); return accessPoint; } diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiUtils.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiUtils.java index 932c6fd82c50..fd48eea25e00 100644 --- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiUtils.java +++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiUtils.java @@ -109,7 +109,7 @@ public class WifiUtils { // TODO: sort list by RSSI or age long nowMs = SystemClock.elapsedRealtime(); - for (ScanResult result : accessPoint.getScanResults().values()) { + for (ScanResult result : accessPoint.getScanResults()) { if (result.frequency >= AccessPoint.LOWER_FREQ_5GHZ && result.frequency <= AccessPoint.HIGHER_FREQ_5GHZ) { // Strictly speaking: [4915, 5825] 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 ec594a69ef03..144031108662 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 @@ -40,6 +40,7 @@ import android.net.wifi.WifiSsid; import android.net.wifi.hotspot2.PasspointConfiguration; import android.net.wifi.hotspot2.pps.HomeSp; import android.os.Bundle; +import android.os.Parcelable; import android.os.SystemClock; import android.support.test.InstrumentationRegistry; import android.support.test.filters.SmallTest; @@ -277,7 +278,8 @@ public class AccessPointTest { scanResult.BSSID = "bssid"; scanResult.timestamp = SystemClock.elapsedRealtime() * 1000; scanResult.capabilities = ""; - assertThat(ap.update(scanResult, true /* evict old scan results */)).isTrue(); + + ap.setScanResults(Collections.singletonList(scanResult)); assertThat(ap.getRssi()).isEqualTo(expectedRssi); } @@ -477,7 +479,7 @@ public class AccessPointTest { result.carrierApEapType = WifiEnterpriseConfig.Eap.SIM; result.carrierName = carrierName; - AccessPoint ap = new AccessPoint(mContext, result); + AccessPoint ap = new AccessPoint(mContext, Collections.singletonList(result)); assertThat(ap.getSummary()).isEqualTo(String.format(mContext.getString( R.string.available_via_carrier), carrierName)); assertThat(ap.isCarrierAp()).isEqualTo(true); @@ -513,7 +515,7 @@ public class AccessPointTest { } @Test - public void testUpdateScanResultWithCarrierInfo() { + public void testSetScanResultWithCarrierInfo() { String ssid = "ssid"; AccessPoint ap = new TestAccessPointBuilder(mContext).setSsid(ssid).build(); assertThat(ap.isCarrierAp()).isEqualTo(false); @@ -529,8 +531,9 @@ public class AccessPointTest { scanResult.isCarrierAp = true; scanResult.carrierApEapType = carrierApEapType; scanResult.carrierName = carrierName; - assertThat(ap.update(scanResult, true /* evictOldScanresults */)).isTrue(); + + ap.setScanResults(Collections.singletonList(scanResult)); assertThat(ap.isCarrierAp()).isEqualTo(true); assertThat(ap.getCarrierApEapType()).isEqualTo(carrierApEapType); assertThat(ap.getCarrierName()).isEqualTo(carrierName); @@ -552,7 +555,9 @@ public class AccessPointTest { private AccessPoint createAccessPointWithScanResultCache() { Bundle bundle = new Bundle(); - bundle.putParcelableArrayList(AccessPoint.KEY_SCANRESULTCACHE, SCAN_RESULTS); + bundle.putParcelableArray( + AccessPoint.KEY_SCANRESULTS, + SCAN_RESULTS.toArray(new Parcelable[SCAN_RESULTS.size()])); return new AccessPoint(mContext, bundle); } @@ -903,7 +908,7 @@ public class AccessPointTest { .setActive(true) .setNetworkId(networkId) .setSsid(TEST_SSID) - .setScanResultCache(scanResults) + .setScanResults(scanResults) .setWifiInfo(info) .build(); @@ -990,7 +995,7 @@ public class AccessPointTest { .setActive(true) .setScoredNetworkCache( new ArrayList(Arrays.asList(recentScore))) - .setScanResultCache(SCAN_RESULTS) + .setScanResults(SCAN_RESULTS) .build(); when(mockWifiNetworkScoreCache.getScoredNetwork(any(ScanResult.class))) @@ -1018,7 +1023,7 @@ public class AccessPointTest { .setActive(true) .setScoredNetworkCache( new ArrayList(Arrays.asList(recentScore))) - .setScanResultCache(SCAN_RESULTS) + .setScanResults(SCAN_RESULTS) .build(); int newSpeed = Speed.MODERATE; 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 6615b8c02297..b36dda9deecf 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 @@ -29,6 +29,7 @@ import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; +import static org.mockito.Mockito.reset; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; @@ -57,9 +58,9 @@ import android.os.HandlerThread; import android.os.SystemClock; import android.provider.Settings; import android.support.test.InstrumentationRegistry; +import android.support.test.filters.FlakyTest; import android.support.test.filters.SmallTest; import android.support.test.runner.AndroidJUnit4; -import android.support.test.filters.FlakyTest; import org.junit.After; import org.junit.Before; @@ -76,7 +77,9 @@ import org.mockito.stubbing.Answer; import java.util.ArrayList; import java.util.Arrays; import java.util.BitSet; +import java.util.Collections; import java.util.List; +import java.util.Map; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; @@ -399,7 +402,8 @@ public class WifiTrackerTest { WifiTracker tracker = new WifiTracker( InstrumentationRegistry.getTargetContext(), null, true, true); - AccessPoint result = tracker.getCachedOrCreate(scanResult, new ArrayList<AccessPoint>()); + AccessPoint result = tracker.getCachedOrCreate( + Collections.singletonList(scanResult), new ArrayList<AccessPoint>()); assertTrue(result.mAccessPointListener != null); } diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/AccessPointPreferenceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/AccessPointPreferenceTest.java index a4c821f235b2..3fee16bd12ac 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/AccessPointPreferenceTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/AccessPointPreferenceTest.java @@ -61,34 +61,6 @@ public class AccessPointPreferenceTest { } @Test - public void generatePreferenceKey_returnsSsidPlusSecurity() { - String ssid = "ssid"; - String bssid = "00:00:00:00:00:00"; - int security = AccessPoint.SECURITY_WEP; - String expectedKey = ssid + ',' + security; - - TestAccessPointBuilder builder = new TestAccessPointBuilder(mContext); - builder.setBssid(bssid).setSsid(ssid).setSecurity(security); - - assertThat(AccessPointPreference.generatePreferenceKey(builder.build())) - .isEqualTo(expectedKey); - } - - @Test - public void generatePreferenceKey_emptySsidReturnsBssidPlusSecurity() { - String ssid = ""; - String bssid = "00:00:00:00:00:00"; - int security = AccessPoint.SECURITY_WEP; - String expectedKey = bssid + ',' + security; - - TestAccessPointBuilder builder = new TestAccessPointBuilder(mContext); - builder.setBssid(bssid).setSsid(ssid).setSecurity(security); - - assertThat(AccessPointPreference.generatePreferenceKey(builder.build())) - .isEqualTo(expectedKey); - } - - @Test public void refresh_openNetwork_updateContentDescription() { final String ssid = "ssid"; final String summary = "connected"; diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/WifiUtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/WifiUtilsTest.java index c5795d34eae8..9310b73afdcc 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/WifiUtilsTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/WifiUtilsTest.java @@ -29,6 +29,7 @@ import android.net.WifiKey; import android.net.wifi.ScanResult; import android.net.wifi.WifiNetworkScoreCache; import android.os.Bundle; +import android.os.Parcelable; import android.os.SystemClock; import android.text.format.DateUtils; @@ -72,7 +73,8 @@ public class WifiUtilsTest { Bundle bundle = new Bundle(); ArrayList<ScanResult> scanResults = buildScanResultCache(); - bundle.putParcelableArrayList(AccessPoint.KEY_SCANRESULTCACHE, scanResults); + bundle.putParcelableArray(AccessPoint.KEY_SCANRESULTS, + scanResults.toArray(new Parcelable[scanResults.size()])); AccessPoint ap = new AccessPoint(mContext, bundle); when(mockWifiNetworkScoreCache.getScoredNetwork(any(ScanResult.class))) diff --git a/packages/SettingsProvider/AndroidManifest.xml b/packages/SettingsProvider/AndroidManifest.xml index fd4d2967bf61..9bc2d7546410 100644 --- a/packages/SettingsProvider/AndroidManifest.xml +++ b/packages/SettingsProvider/AndroidManifest.xml @@ -12,8 +12,6 @@ android:icon="@mipmap/ic_launcher_settings" android:defaultToDeviceProtectedStorage="true" android:directBootAware="true"> - <uses-library android:name="android.test.runner" /> - <provider android:name="SettingsProvider" android:authorities="settings" diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java index a8a67abaf8ae..f158a65663f5 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java @@ -166,6 +166,9 @@ final class SettingsState { @GuardedBy("mLock") private final File mStatePersistFile; + @GuardedBy("mLock") + private final String mStatePersistTag; + private final Setting mNullSetting = new Setting(null, null, false, null, null) { @Override public boolean isNull() { @@ -250,6 +253,7 @@ final class SettingsState { mContext = context; mLock = lock; mStatePersistFile = file; + mStatePersistTag = "settings-" + getTypeFromKey(key) + "-" + getUserIdFromKey(key); mKey = key; mHandler = new MyHandler(looper); if (maxBytesPerAppPackage == MAX_BYTES_PER_APP_PACKAGE_LIMITED) { @@ -634,7 +638,7 @@ final class SettingsState { Slog.i(LOG_TAG, "[PERSIST START]"); } - AtomicFile destination = new AtomicFile(mStatePersistFile); + AtomicFile destination = new AtomicFile(mStatePersistFile, mStatePersistTag); FileOutputStream out = null; try { out = destination.startWrite(); diff --git a/packages/SystemUI/Android.mk b/packages/SystemUI/Android.mk index 251ae2da1215..2bcf4ef9f856 100644 --- a/packages/SystemUI/Android.mk +++ b/packages/SystemUI/Android.mk @@ -48,8 +48,8 @@ LOCAL_STATIC_ANDROID_LIBRARIES := \ android-slices-core \ android-slices-view \ android-slices-builders \ - apptoolkit-arch-core-runtime \ - apptoolkit-lifecycle-extensions \ + android-arch-core-runtime \ + android-arch-lifecycle-extensions \ LOCAL_STATIC_JAVA_LIBRARIES := \ SystemUI-tags \ diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml index 1fc36bef215d..9613a6a8c059 100644 --- a/packages/SystemUI/AndroidManifest.xml +++ b/packages/SystemUI/AndroidManifest.xml @@ -90,7 +90,6 @@ <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" /> <uses-permission android:name="android.permission.GET_TOP_ACTIVITY_INFO" /> <uses-permission android:name="android.permission.MANAGE_ACTIVITY_STACKS" /> - <uses-permission android:name="android.permission.START_ACTIVITY_AS_CALLER" /> <uses-permission android:name="android.permission.START_TASKS_FROM_RECENTS" /> <uses-permission android:name="android.permission.GET_INTENT_SENDER_INTENT" /> @@ -560,22 +559,6 @@ </intent-filter> </activity> - <activity android:name=".chooser.ChooserActivity" - android:theme="@*android:style/Theme.NoDisplay" - android:finishOnCloseSystemDialogs="true" - android:excludeFromRecents="true" - android:documentLaunchMode="never" - android:relinquishTaskIdentity="true" - android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation|keyboard|keyboardHidden" - android:process=":ui" - android:visibleToInstantApps="true"> - <intent-filter> - <action android:name="android.intent.action.CHOOSER_UI" /> - <category android:name="android.intent.category.DEFAULT" /> - <category android:name="android.intent.category.VOICE" /> - </intent-filter> - </activity> - <!-- Doze with notifications, run in main sysui process for every user --> <service android:name=".doze.DozeService" diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/VolumeDialogController.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/VolumeDialogController.java index 32eb5d55568c..4b3afdc3eb7a 100644 --- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/VolumeDialogController.java +++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/VolumeDialogController.java @@ -99,6 +99,9 @@ public interface VolumeDialogController { public ComponentName effectsSuppressor; public String effectsSuppressorName; public int activeStream = NO_ACTIVE_STREAM; + public boolean disallowAlarms; + public boolean disallowMedia; + public boolean disallowRinger; public State copy() { final State rt = new State(); @@ -113,6 +116,9 @@ public interface VolumeDialogController { } rt.effectsSuppressorName = effectsSuppressorName; rt.activeStream = activeStream; + rt.disallowAlarms = disallowAlarms; + rt.disallowMedia = disallowMedia; + rt.disallowRinger = disallowRinger; return rt; } @@ -142,6 +148,9 @@ public interface VolumeDialogController { sep(sb, indent); sb.append("effectsSuppressor:").append(effectsSuppressor); sep(sb, indent); sb.append("effectsSuppressorName:").append(effectsSuppressorName); sep(sb, indent); sb.append("activeStream:").append(activeStream); + sep(sb, indent); sb.append("disallowAlarms:").append(disallowAlarms); + sep(sb, indent); sb.append("disallowMedia:").append(disallowMedia); + sep(sb, indent); sb.append("disallowRinger:").append(disallowRinger); if (indent > 0) sep(sb, indent); return sb.append('}').toString(); } diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTileView.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTileView.java index 18d27bb419d4..53f7e44bc25a 100644 --- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTileView.java +++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTileView.java @@ -26,14 +26,26 @@ import com.android.systemui.plugins.qs.QSTile.State; @DependsOn(target = QSIconView.class) @DependsOn(target = QSTile.class) public abstract class QSTileView extends LinearLayout { - public static final int VERSION = 1; + public static final int VERSION = 2; public QSTileView(Context context) { super(context); } public abstract View updateAccessibilityOrder(View previousView); + + /** + * Returns a {@link QSIconView} containing only the icon for this tile. Use + * {@link #getIconWithBackground()} to retrieve the entire tile (background & peripherals + * included). + */ public abstract QSIconView getIcon(); + + /** + * Returns a {@link View} containing the icon for this tile along with the accompanying + * background circle/peripherals. To retrieve only the inner icon, use {@link #getIcon()}. + */ + public abstract View getIconWithBackground(); public abstract void init(QSTile tile); public abstract void onStateChanged(State state); diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_status_view.xml b/packages/SystemUI/res-keyguard/layout/keyguard_status_view.xml index eda8c69e65f2..faa2c17b63bf 100644 --- a/packages/SystemUI/res-keyguard/layout/keyguard_status_view.xml +++ b/packages/SystemUI/res-keyguard/layout/keyguard_status_view.xml @@ -31,6 +31,7 @@ <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" + android:layout_marginBottom="@dimen/widget_vertical_padding" android:orientation="vertical"> <RelativeLayout android:id="@+id/keyguard_clock_container" @@ -63,7 +64,6 @@ <include layout="@layout/keyguard_status_area" android:id="@+id/keyguard_status_area" android:layout_marginTop="20dp" - android:layout_marginBottom="@dimen/widget_vertical_padding" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_below="@id/clock_separator" /> diff --git a/packages/SystemUI/res-keyguard/values/dimens.xml b/packages/SystemUI/res-keyguard/values/dimens.xml index 5aca7f9332e6..28a0935bf2f5 100644 --- a/packages/SystemUI/res-keyguard/values/dimens.xml +++ b/packages/SystemUI/res-keyguard/values/dimens.xml @@ -75,7 +75,7 @@ <dimen name="password_char_padding">8dp</dimen> <!-- The vertical margin between the date and the owner info. --> - <dimen name="date_owner_info_margin">6dp</dimen> + <dimen name="date_owner_info_margin">2dp</dimen> <!-- The translation for disappearing security views after having solved them. --> <dimen name="disappear_y_translation">-32dp</dimen> diff --git a/packages/SystemUI/res-keyguard/values/strings.xml b/packages/SystemUI/res-keyguard/values/strings.xml index 3a41681f53e9..8a48e7bd9354 100644 --- a/packages/SystemUI/res-keyguard/values/strings.xml +++ b/packages/SystemUI/res-keyguard/values/strings.xml @@ -124,6 +124,10 @@ <string name="keyboardview_keycode_delete">Delete</string> <!-- Description of the button used to disable current carrier when the device supported embedded SIM. [CHAR LIMIT=30] --> <string name="disable_carrier_button_text">Disable eSIM</string> + <!-- Title of Error message when disabling current carrier failed for the device supported embedded SIM. [CHAR LIMIT=80] --> + <string name="error_disable_esim_title">Can\u2019t disable eSIM</string> + <!-- Description of Error message when disabling current carrier failed for the device supported embedded SIM. [CHAR LIMIT=80] --> + <string name="error_disable_esim_msg">The eSIM can\u2019t be disabled due to an error.</string> <!-- Description of the Enter button in a KeyboardView. [CHAR LIMIT=NONE] --> <string name="keyboardview_keycode_enter">Enter</string> @@ -146,8 +150,8 @@ <string name="kg_sim_pin_instructions">Enter SIM PIN.</string> <!-- Instructions for using the SIM PIN unlock screen when there's more than one SIM --> <string name="kg_sim_pin_instructions_multi">Enter SIM PIN for \"<xliff:g id="carrier" example="CARD 1">%1$s</xliff:g>\".</string> - <!-- Instructions for disabling eSIM carrier to unlock the phone with embedded SIM --> - <string name="kg_sim_lock_instructions_esim">Disable eSIM to use device without mobile service.</string> + <!-- Instructions for disabling eSIM carrier to unlock the phone with embedded SIM. This message follows the original SIM PIN/PUK message of device without embedded SIM. --> + <string name="kg_sim_lock_esim_instructions"><xliff:g id="previous_msg" example="Enter SIM PIN.">%1$s</xliff:g> Disable eSIM to use device without mobile service.</string> <!-- Instructions for using the PIN unlock screen --> <string name="kg_pin_instructions">Enter PIN</string> <!-- Instructions for using the password unlock screen --> diff --git a/packages/SystemUI/res/layout/back.xml b/packages/SystemUI/res/layout/back.xml index 43bec91ad053..6843db9313d4 100644 --- a/packages/SystemUI/res/layout/back.xml +++ b/packages/SystemUI/res/layout/back.xml @@ -24,8 +24,8 @@ systemui:keyCode="4" android:scaleType="fitCenter" android:contentDescription="@string/accessibility_back" - android:paddingTop="15dp" - android:paddingBottom="15dp" + android:paddingTop="@dimen/home_padding" + android:paddingBottom="@dimen/home_padding" android:paddingStart="@dimen/navigation_key_padding" android:paddingEnd="@dimen/navigation_key_padding" /> diff --git a/packages/SystemUI/res/layout/recent_apps.xml b/packages/SystemUI/res/layout/recent_apps.xml index c84d28051286..6b08cea7705f 100644 --- a/packages/SystemUI/res/layout/recent_apps.xml +++ b/packages/SystemUI/res/layout/recent_apps.xml @@ -23,8 +23,8 @@ android:layout_weight="0" android:scaleType="fitCenter" android:contentDescription="@string/accessibility_recent" - android:paddingTop="15dp" - android:paddingBottom="15dp" + android:paddingTop="@dimen/home_padding" + android:paddingBottom="@dimen/home_padding" android:paddingStart="@dimen/navigation_key_padding" android:paddingEnd="@dimen/navigation_key_padding" /> diff --git a/packages/SystemUI/res/layout/rounded_corners.xml b/packages/SystemUI/res/layout/rounded_corners.xml index d1ef5d638e78..734c877d7829 100644 --- a/packages/SystemUI/res/layout/rounded_corners.xml +++ b/packages/SystemUI/res/layout/rounded_corners.xml @@ -22,7 +22,7 @@ android:id="@+id/left" android:layout_width="12dp" android:layout_height="12dp" - android:layout_gravity="left" + android:layout_gravity="left|top" android:tint="#ff000000" android:src="@drawable/rounded" /> <ImageView @@ -30,6 +30,6 @@ android:layout_width="12dp" android:layout_height="12dp" android:tint="#ff000000" - android:layout_gravity="right" + android:layout_gravity="right|bottom" android:src="@drawable/rounded" /> </FrameLayout> diff --git a/packages/SystemUI/res/layout/volume_dialog_row.xml b/packages/SystemUI/res/layout/volume_dialog_row.xml index 1d1f95b44659..3e80085225e2 100644 --- a/packages/SystemUI/res/layout/volume_dialog_row.xml +++ b/packages/SystemUI/res/layout/volume_dialog_row.xml @@ -61,6 +61,7 @@ android:layout_width="24dp" android:layout_height="24dp" android:background="?android:selectableItemBackgroundBorderless" + android:contentDescription="@string/accessibility_output_chooser" style="@style/VolumeButtons" android:layout_centerVertical="true" android:src="@drawable/ic_swap" diff --git a/packages/SystemUI/res/values-sw600dp/dimens.xml b/packages/SystemUI/res/values-sw600dp/dimens.xml index 268fdec9a9b4..e95d9fee880b 100644 --- a/packages/SystemUI/res/values-sw600dp/dimens.xml +++ b/packages/SystemUI/res/values-sw600dp/dimens.xml @@ -97,4 +97,7 @@ <!-- The offsets the tasks animate from when recents is launched while docking --> <dimen name="recents_task_stack_animation_launched_while_docking_offset">192dp</dimen> + + <!-- Home button padding for sizing --> + <dimen name="home_padding">0dp</dimen> </resources> diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml index 676847088f31..1cc1cc883268 100644 --- a/packages/SystemUI/res/values/config.xml +++ b/packages/SystemUI/res/values/config.xml @@ -348,8 +348,7 @@ <item>com.android.systemui.util.leak.GarbageMonitor$Service</item> <item>com.android.systemui.LatencyTester</item> <item>com.android.systemui.globalactions.GlobalActionsComponent</item> - <item>com.android.systemui.RoundedCorners</item> - <item>com.android.systemui.EmulatedDisplayCutout</item> + <item>com.android.systemui.ScreenDecorations</item> <item>com.android.systemui.fingerprint.FingerprintDialogImpl</item> </string-array> diff --git a/packages/SystemUI/res/values/ids.xml b/packages/SystemUI/res/values/ids.xml index edda613f2fbc..f9aa8216ec11 100644 --- a/packages/SystemUI/res/values/ids.xml +++ b/packages/SystemUI/res/values/ids.xml @@ -96,5 +96,7 @@ <!-- For StatusBarIconContainer to tag its icon views --> <item type="id" name="status_bar_view_state_tag" /> + + <item type="id" name="display_cutout" /> </resources> diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index 9b6af43a1920..72615ce690e5 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -1272,6 +1272,9 @@ <!-- Content description for accessibility (not shown on the screen): volume dialog collapse button. [CHAR LIMIT=NONE] --> <string name="accessibility_volume_collapse">Collapse</string> + <!-- content description for audio output chooser [CHAR LIMIT=NONE]--> + <string name="accessibility_output_chooser">Switch output device</string> + <!-- Screen pinning dialog title. --> <string name="screen_pinning_title">Screen is pinned</string> <!-- Screen pinning dialog description. --> diff --git a/packages/SystemUI/shared/Android.mk b/packages/SystemUI/shared/Android.mk index 5f75f7d71584..21b0ed85a420 100644 --- a/packages/SystemUI/shared/Android.mk +++ b/packages/SystemUI/shared/Android.mk @@ -30,7 +30,7 @@ include $(BUILD_STATIC_JAVA_LIBRARY) include $(CLEAR_VARS) -LOCAL_PACKAGE_NAME := SystemUISharedLib +LOCAL_PACKAGE_NAME := SysUISharedLib LOCAL_SRC_FILES := $(call all-java-files-under, src) LOCAL_JAVA_LIBRARIES := SystemUISharedLib @@ -39,4 +39,4 @@ LOCAL_PROGUARD_ENABLED := disabled include $(BUILD_PACKAGE) -include $(call all-makefiles-under,$(LOCAL_PATH))
\ No newline at end of file +include $(call all-makefiles-under,$(LOCAL_PATH)) 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 da50776708b3..98ede4eafbe2 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 @@ -31,6 +31,11 @@ interface ISystemUiProxy { int maxLayer, boolean useIdentityTransform, int rotation); /** + * Begins screen pinning on the provided {@param taskId}. + */ + void startScreenPinning(int taskId); + + /** * Called when the overview service has started the recents animation. */ void onRecentsAnimationStarted(); diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardEsimArea.java b/packages/SystemUI/src/com/android/keyguard/KeyguardEsimArea.java index cb5afec79073..b8a07cdbdc41 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardEsimArea.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardEsimArea.java @@ -16,14 +16,18 @@ package com.android.keyguard; +import android.app.AlertDialog; import android.app.PendingIntent; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.os.Handler; +import android.os.HandlerThread; import android.os.UserHandle; import android.util.AttributeSet; import android.view.View; +import android.view.WindowManager; import android.widget.Button; import android.telephony.SubscriptionManager; import android.telephony.SubscriptionInfo; @@ -50,8 +54,17 @@ class KeyguardEsimArea extends Button implements View.OnClickListener { if (ACTION_DISABLE_ESIM.equals(intent.getAction())) { int resultCode = getResultCode(); if (resultCode != EuiccManager.EMBEDDED_SUBSCRIPTION_RESULT_OK) { - // TODO (b/62680294): Surface more info. to the end users for this failure. Log.e(TAG, "Error disabling esim, result code = " + resultCode); + AlertDialog.Builder builder = + new AlertDialog.Builder(mContext) + .setMessage(R.string.error_disable_esim_msg) + .setTitle(R.string.error_disable_esim_title) + .setCancelable(false /* cancelable */) + .setNeutralButton(R.string.ok, null /* listener */); + AlertDialog alertDialog = builder.create(); + alertDialog.getWindow().setType( + WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG); + alertDialog.show(); } } } @@ -101,14 +114,13 @@ class KeyguardEsimArea extends Button implements View.OnClickListener { @Override public void onClick(View v) { - Intent intent = new Intent(mContext, KeyguardEsimArea.class); - intent.setAction(ACTION_DISABLE_ESIM); + Intent intent = new Intent(ACTION_DISABLE_ESIM); intent.setPackage(mContext.getPackageName()); - PendingIntent callbackIntent = PendingIntent.getBroadcast( + PendingIntent callbackIntent = PendingIntent.getBroadcastAsUser( mContext, 0 /* requestCode */, intent, - PendingIntent.FLAG_UPDATE_CURRENT); + PendingIntent.FLAG_UPDATE_CURRENT, UserHandle.SYSTEM); mEuiccManager .switchToSubscription(SubscriptionManager.INVALID_SUBSCRIPTION_ID, callbackIntent); } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinView.java index e7432ba5855d..703b20531390 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinView.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinView.java @@ -131,7 +131,7 @@ public class KeyguardSimPinView extends KeyguardPinBasedInputView { } if (isEsimLocked) { - msg = msg + " " + rez.getString(R.string.kg_sim_lock_instructions_esim); + msg = rez.getString(R.string.kg_sim_lock_esim_instructions, msg); } mSecurityMessageDisplay.setMessage(msg); @@ -187,6 +187,10 @@ public class KeyguardSimPinView extends KeyguardPinBasedInputView { msgId = isDefault ? R.string.kg_sim_pin_instructions : R.string.kg_password_pin_failed; displayMessage = getContext().getString(msgId); } + if (KeyguardEsimArea.isEsimLocked(mContext, mSubId)) { + displayMessage = getResources() + .getString(R.string.kg_sim_lock_esim_instructions, displayMessage); + } if (DEBUG) Log.d(LOG_TAG, "getPinPasswordErrorMessage:" + " attemptsRemaining=" + attemptsRemaining + " displayMessage=" + displayMessage); return displayMessage; diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukView.java index afee8ece26c4..347c9792ec95 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukView.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukView.java @@ -181,7 +181,7 @@ public class KeyguardSimPukView extends KeyguardPinBasedInputView { } } if (isEsimLocked) { - msg = msg + " " + rez.getString(R.string.kg_sim_lock_instructions_esim); + msg = rez.getString(R.string.kg_sim_lock_esim_instructions, msg); } mSecurityMessageDisplay.setMessage(msg); mSimImageView.setImageTintList(ColorStateList.valueOf(color)); @@ -231,6 +231,10 @@ public class KeyguardSimPukView extends KeyguardPinBasedInputView { R.string.kg_password_puk_failed; displayMessage = getContext().getString(msgId); } + if (KeyguardEsimArea.isEsimLocked(mContext, mSubId)) { + displayMessage = getResources() + .getString(R.string.kg_sim_lock_esim_instructions, displayMessage); + } if (DEBUG) Log.d(LOG_TAG, "getPukPasswordErrorMessage:" + " attemptsRemaining=" + attemptsRemaining + " displayMessage=" + displayMessage); return displayMessage; diff --git a/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java b/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java index 8666b0c873e7..1ae06d751255 100644 --- a/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java +++ b/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java @@ -119,18 +119,10 @@ public class BatteryMeterView extends LinearLayout implements addView(mBatteryIconView, mlp); updateShowPercent(); - - Context dualToneDarkTheme = new ContextThemeWrapper(context, - Utils.getThemeAttr(context, R.attr.darkIconTheme)); - Context dualToneLightTheme = new ContextThemeWrapper(context, - Utils.getThemeAttr(context, R.attr.lightIconTheme)); - mDarkModeBackgroundColor = Utils.getColorAttr(dualToneDarkTheme, R.attr.backgroundColor); - mDarkModeFillColor = Utils.getColorAttr(dualToneDarkTheme, R.attr.fillColor); - mLightModeBackgroundColor = Utils.getColorAttr(dualToneLightTheme, R.attr.backgroundColor); - mLightModeFillColor = Utils.getColorAttr(dualToneLightTheme, R.attr.fillColor); - + setColorsFromContext(context); // Init to not dark at all. onDarkChanged(new Rect(), 0, DarkIconDispatcher.DEFAULT_ICON_TINT); + mUserTracker = new CurrentUserTracker(mContext) { @Override public void onUserSwitched(int newUserId) { @@ -148,6 +140,21 @@ public class BatteryMeterView extends LinearLayout implements updateShowPercent(); } + public void setColorsFromContext(Context context) { + if (context == null) { + return; + } + + Context dualToneDarkTheme = new ContextThemeWrapper(context, + Utils.getThemeAttr(context, R.attr.darkIconTheme)); + Context dualToneLightTheme = new ContextThemeWrapper(context, + Utils.getThemeAttr(context, R.attr.lightIconTheme)); + mDarkModeBackgroundColor = Utils.getColorAttr(dualToneDarkTheme, R.attr.backgroundColor); + mDarkModeFillColor = Utils.getColorAttr(dualToneDarkTheme, R.attr.fillColor); + mLightModeBackgroundColor = Utils.getColorAttr(dualToneLightTheme, R.attr.backgroundColor); + mLightModeFillColor = Utils.getColorAttr(dualToneLightTheme, R.attr.fillColor); + } + @Override public boolean hasOverlappingRendering() { return false; diff --git a/packages/SystemUI/src/com/android/systemui/EmulatedDisplayCutout.java b/packages/SystemUI/src/com/android/systemui/EmulatedDisplayCutout.java deleted file mode 100644 index 5d2e4d09ff43..000000000000 --- a/packages/SystemUI/src/com/android/systemui/EmulatedDisplayCutout.java +++ /dev/null @@ -1,129 +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 com.android.systemui; - -import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; - -import android.content.Context; -import android.graphics.Canvas; -import android.graphics.Color; -import android.graphics.Paint; -import android.graphics.Path; -import android.graphics.PixelFormat; -import android.view.DisplayCutout; -import android.view.Gravity; -import android.view.View; -import android.view.ViewGroup; -import android.view.ViewGroup.LayoutParams; -import android.view.WindowInsets; -import android.view.WindowManager; - -import com.android.systemui.statusbar.policy.ConfigurationController; -import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener; - -/** - * Emulates a display cutout by drawing its shape in an overlay as supplied by - * {@link DisplayCutout}. - */ -public class EmulatedDisplayCutout extends SystemUI implements ConfigurationListener { - private View mOverlay; - private boolean mAttached; - private WindowManager mWindowManager; - - @Override - public void start() { - Dependency.get(ConfigurationController.class).addCallback(this); - - mWindowManager = mContext.getSystemService(WindowManager.class); - updateAttached(); - } - - @Override - public void onOverlayChanged() { - updateAttached(); - } - - private void updateAttached() { - boolean shouldAttach = mContext.getResources().getBoolean( - com.android.internal.R.bool.config_fillMainBuiltInDisplayCutout); - setAttached(shouldAttach); - } - - private void setAttached(boolean attached) { - if (attached && !mAttached) { - if (mOverlay == null) { - mOverlay = new CutoutView(mContext); - mOverlay.setLayoutParams(getLayoutParams()); - } - mWindowManager.addView(mOverlay, mOverlay.getLayoutParams()); - mAttached = true; - } else if (!attached && mAttached) { - mWindowManager.removeView(mOverlay); - mAttached = false; - } - } - - private WindowManager.LayoutParams getLayoutParams() { - final WindowManager.LayoutParams lp = new WindowManager.LayoutParams( - ViewGroup.LayoutParams.MATCH_PARENT, - LayoutParams.MATCH_PARENT, - WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL, - WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE - | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE - | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL - | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH - | WindowManager.LayoutParams.FLAG_SLIPPERY - | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN - | WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR, - PixelFormat.TRANSLUCENT); - lp.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS - | WindowManager.LayoutParams.PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY; - lp.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; - lp.setTitle("EmulatedDisplayCutout"); - lp.gravity = Gravity.TOP; - return lp; - } - - private static class CutoutView extends View { - private final Paint mPaint = new Paint(); - private final Path mBounds = new Path(); - - CutoutView(Context context) { - super(context); - } - - @Override - public WindowInsets onApplyWindowInsets(WindowInsets insets) { - mBounds.reset(); - if (insets.getDisplayCutout() != null) { - insets.getDisplayCutout().getBounds().getBoundaryPath(mBounds); - } - invalidate(); - return insets.consumeDisplayCutout(); - } - - @Override - protected void onDraw(Canvas canvas) { - if (!mBounds.isEmpty()) { - mPaint.setColor(Color.BLACK); - mPaint.setStyle(Paint.Style.FILL); - - canvas.drawPath(mBounds, mPaint); - } - } - } -} diff --git a/packages/SystemUI/src/com/android/systemui/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/OverviewProxyService.java index b6e49ae6cc2c..8d8b726f8fa8 100644 --- a/packages/SystemUI/src/com/android/systemui/OverviewProxyService.java +++ b/packages/SystemUI/src/com/android/systemui/OverviewProxyService.java @@ -37,6 +37,7 @@ import com.android.systemui.OverviewProxyService.OverviewProxyListener; import com.android.systemui.shared.recents.IOverviewProxy; import com.android.systemui.shared.recents.ISystemUiProxy; import com.android.systemui.shared.system.GraphicBufferCompat; +import com.android.systemui.statusbar.phone.StatusBar; import com.android.systemui.statusbar.policy.CallbackController; import com.android.systemui.statusbar.policy.DeviceProvisionedController; import com.android.systemui.statusbar.policy.DeviceProvisionedController.DeviceProvisionedListener; @@ -67,6 +68,7 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis private int mConnectionBackoffAttempts; private ISystemUiProxy mSysUiProxy = new ISystemUiProxy.Stub() { + public GraphicBufferCompat screenshot(Rect sourceCrop, int width, int height, int minLayer, int maxLayer, boolean useIdentityTransform, int rotation) { long token = Binder.clearCallingIdentity(); @@ -78,10 +80,27 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis } } + public void startScreenPinning(int taskId) { + long token = Binder.clearCallingIdentity(); + try { + mHandler.post(() -> { + StatusBar statusBar = ((SystemUIApplication) mContext).getComponent( + StatusBar.class); + if (statusBar != null) { + statusBar.showScreenPinningRequest(taskId, false /* allowCancel */); + } + }); + } finally { + Binder.restoreCallingIdentity(token); + } + } + public void onRecentsAnimationStarted() { long token = Binder.clearCallingIdentity(); try { - notifyRecentsAnimationStarted(); + mHandler.post(() -> { + notifyRecentsAnimationStarted(); + }); } finally { Binder.restoreCallingIdentity(token); } diff --git a/packages/SystemUI/src/com/android/systemui/RoundedCorners.java b/packages/SystemUI/src/com/android/systemui/RoundedCorners.java deleted file mode 100644 index c960fa122d50..000000000000 --- a/packages/SystemUI/src/com/android/systemui/RoundedCorners.java +++ /dev/null @@ -1,221 +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 com.android.systemui; - -import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; - -import static com.android.systemui.tuner.TunablePadding.FLAG_START; -import static com.android.systemui.tuner.TunablePadding.FLAG_END; - -import android.app.Fragment; -import android.content.res.ColorStateList; -import android.graphics.Color; -import android.graphics.PixelFormat; -import android.provider.Settings.Secure; -import android.support.annotation.VisibleForTesting; -import android.util.DisplayMetrics; -import android.view.Gravity; -import android.view.LayoutInflater; -import android.view.View; -import android.view.View.OnLayoutChangeListener; -import android.view.ViewGroup; -import android.view.ViewGroup.LayoutParams; -import android.view.WindowManager; -import android.widget.ImageView; - -import com.android.systemui.R.id; -import com.android.systemui.fragments.FragmentHostManager; -import com.android.systemui.fragments.FragmentHostManager.FragmentListener; -import com.android.systemui.plugins.qs.QS; -import com.android.systemui.qs.SecureSetting; -import com.android.systemui.statusbar.phone.CollapsedStatusBarFragment; -import com.android.systemui.statusbar.phone.NavigationBarFragment; -import com.android.systemui.statusbar.phone.StatusBar; -import com.android.systemui.tuner.TunablePadding; -import com.android.systemui.tuner.TunerService; -import com.android.systemui.tuner.TunerService.Tunable; - -public class RoundedCorners extends SystemUI implements Tunable { - public static final String SIZE = "sysui_rounded_size"; - public static final String PADDING = "sysui_rounded_content_padding"; - - private int mRoundedDefault; - private View mOverlay; - private View mBottomOverlay; - private float mDensity; - private TunablePadding mQsPadding; - private TunablePadding mStatusBarPadding; - private TunablePadding mNavBarPadding; - - @Override - public void start() { - mRoundedDefault = mContext.getResources().getDimensionPixelSize( - R.dimen.rounded_corner_radius); - if (mRoundedDefault != 0) { - setupRounding(); - } - int padding = mContext.getResources().getDimensionPixelSize( - R.dimen.rounded_corner_content_padding); - if (padding != 0) { - setupPadding(padding); - } - } - - private void setupRounding() { - mOverlay = LayoutInflater.from(mContext) - .inflate(R.layout.rounded_corners, null); - mOverlay.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE); - mOverlay.setAlpha(0); - mOverlay.findViewById(R.id.right).setRotation(90); - - mContext.getSystemService(WindowManager.class) - .addView(mOverlay, getWindowLayoutParams()); - mBottomOverlay = LayoutInflater.from(mContext) - .inflate(R.layout.rounded_corners, null); - mBottomOverlay.setAlpha(0); - mBottomOverlay.findViewById(R.id.right).setRotation(180); - mBottomOverlay.findViewById(R.id.left).setRotation(270); - WindowManager.LayoutParams layoutParams = getWindowLayoutParams(); - layoutParams.gravity = Gravity.BOTTOM; - mContext.getSystemService(WindowManager.class) - .addView(mBottomOverlay, layoutParams); - - DisplayMetrics metrics = new DisplayMetrics(); - mContext.getSystemService(WindowManager.class) - .getDefaultDisplay().getMetrics(metrics); - mDensity = metrics.density; - - Dependency.get(TunerService.class).addTunable(this, SIZE); - - // Watch color inversion and invert the overlay as needed. - SecureSetting setting = new SecureSetting(mContext, Dependency.get(Dependency.MAIN_HANDLER), - Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED) { - @Override - protected void handleValueChanged(int value, boolean observedChange) { - int tint = value != 0 ? Color.WHITE : Color.BLACK; - ColorStateList tintList = ColorStateList.valueOf(tint); - ((ImageView) mOverlay.findViewById(id.left)).setImageTintList(tintList); - ((ImageView) mOverlay.findViewById(id.right)).setImageTintList(tintList); - ((ImageView) mBottomOverlay.findViewById(id.left)).setImageTintList(tintList); - ((ImageView) mBottomOverlay.findViewById(id.right)).setImageTintList(tintList); - } - }; - setting.setListening(true); - setting.onChange(false); - - mOverlay.addOnLayoutChangeListener(new OnLayoutChangeListener() { - @Override - public void onLayoutChange(View v, int left, int top, int right, int bottom, - int oldLeft, - int oldTop, int oldRight, int oldBottom) { - mOverlay.removeOnLayoutChangeListener(this); - mOverlay.animate() - .alpha(1) - .setDuration(1000) - .start(); - mBottomOverlay.animate() - .alpha(1) - .setDuration(1000) - .start(); - } - }); - } - - private void setupPadding(int padding) { - // Add some padding to all the content near the edge of the screen. - StatusBar sb = getComponent(StatusBar.class); - View statusBar = (sb != null ? sb.getStatusBarWindow() : null); - if (statusBar != null) { - TunablePadding.addTunablePadding(statusBar.findViewById(R.id.keyguard_header), PADDING, - padding, FLAG_END); - - FragmentHostManager fragmentHostManager = FragmentHostManager.get(statusBar); - fragmentHostManager.addTagListener(CollapsedStatusBarFragment.TAG, - new TunablePaddingTagListener(padding, R.id.status_bar)); - fragmentHostManager.addTagListener(QS.TAG, - new TunablePaddingTagListener(padding, R.id.header)); - } - } - - private WindowManager.LayoutParams getWindowLayoutParams() { - final WindowManager.LayoutParams lp = new WindowManager.LayoutParams( - ViewGroup.LayoutParams.MATCH_PARENT, - LayoutParams.WRAP_CONTENT, - WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL, - WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE - | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE - | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL - | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH - | WindowManager.LayoutParams.FLAG_SLIPPERY - | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN, - PixelFormat.TRANSLUCENT); - lp.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS - | WindowManager.LayoutParams.PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY; - lp.setTitle("RoundedOverlay"); - lp.gravity = Gravity.TOP; - lp.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; - return lp; - } - - - @Override - public void onTuningChanged(String key, String newValue) { - if (mOverlay == null) return; - if (SIZE.equals(key)) { - int size = mRoundedDefault; - try { - size = (int) (Integer.parseInt(newValue) * mDensity); - } catch (Exception e) { - } - setSize(mOverlay.findViewById(R.id.left), size); - setSize(mOverlay.findViewById(R.id.right), size); - setSize(mBottomOverlay.findViewById(R.id.left), size); - setSize(mBottomOverlay.findViewById(R.id.right), size); - } - } - - private void setSize(View view, int pixelSize) { - LayoutParams params = view.getLayoutParams(); - params.width = pixelSize; - params.height = pixelSize; - view.setLayoutParams(params); - } - - @VisibleForTesting - static class TunablePaddingTagListener implements FragmentListener { - - private final int mPadding; - private final int mId; - private TunablePadding mTunablePadding; - - public TunablePaddingTagListener(int padding, int id) { - mPadding = padding; - mId = id; - } - - @Override - public void onFragmentViewCreated(String tag, Fragment fragment) { - if (mTunablePadding != null) { - mTunablePadding.destroy(); - } - View view = fragment.getView(); - if (mId != 0) { - view = view.findViewById(mId); - } - mTunablePadding = TunablePadding.addTunablePadding(view, PADDING, mPadding, - FLAG_START | FLAG_END); - } - } -} diff --git a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java new file mode 100644 index 000000000000..0b3e9e5072aa --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java @@ -0,0 +1,415 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the + * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package com.android.systemui; + +import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; +import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; +import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; + +import static com.android.systemui.tuner.TunablePadding.FLAG_START; +import static com.android.systemui.tuner.TunablePadding.FLAG_END; + +import android.app.Fragment; +import android.content.Context; +import android.content.res.ColorStateList; +import android.content.res.Configuration; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.graphics.Path; +import android.graphics.PixelFormat; +import android.graphics.Rect; +import android.hardware.display.DisplayManager; +import android.provider.Settings.Secure; +import android.support.annotation.VisibleForTesting; +import android.util.DisplayMetrics; +import android.view.DisplayCutout; +import android.view.DisplayInfo; +import android.view.Gravity; +import android.view.LayoutInflater; +import android.view.View; +import android.view.View.OnLayoutChangeListener; +import android.view.ViewGroup; +import android.view.ViewGroup.LayoutParams; +import android.view.WindowManager; +import android.widget.FrameLayout; +import android.widget.ImageView; + +import com.android.systemui.fragments.FragmentHostManager; +import com.android.systemui.fragments.FragmentHostManager.FragmentListener; +import com.android.systemui.plugins.qs.QS; +import com.android.systemui.qs.SecureSetting; +import com.android.systemui.statusbar.phone.CollapsedStatusBarFragment; +import com.android.systemui.statusbar.phone.StatusBar; +import com.android.systemui.tuner.TunablePadding; +import com.android.systemui.tuner.TunerService; +import com.android.systemui.tuner.TunerService.Tunable; + +/** + * An overlay that draws screen decorations in software (e.g for rounded corners or display cutout) + * for antialiasing and emulation purposes. + */ +public class ScreenDecorations extends SystemUI implements Tunable { + public static final String SIZE = "sysui_rounded_size"; + public static final String PADDING = "sysui_rounded_content_padding"; + + private int mRoundedDefault; + private View mOverlay; + private View mBottomOverlay; + private float mDensity; + private WindowManager mWindowManager; + private boolean mLandscape; + + @Override + public void start() { + mWindowManager = mContext.getSystemService(WindowManager.class); + mRoundedDefault = mContext.getResources().getDimensionPixelSize( + R.dimen.rounded_corner_radius); + if (mRoundedDefault != 0 || shouldDrawCutout()) { + setupDecorations(); + } + int padding = mContext.getResources().getDimensionPixelSize( + R.dimen.rounded_corner_content_padding); + if (padding != 0) { + setupPadding(padding); + } + } + + private void setupDecorations() { + mOverlay = LayoutInflater.from(mContext) + .inflate(R.layout.rounded_corners, null); + ((ViewGroup)mOverlay).addView(new DisplayCutoutView(mContext, true, + this::updateWindowVisibilities)); + mBottomOverlay = LayoutInflater.from(mContext) + .inflate(R.layout.rounded_corners, null); + ((ViewGroup)mBottomOverlay).addView(new DisplayCutoutView(mContext, false, + this::updateWindowVisibilities)); + + mOverlay.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE); + mOverlay.setAlpha(0); + + mBottomOverlay.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE); + mBottomOverlay.setAlpha(0); + + updateViews(); + + mWindowManager.addView(mOverlay, getWindowLayoutParams()); + mWindowManager.addView(mBottomOverlay, getBottomLayoutParams()); + + DisplayMetrics metrics = new DisplayMetrics(); + mWindowManager.getDefaultDisplay().getMetrics(metrics); + mDensity = metrics.density; + + Dependency.get(TunerService.class).addTunable(this, SIZE); + + // Watch color inversion and invert the overlay as needed. + SecureSetting setting = new SecureSetting(mContext, Dependency.get(Dependency.MAIN_HANDLER), + Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED) { + @Override + protected void handleValueChanged(int value, boolean observedChange) { + int tint = value != 0 ? Color.WHITE : Color.BLACK; + ColorStateList tintList = ColorStateList.valueOf(tint); + ((ImageView) mOverlay.findViewById(R.id.left)).setImageTintList(tintList); + ((ImageView) mOverlay.findViewById(R.id.right)).setImageTintList(tintList); + ((ImageView) mBottomOverlay.findViewById(R.id.left)).setImageTintList(tintList); + ((ImageView) mBottomOverlay.findViewById(R.id.right)).setImageTintList(tintList); + } + }; + setting.setListening(true); + setting.onChange(false); + + mOverlay.addOnLayoutChangeListener(new OnLayoutChangeListener() { + @Override + public void onLayoutChange(View v, int left, int top, int right, int bottom, + int oldLeft, + int oldTop, int oldRight, int oldBottom) { + mOverlay.removeOnLayoutChangeListener(this); + mOverlay.animate() + .alpha(1) + .setDuration(1000) + .start(); + mBottomOverlay.animate() + .alpha(1) + .setDuration(1000) + .start(); + } + }); + } + + @Override + protected void onConfigurationChanged(Configuration newConfig) { + boolean newLanscape = newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE; + if (newLanscape != mLandscape) { + mLandscape = newLanscape; + + if (mOverlay != null) { + updateLayoutParams(); + updateViews(); + } + } + if (shouldDrawCutout() && mOverlay == null) { + setupDecorations(); + } + } + + private void updateViews() { + View topLeft = mOverlay.findViewById(R.id.left); + View topRight = mOverlay.findViewById(R.id.right); + View bottomLeft = mBottomOverlay.findViewById(R.id.left); + View bottomRight = mBottomOverlay.findViewById(R.id.right); + if (mLandscape) { + // Flip corners + View tmp = topRight; + topRight = bottomLeft; + bottomLeft = tmp; + } + updateView(topLeft, Gravity.TOP | Gravity.LEFT, 0); + updateView(topRight, Gravity.TOP | Gravity.RIGHT, 90); + updateView(bottomLeft, Gravity.BOTTOM | Gravity.LEFT, 270); + updateView(bottomRight, Gravity.BOTTOM | Gravity.RIGHT, 180); + + updateWindowVisibilities(); + } + + private void updateView(View v, int gravity, int rotation) { + ((FrameLayout.LayoutParams)v.getLayoutParams()).gravity = gravity; + v.setRotation(rotation); + } + + private void updateWindowVisibilities() { + updateWindowVisibility(mOverlay); + updateWindowVisibility(mBottomOverlay); + } + + private void updateWindowVisibility(View overlay) { + boolean visibleForCutout = shouldDrawCutout() + && overlay.findViewById(R.id.display_cutout).getVisibility() == View.VISIBLE; + boolean visibleForRoundedCorners = mRoundedDefault > 0; + overlay.setVisibility(visibleForCutout || visibleForRoundedCorners + ? View.VISIBLE : View.GONE); + } + + private boolean shouldDrawCutout() { + return mContext.getResources().getBoolean( + com.android.internal.R.bool.config_fillMainBuiltInDisplayCutout); + } + + private void setupPadding(int padding) { + // Add some padding to all the content near the edge of the screen. + StatusBar sb = getComponent(StatusBar.class); + View statusBar = (sb != null ? sb.getStatusBarWindow() : null); + if (statusBar != null) { + TunablePadding.addTunablePadding(statusBar.findViewById(R.id.keyguard_header), PADDING, + padding, FLAG_END); + + FragmentHostManager fragmentHostManager = FragmentHostManager.get(statusBar); + fragmentHostManager.addTagListener(CollapsedStatusBarFragment.TAG, + new TunablePaddingTagListener(padding, R.id.status_bar)); + fragmentHostManager.addTagListener(QS.TAG, + new TunablePaddingTagListener(padding, R.id.header)); + } + } + + @VisibleForTesting + WindowManager.LayoutParams getWindowLayoutParams() { + final WindowManager.LayoutParams lp = new WindowManager.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + LayoutParams.WRAP_CONTENT, + WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL, + WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE + | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE + | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL + | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH + | WindowManager.LayoutParams.FLAG_SLIPPERY + | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN, + PixelFormat.TRANSLUCENT); + lp.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS + | WindowManager.LayoutParams.PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY; + lp.setTitle("ScreenDecorOverlay"); + lp.gravity = Gravity.TOP | Gravity.LEFT; + lp.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; + if (mLandscape) { + lp.width = WRAP_CONTENT; + lp.height = MATCH_PARENT; + } + return lp; + } + + private WindowManager.LayoutParams getBottomLayoutParams() { + WindowManager.LayoutParams lp = getWindowLayoutParams(); + lp.setTitle("ScreenDecorOverlayBottom"); + lp.gravity = Gravity.BOTTOM | Gravity.RIGHT; + return lp; + } + + private void updateLayoutParams() { + mWindowManager.updateViewLayout(mOverlay, getWindowLayoutParams()); + mWindowManager.updateViewLayout(mBottomOverlay, getBottomLayoutParams()); + } + + @Override + public void onTuningChanged(String key, String newValue) { + if (mOverlay == null) return; + if (SIZE.equals(key)) { + int size = mRoundedDefault; + try { + size = (int) (Integer.parseInt(newValue) * mDensity); + } catch (Exception e) { + } + setSize(mOverlay.findViewById(R.id.left), size); + setSize(mOverlay.findViewById(R.id.right), size); + setSize(mBottomOverlay.findViewById(R.id.left), size); + setSize(mBottomOverlay.findViewById(R.id.right), size); + } + } + + private void setSize(View view, int pixelSize) { + LayoutParams params = view.getLayoutParams(); + params.width = pixelSize; + params.height = pixelSize; + view.setLayoutParams(params); + } + + @VisibleForTesting + static class TunablePaddingTagListener implements FragmentListener { + + private final int mPadding; + private final int mId; + private TunablePadding mTunablePadding; + + public TunablePaddingTagListener(int padding, int id) { + mPadding = padding; + mId = id; + } + + @Override + public void onFragmentViewCreated(String tag, Fragment fragment) { + if (mTunablePadding != null) { + mTunablePadding.destroy(); + } + View view = fragment.getView(); + if (mId != 0) { + view = view.findViewById(mId); + } + mTunablePadding = TunablePadding.addTunablePadding(view, PADDING, mPadding, + FLAG_START | FLAG_END); + } + } + + public static class DisplayCutoutView extends View implements DisplayManager.DisplayListener { + + private final DisplayInfo mInfo = new DisplayInfo(); + private final Paint mPaint = new Paint(); + private final Rect mBoundingRect = new Rect(); + private final Path mBoundingPath = new Path(); + private final int[] mLocation = new int[2]; + private final boolean mStart; + private final Runnable mVisibilityChangedListener; + + public DisplayCutoutView(Context context, boolean start, + Runnable visibilityChangedListener) { + super(context); + mStart = start; + mVisibilityChangedListener = visibilityChangedListener; + setId(R.id.display_cutout); + } + + @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + mContext.getSystemService(DisplayManager.class).registerDisplayListener(this, + getHandler()); + update(); + } + + @Override + protected void onDetachedFromWindow() { + super.onDetachedFromWindow(); + mContext.getSystemService(DisplayManager.class).unregisterDisplayListener(this); + } + + @Override + protected void onDraw(Canvas canvas) { + super.onDraw(canvas); + getLocationOnScreen(mLocation); + canvas.translate(-mLocation[0], -mLocation[1]); + if (!mBoundingPath.isEmpty()) { + mPaint.setColor(Color.BLACK); + mPaint.setStyle(Paint.Style.FILL); + canvas.drawPath(mBoundingPath, mPaint); + } + } + + @Override + public void onDisplayAdded(int displayId) { + } + + @Override + public void onDisplayRemoved(int displayId) { + } + + @Override + public void onDisplayChanged(int displayId) { + if (displayId == getDisplay().getDisplayId()) { + update(); + } + } + + private void update() { + requestLayout(); + getDisplay().getDisplayInfo(mInfo); + mBoundingRect.setEmpty(); + mBoundingPath.reset(); + int newVisible; + if (hasCutout()) { + mBoundingRect.set(mInfo.displayCutout.getBoundingRect()); + mInfo.displayCutout.getBounds().getBoundaryPath(mBoundingPath); + newVisible = VISIBLE; + } else { + newVisible = GONE; + } + if (newVisible != getVisibility()) { + setVisibility(newVisible); + mVisibilityChangedListener.run(); + } + } + + private boolean hasCutout() { + if (mInfo.displayCutout == null) { + return false; + } + DisplayCutout displayCutout = mInfo.displayCutout.calculateRelativeTo( + new Rect(0, 0, mInfo.logicalWidth, mInfo.logicalHeight)); + if (mStart) { + return displayCutout.getSafeInsetLeft() > 0 + || displayCutout.getSafeInsetTop() > 0; + } else { + return displayCutout.getSafeInsetRight() > 0 + || displayCutout.getSafeInsetBottom() > 0; + } + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + if (mBoundingRect.isEmpty()) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + return; + } + setMeasuredDimension( + resolveSizeAndState(mBoundingRect.width(), widthMeasureSpec, 0), + resolveSizeAndState(mBoundingRect.height(), heightMeasureSpec, 0)); + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/chooser/ChooserActivity.java b/packages/SystemUI/src/com/android/systemui/chooser/ChooserActivity.java deleted file mode 100644 index 085ece75362d..000000000000 --- a/packages/SystemUI/src/com/android/systemui/chooser/ChooserActivity.java +++ /dev/null @@ -1,41 +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 com.android.systemui.chooser; - -import android.app.Activity; -import android.app.ActivityManager; -import android.content.Intent; -import android.os.Bundle; -import android.os.IBinder; -import android.util.Log; - -import com.android.systemui.R; - -import java.lang.Thread; -import java.util.ArrayList; - -public final class ChooserActivity extends Activity { - - private static final String TAG = "ChooserActivity"; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - ChooserHelper.onChoose(this); - finish(); - } -} diff --git a/packages/SystemUI/src/com/android/systemui/chooser/ChooserHelper.java b/packages/SystemUI/src/com/android/systemui/chooser/ChooserHelper.java deleted file mode 100644 index ac22568f7368..000000000000 --- a/packages/SystemUI/src/com/android/systemui/chooser/ChooserHelper.java +++ /dev/null @@ -1,45 +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 com.android.systemui.chooser; - -import android.app.Activity; -import android.app.ActivityManager; -import android.content.Intent; -import android.os.Bundle; -import android.os.IBinder; -import android.util.Log; - -import com.android.systemui.R; - -public class ChooserHelper { - - private static final String TAG = "ChooserHelper"; - - static void onChoose(Activity activity) { - final Intent thisIntent = activity.getIntent(); - final Bundle thisExtras = thisIntent.getExtras(); - final Intent chosenIntent = thisIntent.getParcelableExtra(Intent.EXTRA_INTENT); - final Bundle options = thisIntent.getParcelableExtra(ActivityManager.EXTRA_OPTIONS); - final IBinder permissionToken = - thisExtras.getBinder(ActivityManager.EXTRA_PERMISSION_TOKEN); - final boolean ignoreTargetSecurity = - thisIntent.getBooleanExtra(ActivityManager.EXTRA_IGNORE_TARGET_SECURITY, false); - final int userId = thisIntent.getIntExtra(Intent.EXTRA_USER_ID, -1); - activity.startActivityAsCaller( - chosenIntent, options, permissionToken, ignoreTargetSecurity, userId); - } -} diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java index 0f34513bc40f..c28b7eed1614 100644 --- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java +++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java @@ -147,6 +147,7 @@ class GlobalActionsDialog implements DialogInterface.OnDismissListener, private boolean mHasTelephony; private boolean mHasVibrator; private boolean mHasLogoutButton; + private boolean mHasLockdownButton; private final boolean mShowSilentToggle; private final EmergencyAffordanceManager mEmergencyAffordanceManager; private final ScreenshotHelper mScreenshotHelper; @@ -311,6 +312,7 @@ class GlobalActionsDialog implements DialogInterface.OnDismissListener, ArraySet<String> addedKeys = new ArraySet<String>(); mHasLogoutButton = false; + mHasLockdownButton = false; for (int i = 0; i < defaultActions.length; i++) { String actionKey = defaultActions[i]; if (addedKeys.contains(actionKey)) { @@ -341,6 +343,7 @@ class GlobalActionsDialog implements DialogInterface.OnDismissListener, Settings.Secure.LOCKDOWN_IN_POWER_MENU, 0) != 0 && shouldDisplayLockdown()) { mItems.add(getLockdownAction()); + mHasLockdownButton = true; } } else if (GLOBAL_ACTION_KEY_VOICEASSIST.equals(actionKey)) { mItems.add(getVoiceAssistAction()); @@ -587,9 +590,9 @@ class GlobalActionsDialog implements DialogInterface.OnDismissListener, // switching user mHandler.postDelayed(() -> { try { + int currentUserId = getCurrentUser().id; ActivityManager.getService().switchUser(UserHandle.USER_SYSTEM); - ActivityManager.getService().stopUser(getCurrentUser().id, true /*force*/, - null); + ActivityManager.getService().stopUser(currentUserId, true /*force*/, null); } catch (RemoteException re) { Log.e(TAG, "Couldn't logout user " + re); } @@ -871,10 +874,9 @@ class GlobalActionsDialog implements DialogInterface.OnDismissListener, public View getView(int position, View convertView, ViewGroup parent) { Action action = getItem(position); View view = action.create(mContext, convertView, parent, LayoutInflater.from(mContext)); - // When there is no logout button, only power off and restart should be in white - // background, thus setting division view at third item; with logout button being the - // third item, set the division view at fourth item instead. - if (position == (mHasLogoutButton ? 3 : 2)) { + // Power off, restart, logout (if present) and lockdown (if present) should be in white + // background. Set the division based on which buttons are currently being displayed. + if (position == 2 + (mHasLogoutButton ? 1 : 0) + (mHasLockdownButton ? 1 : 0)) { HardwareUiLayout.get(parent).setDivisionView(view); } return view; diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java b/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java index 95185c087f38..001b40980065 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java @@ -44,7 +44,11 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha public static final float EXPANDED_TILE_DELAY = .86f; private final ArrayList<View> mAllViews = new ArrayList<>(); - private final ArrayList<View> mTopFiveQs = new ArrayList<>(); + /** + * List of {@link View}s representing Quick Settings that are being animated from the quick QS + * position to the normal QS panel. + */ + private final ArrayList<View> mQuickQsViews = new ArrayList<>(); private final QuickQSPanel mQuickQsPanel; private final QSPanel mQsPanel; private final QS mQs; @@ -157,7 +161,7 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha clearAnimationState(); mAllViews.clear(); - mTopFiveQs.clear(); + mQuickQsViews.clear(); QSTileLayout tileLayout = mQsPanel.getTileLayout(); mAllViews.add((View) tileLayout); @@ -198,7 +202,7 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha translationXBuilder.addFloat(tileView, "translationX", -xDiff, 0); translationYBuilder.addFloat(tileView, "translationY", -yDiff, 0); - mTopFiveQs.add(tileView.getIcon()); + mQuickQsViews.add(tileView.getIconWithBackground()); mAllViews.add(tileView.getIcon()); mAllViews.add(quickTileView); } else if (mFullRows && isIconInAnimatedRow(count)) { @@ -322,9 +326,9 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha @Override public void onAnimationAtEnd() { mQuickQsPanel.setVisibility(View.INVISIBLE); - final int N = mTopFiveQs.size(); + final int N = mQuickQsViews.size(); for (int i = 0; i < N; i++) { - mTopFiveQs.get(i).setVisibility(View.VISIBLE); + mQuickQsViews.get(i).setVisibility(View.VISIBLE); } } @@ -332,9 +336,9 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha public void onAnimationStarted() { mQuickQsPanel.setVisibility(mOnKeyguard ? View.INVISIBLE : View.VISIBLE); if (mOnFirstPage) { - final int N = mTopFiveQs.size(); + final int N = mQuickQsViews.size(); for (int i = 0; i < N; i++) { - mTopFiveQs.get(i).setVisibility(View.INVISIBLE); + mQuickQsViews.get(i).setVisibility(View.INVISIBLE); } } } @@ -348,9 +352,9 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha v.setTranslationX(0); v.setTranslationY(0); } - final int N2 = mTopFiveQs.size(); + final int N2 = mQuickQsViews.size(); for (int i = 0; i < N2; i++) { - mTopFiveQs.get(i).setVisibility(View.VISIBLE); + mQuickQsViews.get(i).setVisibility(View.VISIBLE); } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java index 77768b1a4bd1..4d7333b99eee 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java @@ -97,7 +97,6 @@ public class QuickStatusBarHeader extends RelativeLayout mIconManager.setTint(fillColor); BatteryMeterView battery = findViewById(R.id.battery); - battery.setFillColor(Color.WHITE); battery.setForceShowPercent(true); mActivityStarter = Dependency.get(ActivityStarter.class); @@ -216,6 +215,11 @@ public class QuickStatusBarHeader extends RelativeLayout //host.setHeaderView(mExpandIndicator); mHeaderQsPanel.setQSPanelAndHeader(mQsPanel, this); mHeaderQsPanel.setHost(host, null /* No customization in header */); + + // Use SystemUI context to get battery meter colors, and let it use the default tint (white) + BatteryMeterView battery = findViewById(R.id.battery); + battery.setColorsFromContext(mHost.getContext()); + battery.onDarkChanged(new Rect(), 0, DarkIconDispatcher.DEFAULT_ICON_TINT); } public void setCallback(Callback qsPanelCallback) { diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileBaseView.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileBaseView.java index b4cfda60fba3..c9c678c2ad25 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileBaseView.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileBaseView.java @@ -236,6 +236,10 @@ public class QSTileBaseView extends com.android.systemui.plugins.qs.QSTileView { return mIcon; } + public View getIconWithBackground() { + return mIconFrame; + } + @Override public boolean performClick() { mClicked = true; diff --git a/packages/SystemUI/src/com/android/systemui/recents/SwipeUpOnboarding.java b/packages/SystemUI/src/com/android/systemui/recents/SwipeUpOnboarding.java index 0494e1b09f57..b2472bf73874 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/SwipeUpOnboarding.java +++ b/packages/SystemUI/src/com/android/systemui/recents/SwipeUpOnboarding.java @@ -73,7 +73,6 @@ public class SwipeUpOnboarding { private final RippleDrawable mLightRipple; private boolean mTaskListenerRegistered; - private ComponentName mLauncherComponent; private boolean mLayoutAttachedToWindow; private boolean mBackgroundIsLight; @@ -92,15 +91,7 @@ public class SwipeUpOnboarding { Prefs.putInt(mContext, Prefs.Key.NUM_APPS_LAUNCHED, numAppsLaunched); } } else { - String runningPackage = info.topActivity.getPackageName(); - // TODO: use callback from the overview proxy service to handle this case - if (runningPackage.equals(mLauncherComponent.getPackageName()) - && activityType == ACTIVITY_TYPE_RECENTS) { - Prefs.putBoolean(mContext, Prefs.Key.HAS_SWIPED_UP_FOR_RECENTS, true); - onDisconnectedFromLauncher(); - } else { - hide(false); - } + hide(false); } } }; @@ -127,8 +118,8 @@ public class SwipeUpOnboarding { final Resources res = context.getResources(); mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE); mLayout = LayoutInflater.from(mContext).inflate(R.layout.recents_swipe_up_onboarding, null); - mTextView = (TextView) mLayout.findViewById(R.id.onboarding_text); - mDismissView = (ImageView) mLayout.findViewById(R.id.dismiss); + mTextView = mLayout.findViewById(R.id.onboarding_text); + mDismissView = mLayout.findViewById(R.id.dismiss); mDarkBackgroundColor = res.getColor(android.R.color.background_dark); mLightBackgroundColor = res.getColor(android.R.color.background_light); mDarkContentColor = res.getColor(R.color.primary_text_default_material_light); @@ -149,13 +140,7 @@ public class SwipeUpOnboarding { } } - public void onConnectedToLauncher(ComponentName launcherComponent) { - // TODO: re-enable this once we have the proper callback for when a swipe up was performed. - final boolean disableOnboarding = true; - if (disableOnboarding) { - return; - } - mLauncherComponent = launcherComponent; + public void onConnectedToLauncher() { boolean alreadyLearnedSwipeUpForRecents = Prefs.getBoolean(mContext, Prefs.Key.HAS_SWIPED_UP_FOR_RECENTS, false); if (!mTaskListenerRegistered && !alreadyLearnedSwipeUpForRecents) { @@ -164,6 +149,15 @@ public class SwipeUpOnboarding { } } + public void onRecentsAnimationStarted() { + boolean alreadyLearnedSwipeUpForRecents = Prefs.getBoolean(mContext, + Prefs.Key.HAS_SWIPED_UP_FOR_RECENTS, false); + if (!alreadyLearnedSwipeUpForRecents) { + Prefs.putBoolean(mContext, Prefs.Key.HAS_SWIPED_UP_FOR_RECENTS, true); + onDisconnectedFromLauncher(); + } + } + public void onDisconnectedFromLauncher() { if (mTaskListenerRegistered) { ActivityManagerWrapper.getInstance().unregisterTaskStackListener(mTaskListener); diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java index 0132fa850f95..bf4a225a00ec 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java @@ -293,6 +293,7 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> { sharingIntent.setType("image/png"); sharingIntent.putExtra(Intent.EXTRA_STREAM, uri); sharingIntent.putExtra(Intent.EXTRA_SUBJECT, subject); + sharingIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); // Create a share action for the notification. Note, we proxy the call to // ScreenshotActionReceiver because RemoteViews currently forces an activity options @@ -310,7 +311,9 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> { Intent editIntent = new Intent(Intent.ACTION_EDIT); editIntent.setType("image/png"); - editIntent.putExtra(Intent.EXTRA_STREAM, uri); + editIntent.setData(uri); + editIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); + editIntent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION); // Create a edit action for the notification the same way. PendingIntent editAction = PendingIntent.getBroadcast(context, 1, @@ -902,6 +905,7 @@ class GlobalScreenshot { Intent chooserIntent = Intent.createChooser(sharingIntent, null, chooseAction.getIntentSender()) .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK); + ActivityOptions opts = ActivityOptions.makeBasic(); opts.setDisallowEnterPictureInPictureWhileLaunching(true); context.startActivityAsUser(chooserIntent, opts.toBundle(), UserHandle.CURRENT); diff --git a/packages/SystemUI/src/com/android/systemui/settings/BrightnessController.java b/packages/SystemUI/src/com/android/systemui/settings/BrightnessController.java index 15e92f48890f..406eef82f737 100644 --- a/packages/SystemUI/src/com/android/systemui/settings/BrightnessController.java +++ b/packages/SystemUI/src/com/android/systemui/settings/BrightnessController.java @@ -81,7 +81,7 @@ public class BrightnessController implements ToggleSlider.Listener { private volatile boolean mIsVrModeEnabled; private boolean mListening; private boolean mExternalChange; - private boolean mControlInitialized; + private boolean mControlValueInitialized; private ValueAnimator mSliderAnimator; @@ -337,6 +337,7 @@ public class BrightnessController implements ToggleSlider.Listener { mBackgroundHandler.post(mStopListeningRunnable); mListening = false; + mControlValueInitialized = false; } @Override @@ -428,10 +429,10 @@ public class BrightnessController implements ToggleSlider.Listener { } private void animateSliderTo(int target) { - if (!mControlInitialized) { + if (!mControlValueInitialized) { // Don't animate the first value since it's default state isn't meaningful to users. mControl.setValue(target); - mControlInitialized = true; + mControlValueInitialized = true; } if (mSliderAnimator != null && mSliderAnimator.isStarted()) { mSliderAnimator.cancel(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java index 3ebeb4d45c26..3dfb9130af2e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java @@ -379,7 +379,7 @@ public class CarStatusBar extends StatusBar implements // Because space is usually constrained in the auto use-case, there should not be a // pinned notification when the shade has been expanded. Ensure this by removing all heads- // up notifications. - mHeadsUpManager.removeAllHeadsUpEntries(); + mHeadsUpManager.releaseAllImmediately(); super.animateExpandNotificationsPanel(); } 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 907af690a615..11d20b221051 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ActivityLaunchAnimator.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ActivityLaunchAnimator.java @@ -56,10 +56,15 @@ public class ActivityLaunchAnimator { public static final long ANIMATION_DELAY_ICON_FADE_IN = ANIMATION_DURATION - CollapsedStatusBarFragment.FADE_IN_DURATION - CollapsedStatusBarFragment.FADE_IN_DELAY - 16; + private static final long LAUNCH_TIMEOUT = 500; private final NotificationPanelView mNotificationPanel; private final NotificationListContainer mNotificationContainer; private final StatusBarWindowView mStatusBarWindow; - private final StatusBar mStatusBar; + private StatusBar mStatusBar; + private final Runnable mTimeoutRunnable = () -> { + setAnimationPending(false); + mStatusBar.collapsePanel(true /* animate */); + }; private boolean mAnimationPending; public ActivityLaunchAnimator(StatusBarWindowView statusBarWindow, @@ -92,6 +97,11 @@ public class ActivityLaunchAnimator { private void setAnimationPending(boolean pending) { mAnimationPending = pending; mStatusBarWindow.setExpandAnimationPending(pending); + if (pending) { + mStatusBarWindow.postDelayed(mTimeoutRunnable, LAUNCH_TIMEOUT); + } else { + mStatusBarWindow.removeCallbacks(mTimeoutRunnable); + } } class AnimationRunner extends IRemoteAnimationRunner.Stub { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java new file mode 100644 index 000000000000..c45c5386eda4 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java @@ -0,0 +1,455 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.phone; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.Context; +import android.content.res.Resources; +import android.support.v4.util.ArraySet; +import android.util.Log; +import android.util.Pools; +import android.view.View; +import android.view.ViewTreeObserver; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.systemui.Dumpable; +import com.android.systemui.statusbar.ExpandableNotificationRow; +import com.android.systemui.statusbar.NotificationData; +import com.android.systemui.statusbar.StatusBarState; +import com.android.systemui.statusbar.notification.VisualStabilityManager; +import com.android.systemui.statusbar.policy.HeadsUpManager; +import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener; + +import java.io.FileDescriptor; +import java.io.PrintWriter; +import java.util.HashSet; +import java.util.Stack; + +/** + * A implementation of HeadsUpManager for phone and car. + */ +public class HeadsUpManagerPhone extends HeadsUpManager implements Dumpable, + ViewTreeObserver.OnComputeInternalInsetsListener, VisualStabilityManager.Callback, + OnHeadsUpChangedListener { + private static final String TAG = "HeadsUpManagerPhone"; + private static final boolean DEBUG = false; + + private final View mStatusBarWindowView; + private final int mStatusBarHeight; + private final NotificationGroupManager mGroupManager; + private final StatusBar mBar; + private final VisualStabilityManager mVisualStabilityManager; + + private boolean mReleaseOnExpandFinish; + private boolean mTrackingHeadsUp; + private HashSet<String> mSwipedOutKeys = new HashSet<>(); + private HashSet<NotificationData.Entry> mEntriesToRemoveAfterExpand = new HashSet<>(); + private ArraySet<NotificationData.Entry> mEntriesToRemoveWhenReorderingAllowed + = new ArraySet<>(); + private boolean mIsExpanded; + private int[] mTmpTwoArray = new int[2]; + private boolean mHeadsUpGoingAway; + private boolean mWaitingOnCollapseWhenGoingAway; + private boolean mIsObserving; + private int mStatusBarState; + + private final Pools.Pool<HeadsUpEntryPhone> mEntryPool = new Pools.Pool<HeadsUpEntryPhone>() { + private Stack<HeadsUpEntryPhone> mPoolObjects = new Stack<>(); + + @Override + public HeadsUpEntryPhone acquire() { + if (!mPoolObjects.isEmpty()) { + return mPoolObjects.pop(); + } + return new HeadsUpEntryPhone(); + } + + @Override + public boolean release(@NonNull HeadsUpEntryPhone instance) { + instance.reset(); + mPoolObjects.push(instance); + return true; + } + }; + + /////////////////////////////////////////////////////////////////////////////////////////////// + // Constructor: + + public HeadsUpManagerPhone(@NonNull final Context context, @NonNull View statusBarWindowView, + @NonNull NotificationGroupManager groupManager, @NonNull StatusBar bar, + @NonNull VisualStabilityManager visualStabilityManager) { + super(context); + + mStatusBarWindowView = statusBarWindowView; + mGroupManager = groupManager; + mBar = bar; + mVisualStabilityManager = visualStabilityManager; + + Resources resources = mContext.getResources(); + mStatusBarHeight = resources.getDimensionPixelSize( + com.android.internal.R.dimen.status_bar_height); + + addListener(new OnHeadsUpChangedListener() { + @Override + public void onHeadsUpPinnedModeChanged(boolean hasPinnedNotification) { + if (DEBUG) Log.w(TAG, "onHeadsUpPinnedModeChanged"); + updateTouchableRegionListener(); + } + }); + } + + /////////////////////////////////////////////////////////////////////////////////////////////// + // Public methods: + + /** + * Decides whether a click is invalid for a notification, i.e it has not been shown long enough + * that a user might have consciously clicked on it. + * + * @param key the key of the touched notification + * @return whether the touch is invalid and should be discarded + */ + public boolean shouldSwallowClick(@NonNull String key) { + HeadsUpManager.HeadsUpEntry entry = getHeadsUpEntry(key); + if (entry != null && mClock.currentTimeMillis() < entry.postTime) { + return true; + } + return false; + } + + public void onExpandingFinished() { + if (mReleaseOnExpandFinish) { + releaseAllImmediately(); + mReleaseOnExpandFinish = false; + } else { + for (NotificationData.Entry entry : mEntriesToRemoveAfterExpand) { + if (isHeadsUp(entry.key)) { + // Maybe the heads-up was removed already + removeHeadsUpEntry(entry); + } + } + } + mEntriesToRemoveAfterExpand.clear(); + } + + /** + * Sets the tracking-heads-up flag. If the flag is true, HeadsUpManager doesn't remove the entry + * from the list even after a Heads Up Notification is gone. + */ + public void setTrackingHeadsUp(boolean trackingHeadsUp) { + mTrackingHeadsUp = trackingHeadsUp; + } + + /** + * Notify that the status bar panel gets expanded or collapsed. + * + * @param isExpanded True to notify expanded, false to notify collapsed. + */ + public void setIsPanelExpanded(boolean isExpanded) { + if (isExpanded != mIsExpanded) { + mIsExpanded = isExpanded; + if (isExpanded) { + // make sure our state is sane + mWaitingOnCollapseWhenGoingAway = false; + mHeadsUpGoingAway = false; + updateTouchableRegionListener(); + } + } + } + + /** + * Set the current state of the statusbar. + */ + public void setStatusBarState(int statusBarState) { + mStatusBarState = statusBarState; + } + + /** + * Set that we are exiting the headsUp pinned mode, but some notifications might still be + * animating out. This is used to keep the touchable regions in a sane state. + */ + public void setHeadsUpGoingAway(boolean headsUpGoingAway) { + if (headsUpGoingAway != mHeadsUpGoingAway) { + mHeadsUpGoingAway = headsUpGoingAway; + if (!headsUpGoingAway) { + waitForStatusBarLayout(); + } + updateTouchableRegionListener(); + } + } + + /** + * Notifies that a remote input textbox in notification gets active or inactive. + * @param entry The entry of the target notification. + * @param remoteInputActive True to notify active, False to notify inactive. + */ + public void setRemoteInputActive( + @NonNull NotificationData.Entry entry, boolean remoteInputActive) { + HeadsUpEntryPhone headsUpEntry = getHeadsUpEntryPhone(entry.key); + if (headsUpEntry != null && headsUpEntry.remoteInputActive != remoteInputActive) { + headsUpEntry.remoteInputActive = remoteInputActive; + if (remoteInputActive) { + headsUpEntry.removeAutoRemovalCallbacks(); + } else { + headsUpEntry.updateEntry(false /* updatePostTime */); + } + } + } + + @VisibleForTesting + public void removeMinimumDisplayTimeForTesting() { + mMinimumDisplayTime = 1; + mHeadsUpNotificationDecay = 1; + mTouchAcceptanceDelay = 1; + } + + /////////////////////////////////////////////////////////////////////////////////////////////// + // HeadsUpManager public methods overrides: + + @Override + public boolean isTrackingHeadsUp() { + return mTrackingHeadsUp; + } + + @Override + public void snooze() { + super.snooze(); + mReleaseOnExpandFinish = true; + } + + /** + * React to the removal of the notification in the heads up. + * + * @return true if the notification was removed and false if it still needs to be kept around + * for a bit since it wasn't shown long enough + */ + @Override + public boolean removeNotification(@NonNull String key, boolean ignoreEarliestRemovalTime) { + if (wasShownLongEnough(key) || ignoreEarliestRemovalTime) { + return super.removeNotification(key, ignoreEarliestRemovalTime); + } else { + HeadsUpEntryPhone entry = getHeadsUpEntryPhone(key); + entry.removeAsSoonAsPossible(); + return false; + } + } + + public void addSwipedOutNotification(@NonNull String key) { + mSwipedOutKeys.add(key); + } + + /////////////////////////////////////////////////////////////////////////////////////////////// + // Dumpable overrides: + + @Override + public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + pw.println("HeadsUpManagerPhone state:"); + dumpInternal(fd, pw, args); + } + + /////////////////////////////////////////////////////////////////////////////////////////////// + // ViewTreeObserver.OnComputeInternalInsetsListener overrides: + + /** + * Overridden from TreeObserver. + */ + @Override + public void onComputeInternalInsets(ViewTreeObserver.InternalInsetsInfo info) { + if (mIsExpanded || mBar.isBouncerShowing()) { + // The touchable region is always the full area when expanded + return; + } + if (hasPinnedHeadsUp()) { + ExpandableNotificationRow topEntry = getTopEntry().row; + if (topEntry.isChildInGroup()) { + final ExpandableNotificationRow groupSummary + = mGroupManager.getGroupSummary(topEntry.getStatusBarNotification()); + if (groupSummary != null) { + topEntry = groupSummary; + } + } + topEntry.getLocationOnScreen(mTmpTwoArray); + int minX = mTmpTwoArray[0]; + int maxX = mTmpTwoArray[0] + topEntry.getWidth(); + int maxY = topEntry.getIntrinsicHeight(); + + info.setTouchableInsets(ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION); + info.touchableRegion.set(minX, 0, maxX, maxY); + } else if (mHeadsUpGoingAway || mWaitingOnCollapseWhenGoingAway) { + info.setTouchableInsets(ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION); + info.touchableRegion.set(0, 0, mStatusBarWindowView.getWidth(), mStatusBarHeight); + } + } + + /////////////////////////////////////////////////////////////////////////////////////////////// + // VisualStabilityManager.Callback overrides: + + @Override + public void onReorderingAllowed() { + mBar.getNotificationScrollLayout().setHeadsUpGoingAwayAnimationsAllowed(false); + for (NotificationData.Entry entry : mEntriesToRemoveWhenReorderingAllowed) { + if (isHeadsUp(entry.key)) { + // Maybe the heads-up was removed already + removeHeadsUpEntry(entry); + } + } + mEntriesToRemoveWhenReorderingAllowed.clear(); + mBar.getNotificationScrollLayout().setHeadsUpGoingAwayAnimationsAllowed(true); + } + + /////////////////////////////////////////////////////////////////////////////////////////////// + // HeadsUpManager utility (protected) methods overrides: + + @Override + protected HeadsUpEntry createHeadsUpEntry() { + return mEntryPool.acquire(); + } + + @Override + protected void releaseHeadsUpEntry(HeadsUpEntry entry) { + mEntryPool.release((HeadsUpEntryPhone) entry); + } + + @Override + protected boolean shouldHeadsUpBecomePinned(NotificationData.Entry entry) { + return mStatusBarState != StatusBarState.KEYGUARD && !mIsExpanded + || super.shouldHeadsUpBecomePinned(entry); + } + + @Override + protected void dumpInternal(FileDescriptor fd, PrintWriter pw, String[] args) { + super.dumpInternal(fd, pw, args); + pw.print(" mStatusBarState="); pw.println(mStatusBarState); + } + + /////////////////////////////////////////////////////////////////////////////////////////////// + // Protected utility methods: + + @Nullable + protected HeadsUpEntryPhone getHeadsUpEntryPhone(@NonNull String key) { + return (HeadsUpEntryPhone) getHeadsUpEntry(key); + } + + @Nullable + protected HeadsUpEntryPhone getTopHeadsUpEntryPhone() { + return (HeadsUpEntryPhone) getTopHeadsUpEntry(); + } + + /////////////////////////////////////////////////////////////////////////////////////////////// + // Private utility methods: + + private boolean wasShownLongEnough(@NonNull String key) { + if (mSwipedOutKeys.contains(key)) { + // We always instantly dismiss views being manually swiped out. + mSwipedOutKeys.remove(key); + return true; + } + + HeadsUpEntryPhone headsUpEntry = getHeadsUpEntryPhone(key); + HeadsUpEntryPhone topEntry = getTopHeadsUpEntryPhone(); + if (headsUpEntry != topEntry) { + return true; + } + return headsUpEntry.wasShownLongEnough(); + } + + /** + * We need to wait on the whole panel to collapse, before we can remove the touchable region + * listener. + */ + private void waitForStatusBarLayout() { + mWaitingOnCollapseWhenGoingAway = true; + mStatusBarWindowView.addOnLayoutChangeListener(new View.OnLayoutChangeListener() { + @Override + public void onLayoutChange(View v, int left, int top, int right, int bottom, + int oldLeft, + int oldTop, int oldRight, int oldBottom) { + if (mStatusBarWindowView.getHeight() <= mStatusBarHeight) { + mStatusBarWindowView.removeOnLayoutChangeListener(this); + mWaitingOnCollapseWhenGoingAway = false; + updateTouchableRegionListener(); + } + } + }); + } + + private void updateTouchableRegionListener() { + boolean shouldObserve = hasPinnedHeadsUp() || mHeadsUpGoingAway + || mWaitingOnCollapseWhenGoingAway; + if (shouldObserve == mIsObserving) { + return; + } + if (shouldObserve) { + mStatusBarWindowView.getViewTreeObserver().addOnComputeInternalInsetsListener(this); + mStatusBarWindowView.requestLayout(); + } else { + mStatusBarWindowView.getViewTreeObserver().removeOnComputeInternalInsetsListener(this); + } + mIsObserving = shouldObserve; + } + + /////////////////////////////////////////////////////////////////////////////////////////////// + // HeadsUpEntryPhone: + + protected class HeadsUpEntryPhone extends HeadsUpManager.HeadsUpEntry { + public void setEntry(@NonNull final NotificationData.Entry entry) { + Runnable removeHeadsUpRunnable = () -> { + if (!mVisualStabilityManager.isReorderingAllowed()) { + mEntriesToRemoveWhenReorderingAllowed.add(entry); + mVisualStabilityManager.addReorderingAllowedCallback( + HeadsUpManagerPhone.this); + } else if (!mTrackingHeadsUp) { + removeHeadsUpEntry(entry); + } else { + mEntriesToRemoveAfterExpand.add(entry); + } + }; + + super.setEntry(entry, removeHeadsUpRunnable); + } + + public boolean wasShownLongEnough() { + return earliestRemovaltime < mClock.currentTimeMillis(); + } + + @Override + public void updateEntry(boolean updatePostTime) { + super.updateEntry(updatePostTime); + + if (mEntriesToRemoveAfterExpand.contains(entry)) { + mEntriesToRemoveAfterExpand.remove(entry); + } + if (mEntriesToRemoveWhenReorderingAllowed.contains(entry)) { + mEntriesToRemoveWhenReorderingAllowed.remove(entry); + } + } + + @Override + public void expanded(boolean expanded) { + if (this.expanded == expanded) { + return; + } + + this.expanded = expanded; + if (expanded) { + removeAutoRemovalCallbacks(); + } else { + updateEntry(false /* updatePostTime */); + } + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpTouchHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpTouchHelper.java index c85571c1895d..2bfdefe39017 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpTouchHelper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpTouchHelper.java @@ -23,7 +23,7 @@ import android.view.ViewConfiguration; import com.android.systemui.Gefingerpoken; import com.android.systemui.statusbar.ExpandableNotificationRow; import com.android.systemui.statusbar.ExpandableView; -import com.android.systemui.statusbar.policy.HeadsUpManager; +import com.android.systemui.statusbar.phone.HeadsUpManagerPhone; import com.android.systemui.statusbar.stack.NotificationStackScrollLayout; /** @@ -31,7 +31,7 @@ import com.android.systemui.statusbar.stack.NotificationStackScrollLayout; */ public class HeadsUpTouchHelper implements Gefingerpoken { - private HeadsUpManager mHeadsUpManager; + private HeadsUpManagerPhone mHeadsUpManager; private NotificationStackScrollLayout mStackScroller; private int mTrackingPointer; private float mTouchSlop; @@ -43,7 +43,7 @@ public class HeadsUpTouchHelper implements Gefingerpoken { private NotificationPanelView mPanel; private ExpandableNotificationRow mPickedChild; - public HeadsUpTouchHelper(HeadsUpManager headsUpManager, + public HeadsUpTouchHelper(HeadsUpManagerPhone headsUpManager, NotificationStackScrollLayout stackScroller, NotificationPanelView notificationPanelView) { mHeadsUpManager = headsUpManager; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarInflaterView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarInflaterView.java index b8b309b38b27..9d20e4e1bb87 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarInflaterView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarInflaterView.java @@ -220,6 +220,11 @@ public class NavigationBarInflaterView extends FrameLayout newLayout = getDefaultLayout(); } String[] sets = newLayout.split(GRAVITY_SEPARATOR, 3); + if (sets.length != 3) { + Log.d(TAG, "Invalid layout."); + newLayout = getDefaultLayout(); + sets = newLayout.split(GRAVITY_SEPARATOR, 3); + } String[] start = sets[0].split(BUTTON_SEPARATOR); String[] center = sets[1].split(BUTTON_SEPARATOR); String[] end = sets[2].split(BUTTON_SEPARATOR); 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 b5fa52378660..de6ecac5b00e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java @@ -218,6 +218,9 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav @Override public void onRecentsAnimationStarted() { mRecentsAnimationStarted = true; + if (mSwipeUpOnboarding != null) { + mSwipeUpOnboarding.onRecentsAnimationStarted(); + } } }; @@ -887,7 +890,7 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav private void setUpSwipeUpOnboarding(boolean connectedToOverviewProxy) { if (connectedToOverviewProxy) { - mSwipeUpOnboarding.onConnectedToLauncher(mOverviewProxyService.getLauncherComponent()); + mSwipeUpOnboarding.onConnectedToLauncher(); } else { mSwipeUpOnboarding.onDisconnectedFromLauncher(); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java index 31b8159d3cdf..2111d2ef5d87 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java @@ -68,7 +68,7 @@ import com.android.systemui.statusbar.NotificationShelf; import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.notification.ActivityLaunchAnimator; import com.android.systemui.statusbar.notification.NotificationUtils; -import com.android.systemui.statusbar.policy.HeadsUpManager; +import com.android.systemui.statusbar.phone.HeadsUpManagerPhone; import com.android.systemui.statusbar.policy.KeyguardUserSwitcher; import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener; import com.android.systemui.statusbar.stack.NotificationStackScrollLayout; @@ -1571,7 +1571,7 @@ public class NotificationPanelView extends PanelView implements private void updatePanelExpanded() { boolean isExpanded = !isFullyCollapsed(); if (mPanelExpanded != isExpanded) { - mHeadsUpManager.setIsExpanded(isExpanded); + mHeadsUpManager.setIsPanelExpanded(isExpanded); mStatusBar.setPanelExpanded(isExpanded); mPanelExpanded = isExpanded; } @@ -2338,7 +2338,7 @@ public class NotificationPanelView extends PanelView implements } @Override - public void setHeadsUpManager(HeadsUpManager headsUpManager) { + public void setHeadsUpManager(HeadsUpManagerPhone headsUpManager) { super.setHeadsUpManager(headsUpManager); mHeadsUpTouchHelper = new HeadsUpTouchHelper(headsUpManager, mNotificationStackScroller, this); @@ -2630,8 +2630,8 @@ public class NotificationPanelView extends PanelView implements } } - public void setPulsing(Collection<HeadsUpManager.HeadsUpEntry> pulsing) { - mKeyguardStatusView.setPulsing(pulsing != null); + public void setPulsing(boolean pulsing) { + mKeyguardStatusView.setPulsing(pulsing); positionClockAndNotifications(); mNotificationStackScroller.setPulsing(pulsing, mKeyguardStatusView.getLocationOnScreen()[1] + mKeyguardStatusView.getClockBottom()); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java index 2b7e4747a837..6daabede7f32 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java @@ -50,7 +50,7 @@ import com.android.systemui.classifier.FalsingManager; import com.android.systemui.doze.DozeLog; import com.android.systemui.statusbar.FlingAnimationUtils; import com.android.systemui.statusbar.StatusBarState; -import com.android.systemui.statusbar.policy.HeadsUpManager; +import com.android.systemui.statusbar.phone.HeadsUpManagerPhone; import java.io.FileDescriptor; import java.io.PrintWriter; @@ -75,7 +75,7 @@ public abstract class PanelView extends FrameLayout { } protected StatusBar mStatusBar; - protected HeadsUpManager mHeadsUpManager; + protected HeadsUpManagerPhone mHeadsUpManager; private float mPeekHeight; private float mHintDistance; @@ -1252,7 +1252,7 @@ public abstract class PanelView extends FrameLayout { */ protected abstract int getClearAllHeight(); - public void setHeadsUpManager(HeadsUpManager headsUpManager) { + public void setHeadsUpManager(HeadsUpManagerPhone headsUpManager) { mHeadsUpManager = headsUpManager; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java index 20b501821a97..6444cc816663 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java @@ -407,17 +407,17 @@ public class PhoneStatusBarPolicy implements Callback, Callbacks, int iconId = R.drawable.stat_sys_data_bluetooth; String contentDescription = mContext.getString(R.string.accessibility_quick_settings_bluetooth_on); - boolean bluetoothEnabled = false; + boolean bluetoothVisible = false; if (mBluetooth != null) { - bluetoothEnabled = mBluetooth.isBluetoothEnabled(); if (mBluetooth.isBluetoothConnected()) { iconId = R.drawable.stat_sys_data_bluetooth_connected; contentDescription = mContext.getString(R.string.accessibility_bluetooth_connected); + bluetoothVisible = mBluetooth.isBluetoothEnabled(); } } mIconController.setIcon(mSlotBluetooth, iconId, contentDescription); - mIconController.setIconVisibility(mSlotBluetooth, bluetoothEnabled); + mIconController.setIconVisibility(mSlotBluetooth, bluetoothVisible); } private final void updateTTY() { 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 b51982415bbd..116e3f93bf8f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java @@ -208,6 +208,7 @@ import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.notification.AboveShelfObserver; import com.android.systemui.statusbar.notification.ActivityLaunchAnimator; import com.android.systemui.statusbar.notification.VisualStabilityManager; +import com.android.systemui.statusbar.phone.HeadsUpManagerPhone; import com.android.systemui.statusbar.phone.UnlockMethodCache.OnUnlockMethodChangedListener; import com.android.systemui.statusbar.policy.BatteryController; import com.android.systemui.statusbar.policy.BatteryController.BatteryStateChangeCallback; @@ -219,6 +220,7 @@ import com.android.systemui.statusbar.policy.DeviceProvisionedController; import com.android.systemui.statusbar.policy.DeviceProvisionedController.DeviceProvisionedListener; import com.android.systemui.statusbar.policy.ExtensionController; import com.android.systemui.statusbar.policy.HeadsUpManager; +import com.android.systemui.statusbar.policy.HeadsUpUtil; import com.android.systemui.statusbar.policy.KeyguardMonitor; import com.android.systemui.statusbar.policy.KeyguardMonitorImpl; import com.android.systemui.statusbar.policy.KeyguardUserSwitcher; @@ -802,15 +804,14 @@ public class StatusBar extends SystemUI implements DemoMode, .commit(); mIconController = Dependency.get(StatusBarIconController.class); - mHeadsUpManager = new HeadsUpManager(context, mStatusBarWindow, mGroupManager); - mHeadsUpManager.setBar(this); + mHeadsUpManager = new HeadsUpManagerPhone(context, mStatusBarWindow, mGroupManager, this, + mVisualStabilityManager); mHeadsUpManager.addListener(this); mHeadsUpManager.addListener(mNotificationPanel); mHeadsUpManager.addListener(mGroupManager); mHeadsUpManager.addListener(mVisualStabilityManager); mNotificationPanel.setHeadsUpManager(mHeadsUpManager); mGroupManager.setHeadsUpManager(mHeadsUpManager); - mHeadsUpManager.setVisualStabilityManager(mVisualStabilityManager); putComponent(HeadsUpManager.class, mHeadsUpManager); mEntryManager.setUpWithPresenter(this, mStackScroller, this, mHeadsUpManager); @@ -1341,7 +1342,8 @@ public class StatusBar extends SystemUI implements DemoMode, @Override public void onPerformRemoveNotification(StatusBarNotification n) { - if (mStackScroller.hasPulsingNotifications() && mHeadsUpManager.getAllEntries().isEmpty()) { + if (mStackScroller.hasPulsingNotifications() && + !mHeadsUpManager.hasHeadsUpNotifications()) { // We were showing a pulse for a notification, but no notifications are pulsing anymore. // Finish the pulse. mDozeScrimController.pulseOutNow(); @@ -2090,9 +2092,8 @@ public class StatusBar extends SystemUI implements DemoMode, } public void maybeEscalateHeadsUp() { - Collection<HeadsUpManager.HeadsUpEntry> entries = mHeadsUpManager.getAllEntries(); - for (HeadsUpManager.HeadsUpEntry entry : entries) { - final StatusBarNotification sbn = entry.entry.notification; + mHeadsUpManager.getAllEntries().forEach(entry -> { + final StatusBarNotification sbn = entry.notification; final Notification notification = sbn.getNotification(); if (notification.fullScreenIntent != null) { if (DEBUG) { @@ -2102,11 +2103,11 @@ public class StatusBar extends SystemUI implements DemoMode, EventLog.writeEvent(EventLogTags.SYSUI_HEADS_UP_ESCALATION, sbn.getKey()); notification.fullScreenIntent.send(); - entry.entry.notifyFullScreenIntentLaunched(); + entry.notifyFullScreenIntentLaunched(); } catch (PendingIntent.CanceledException e) { } } - } + }); mHeadsUpManager.releaseAllImmediately(); } @@ -2656,6 +2657,10 @@ public class StatusBar extends SystemUI implements DemoMode, if (mStatusBarView != null) { dumpBarTransitions(pw, "mStatusBarView", mStatusBarView.getBarTransitions()); } + pw.println(" StatusBarWindowView: "); + if (mStatusBarWindow != null) { + mStatusBarWindow.dump(fd, pw, args); + } pw.println(" mMediaManager: "); if (mMediaManager != null) { @@ -4632,24 +4637,22 @@ public class StatusBar extends SystemUI implements DemoMode, @Override public void onPulseStarted() { callback.onPulseStarted(); - Collection<HeadsUpManager.HeadsUpEntry> pulsingEntries = - mHeadsUpManager.getAllEntries(); - if (!pulsingEntries.isEmpty()) { + if (mHeadsUpManager.hasHeadsUpNotifications()) { // Only pulse the stack scroller if there's actually something to show. // Otherwise just show the always-on screen. - setPulsing(pulsingEntries); + setPulsing(true); } } @Override public void onPulseFinished() { callback.onPulseFinished(); - setPulsing(null); + setPulsing(false); } - private void setPulsing(Collection<HeadsUpManager.HeadsUpEntry> pulsing) { + private void setPulsing(boolean pulsing) { mNotificationPanel.setPulsing(pulsing); - mVisualStabilityManager.setPulsing(pulsing != null); + mVisualStabilityManager.setPulsing(pulsing); mIgnoreTouchWhilePulsing = false; } }, reason); @@ -4797,7 +4800,7 @@ public class StatusBar extends SystemUI implements DemoMode, // for heads up notifications - protected HeadsUpManager mHeadsUpManager; + protected HeadsUpManagerPhone mHeadsUpManager; private AboveShelfObserver mAboveShelfObserver; @@ -4900,7 +4903,7 @@ public class StatusBar extends SystemUI implements DemoMode, // Release the HUN notification to the shade. if (isPresenterFullyCollapsed()) { - HeadsUpManager.setIsClickedNotification(row, true); + HeadsUpUtil.setIsClickedHeadsUpNotification(row, true); } // // In most cases, when FLAG_AUTO_CANCEL is set, the notification will diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java index e32914fa368b..a79a41b07797 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java @@ -62,6 +62,9 @@ import com.android.systemui.statusbar.DragDownHelper; import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.stack.NotificationStackScrollLayout; +import java.io.FileDescriptor; +import java.io.PrintWriter; + public class StatusBarWindowView extends FrameLayout { public static final String TAG = "StatusBarWindowView"; @@ -398,6 +401,13 @@ public class StatusBarWindowView extends FrameLayout { mExpandAnimationPending = pending; } + public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + pw.print(" mExpandAnimationPending="); pw.println(mExpandAnimationPending); + pw.print(" mExpandAnimationRunning="); pw.println(mExpandAnimationRunning); + pw.print(" mTouchCancelled="); pw.println(mTouchCancelled); + pw.print(" mTouchActive="); pw.println(mTouchActive); + } + public class LayoutParams extends FrameLayout.LayoutParams { public boolean ignoreRightInset; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/AccessibilityManagerWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/AccessibilityManagerWrapper.java index 6a573f593a92..d85e18c17252 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/AccessibilityManagerWrapper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/AccessibilityManagerWrapper.java @@ -14,10 +14,14 @@ package com.android.systemui.statusbar.policy; +import android.accessibilityservice.AccessibilityServiceInfo; import android.content.Context; +import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityManager; import android.view.accessibility.AccessibilityManager.AccessibilityServicesStateChangeListener; +import java.util.List; + /** * For mocking because AccessibilityManager is final for some reason... */ @@ -39,4 +43,27 @@ public class AccessibilityManagerWrapper implements public void removeCallback(AccessibilityServicesStateChangeListener listener) { mAccessibilityManager.removeAccessibilityServicesStateChangeListener(listener); } + + public void addAccessibilityStateChangeListener( + AccessibilityManager.AccessibilityStateChangeListener listener) { + mAccessibilityManager.addAccessibilityStateChangeListener(listener); + } + + public void removeAccessibilityStateChangeListener( + AccessibilityManager.AccessibilityStateChangeListener listener) { + mAccessibilityManager.removeAccessibilityStateChangeListener(listener); + } + + public boolean isEnabled() { + return mAccessibilityManager.isEnabled(); + } + + public void sendAccessibilityEvent(AccessibilityEvent event) { + mAccessibilityManager.sendAccessibilityEvent(event); + } + + public List<AccessibilityServiceInfo> getEnabledAccessibilityServiceList( + int feedbackTypeFlags) { + return mAccessibilityManager.getEnabledAccessibilityServiceList(feedbackTypeFlags); + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java index 53dfb244c776..a2b896dc015b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java @@ -16,118 +16,68 @@ package com.android.systemui.statusbar.policy; +import android.annotation.NonNull; +import android.annotation.Nullable; import android.content.Context; import android.content.res.Resources; import android.database.ContentObserver; +import android.os.SystemClock; import android.os.Handler; import android.os.Looper; -import android.os.SystemClock; -import android.provider.Settings; -import android.support.v4.util.ArraySet; import android.util.ArrayMap; +import android.provider.Settings; import android.util.Log; -import android.util.Pools; -import android.view.View; -import android.view.ViewTreeObserver; import android.view.accessibility.AccessibilityEvent; import com.android.internal.logging.MetricsLogger; import com.android.systemui.R; import com.android.systemui.statusbar.ExpandableNotificationRow; import com.android.systemui.statusbar.NotificationData; -import com.android.systemui.statusbar.StatusBarState; -import com.android.systemui.statusbar.notification.VisualStabilityManager; -import com.android.systemui.statusbar.phone.NotificationGroupManager; -import com.android.systemui.statusbar.phone.StatusBar; import java.io.FileDescriptor; import java.io.PrintWriter; -import java.util.ArrayList; -import java.util.Collection; +import java.util.Iterator; +import java.util.stream.Stream; import java.util.HashMap; import java.util.HashSet; -import java.util.Stack; /** * A manager which handles heads up notifications which is a special mode where * they simply peek from the top of the screen. */ -public class HeadsUpManager implements ViewTreeObserver.OnComputeInternalInsetsListener, - VisualStabilityManager.Callback { +public class HeadsUpManager { private static final String TAG = "HeadsUpManager"; private static final boolean DEBUG = false; private static final String SETTING_HEADS_UP_SNOOZE_LENGTH_MS = "heads_up_snooze_length_ms"; - private static final int TAG_CLICKED_NOTIFICATION = R.id.is_clicked_heads_up_tag; - - private final int mHeadsUpNotificationDecay; - private final int mMinimumDisplayTime; - private final int mTouchAcceptanceDelay; - private final ArrayMap<String, Long> mSnoozedPackages; - private final HashSet<OnHeadsUpChangedListener> mListeners = new HashSet<>(); - private final int mDefaultSnoozeLengthMs; - private final Handler mHandler = new Handler(Looper.getMainLooper()); - private final Pools.Pool<HeadsUpEntry> mEntryPool = new Pools.Pool<HeadsUpEntry>() { + protected final Clock mClock = new Clock(); + protected final Context mContext; + protected final HashSet<OnHeadsUpChangedListener> mListeners = new HashSet<>(); + protected final Handler mHandler = new Handler(Looper.getMainLooper()); - private Stack<HeadsUpEntry> mPoolObjects = new Stack<>(); + protected int mHeadsUpNotificationDecay; + protected int mMinimumDisplayTime; + protected int mTouchAcceptanceDelay; + protected int mSnoozeLengthMs; + protected boolean mHasPinnedNotification; + protected int mUser; - @Override - public HeadsUpEntry acquire() { - if (!mPoolObjects.isEmpty()) { - return mPoolObjects.pop(); - } - return new HeadsUpEntry(); - } + private final HashMap<String, HeadsUpEntry> mHeadsUpEntries = new HashMap<>(); + private final ArrayMap<String, Long> mSnoozedPackages; + private final ContentObserver mSettingsObserver; - @Override - public boolean release(HeadsUpEntry instance) { - instance.reset(); - mPoolObjects.push(instance); - return true; - } - }; - - private final View mStatusBarWindowView; - private final int mStatusBarHeight; - private final Context mContext; - private final NotificationGroupManager mGroupManager; - private StatusBar mBar; - private int mSnoozeLengthMs; - private ContentObserver mSettingsObserver; - private HashMap<String, HeadsUpEntry> mHeadsUpEntries = new HashMap<>(); - private HashSet<String> mSwipedOutKeys = new HashSet<>(); - private int mUser; - private Clock mClock; - private boolean mReleaseOnExpandFinish; - private boolean mTrackingHeadsUp; - private HashSet<NotificationData.Entry> mEntriesToRemoveAfterExpand = new HashSet<>(); - private ArraySet<NotificationData.Entry> mEntriesToRemoveWhenReorderingAllowed - = new ArraySet<>(); - private boolean mIsExpanded; - private boolean mHasPinnedNotification; - private int[] mTmpTwoArray = new int[2]; - private boolean mHeadsUpGoingAway; - private boolean mWaitingOnCollapseWhenGoingAway; - private boolean mIsObserving; - private boolean mRemoteInputActive; - private float mExpandedHeight; - private VisualStabilityManager mVisualStabilityManager; - private int mStatusBarState; - - public HeadsUpManager(final Context context, View statusBarWindowView, - NotificationGroupManager groupManager) { + public HeadsUpManager(@NonNull final Context context) { mContext = context; - Resources resources = mContext.getResources(); - mTouchAcceptanceDelay = resources.getInteger(R.integer.touch_acceptance_delay); - mSnoozedPackages = new ArrayMap<>(); - mDefaultSnoozeLengthMs = resources.getInteger(R.integer.heads_up_default_snooze_length_ms); - mSnoozeLengthMs = mDefaultSnoozeLengthMs; + Resources resources = context.getResources(); mMinimumDisplayTime = resources.getInteger(R.integer.heads_up_notification_minimum_time); mHeadsUpNotificationDecay = resources.getInteger(R.integer.heads_up_notification_decay); - mClock = new Clock(); + mTouchAcceptanceDelay = resources.getInteger(R.integer.touch_acceptance_delay); + mSnoozedPackages = new ArrayMap<>(); + int defaultSnoozeLengthMs = + resources.getInteger(R.integer.heads_up_default_snooze_length_ms); mSnoozeLengthMs = Settings.Global.getInt(context.getContentResolver(), - SETTING_HEADS_UP_SNOOZE_LENGTH_MS, mDefaultSnoozeLengthMs); + SETTING_HEADS_UP_SNOOZE_LENGTH_MS, defaultSnoozeLengthMs); mSettingsObserver = new ContentObserver(mHandler) { @Override public void onChange(boolean selfChange) { @@ -142,47 +92,26 @@ public class HeadsUpManager implements ViewTreeObserver.OnComputeInternalInsetsL context.getContentResolver().registerContentObserver( Settings.Global.getUriFor(SETTING_HEADS_UP_SNOOZE_LENGTH_MS), false, mSettingsObserver); - mStatusBarWindowView = statusBarWindowView; - mGroupManager = groupManager; - mStatusBarHeight = resources.getDimensionPixelSize( - com.android.internal.R.dimen.status_bar_height); - } - - private void updateTouchableRegionListener() { - boolean shouldObserve = mHasPinnedNotification || mHeadsUpGoingAway - || mWaitingOnCollapseWhenGoingAway; - if (shouldObserve == mIsObserving) { - return; - } - if (shouldObserve) { - mStatusBarWindowView.getViewTreeObserver().addOnComputeInternalInsetsListener(this); - mStatusBarWindowView.requestLayout(); - } else { - mStatusBarWindowView.getViewTreeObserver().removeOnComputeInternalInsetsListener(this); - } - mIsObserving = shouldObserve; } - public void setBar(StatusBar bar) { - mBar = bar; - } - - public void addListener(OnHeadsUpChangedListener listener) { + /** + * Adds an OnHeadUpChangedListener to observe events. + */ + public void addListener(@NonNull OnHeadsUpChangedListener listener) { mListeners.add(listener); } - public void removeListener(OnHeadsUpChangedListener listener) { + /** + * Removes the OnHeadUpChangedListener from the observer list. + */ + public void removeListener(@NonNull OnHeadsUpChangedListener listener) { mListeners.remove(listener); } - public StatusBar getBar() { - return mBar; - } - /** * Called when posting a new notification to the heads up. */ - public void showNotification(NotificationData.Entry headsUp) { + public void showNotification(@NonNull NotificationData.Entry headsUp) { if (DEBUG) Log.v(TAG, "showNotification"); addHeadsUpEntry(headsUp); updateNotification(headsUp, true); @@ -192,7 +121,7 @@ public class HeadsUpManager implements ViewTreeObserver.OnComputeInternalInsetsL /** * Called when updating or posting a notification to the heads up. */ - public void updateNotification(NotificationData.Entry headsUp, boolean alert) { + public void updateNotification(@NonNull NotificationData.Entry headsUp, boolean alert) { if (DEBUG) Log.v(TAG, "updateNotification"); headsUp.row.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED); @@ -204,14 +133,13 @@ public class HeadsUpManager implements ViewTreeObserver.OnComputeInternalInsetsL // with the groupmanager return; } - headsUpEntry.updateEntry(); + headsUpEntry.updateEntry(true /* updatePostTime */); setEntryPinned(headsUpEntry, shouldHeadsUpBecomePinned(headsUp)); } } - private void addHeadsUpEntry(NotificationData.Entry entry) { - HeadsUpEntry headsUpEntry = mEntryPool.acquire(); - + private void addHeadsUpEntry(@NonNull NotificationData.Entry entry) { + HeadsUpEntry headsUpEntry = createHeadsUpEntry(); // This will also add the entry to the sortedList headsUpEntry.setEntry(entry); mHeadsUpEntries.put(entry.key, headsUpEntry); @@ -223,16 +151,17 @@ public class HeadsUpManager implements ViewTreeObserver.OnComputeInternalInsetsL entry.row.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED); } - private boolean shouldHeadsUpBecomePinned(NotificationData.Entry entry) { - return mStatusBarState != StatusBarState.KEYGUARD - && !mIsExpanded || hasFullScreenIntent(entry); + protected boolean shouldHeadsUpBecomePinned(@NonNull NotificationData.Entry entry) { + return hasFullScreenIntent(entry); } - private boolean hasFullScreenIntent(NotificationData.Entry entry) { + protected boolean hasFullScreenIntent(@NonNull NotificationData.Entry entry) { return entry.notification.getNotification().fullScreenIntent != null; } - private void setEntryPinned(HeadsUpEntry headsUpEntry, boolean isPinned) { + protected void setEntryPinned( + @NonNull HeadsUpManager.HeadsUpEntry headsUpEntry, boolean isPinned) { + if (DEBUG) Log.v(TAG, "setEntryPinned: " + isPinned); ExpandableNotificationRow row = headsUpEntry.entry.row; if (row.isPinned() != isPinned) { row.setPinned(isPinned); @@ -247,33 +176,35 @@ public class HeadsUpManager implements ViewTreeObserver.OnComputeInternalInsetsL } } - private void removeHeadsUpEntry(NotificationData.Entry entry) { + protected void removeHeadsUpEntry(NotificationData.Entry entry) { HeadsUpEntry remove = mHeadsUpEntries.remove(entry.key); + onHeadsUpEntryRemoved(remove); + releaseHeadsUpEntry(remove); + } + + protected void onHeadsUpEntryRemoved(HeadsUpEntry remove) { + NotificationData.Entry entry = remove.entry; entry.row.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED); entry.row.setHeadsUp(false); setEntryPinned(remove, false /* isPinned */); for (OnHeadsUpChangedListener listener : mListeners) { listener.onHeadsUpStateChanged(entry, false); } - mEntryPool.release(remove); - } - - public void removeAllHeadsUpEntries() { - for (String key : mHeadsUpEntries.keySet()) { - removeHeadsUpEntry(mHeadsUpEntries.get(key).entry); - } } - private void updatePinnedMode() { + protected void updatePinnedMode() { boolean hasPinnedNotification = hasPinnedNotificationInternal(); if (hasPinnedNotification == mHasPinnedNotification) { return; } + if (DEBUG) { + Log.v(TAG, "Pinned mode changed: " + mHasPinnedNotification + " -> " + + hasPinnedNotification); + } mHasPinnedNotification = hasPinnedNotification; if (mHasPinnedNotification) { MetricsLogger.count(mContext, "note_peek", 1); } - updateTouchableRegionListener(); for (OnHeadsUpChangedListener listener : mListeners) { listener.onHeadsUpPinnedModeChanged(hasPinnedNotification); } @@ -285,47 +216,36 @@ public class HeadsUpManager implements ViewTreeObserver.OnComputeInternalInsetsL * @return true if the notification was removed and false if it still needs to be kept around * for a bit since it wasn't shown long enough */ - public boolean removeNotification(String key, boolean ignoreEarliestRemovalTime) { - if (DEBUG) Log.v(TAG, "remove"); - if (wasShownLongEnough(key) || ignoreEarliestRemovalTime) { - releaseImmediately(key); - return true; - } else { - getHeadsUpEntry(key).removeAsSoonAsPossible(); - return false; - } - } - - private boolean wasShownLongEnough(String key) { - HeadsUpEntry headsUpEntry = getHeadsUpEntry(key); - HeadsUpEntry topEntry = getTopEntry(); - if (mSwipedOutKeys.contains(key)) { - // We always instantly dismiss views being manually swiped out. - mSwipedOutKeys.remove(key); - return true; - } - if (headsUpEntry != topEntry) { - return true; - } - return headsUpEntry.wasShownLongEnough(); + public boolean removeNotification(@NonNull String key, boolean ignoreEarliestRemovalTime) { + if (DEBUG) Log.v(TAG, "removeNotification"); + releaseImmediately(key); + return true; } + /** + * Returns if the given notification is in the Heads Up Notification list or not. + */ public boolean isHeadsUp(String key) { return mHeadsUpEntries.containsKey(key); } /** - * Push any current Heads Up notification down into the shade. + * Pushes any current Heads Up notification down into the shade. */ public void releaseAllImmediately() { if (DEBUG) Log.v(TAG, "releaseAllImmediately"); - ArrayList<String> keys = new ArrayList<>(mHeadsUpEntries.keySet()); - for (String key : keys) { - releaseImmediately(key); + Iterator<HeadsUpEntry> iterator = mHeadsUpEntries.values().iterator(); + while (iterator.hasNext()) { + HeadsUpEntry entry = iterator.next(); + iterator.remove(); + onHeadsUpEntryRemoved(entry); } } - public void releaseImmediately(String key) { + /** + * Pushes the given Heads Up notification down into the shade. + */ + public void releaseImmediately(@NonNull String key) { HeadsUpEntry headsUpEntry = getHeadsUpEntry(key); if (headsUpEntry == null) { return; @@ -334,11 +254,14 @@ public class HeadsUpManager implements ViewTreeObserver.OnComputeInternalInsetsL removeHeadsUpEntry(shadeEntry); } - public boolean isSnoozed(String packageName) { + /** + * Returns if the given notification is snoozed or not. + */ + public boolean isSnoozed(@NonNull String packageName) { final String key = snoozeKey(packageName, mUser); Long snoozedUntil = mSnoozedPackages.get(key); if (snoozedUntil != null) { - if (snoozedUntil > SystemClock.elapsedRealtime()) { + if (snoozedUntil > mClock.currentTimeMillis()) { if (DEBUG) Log.v(TAG, key + " snoozed"); return true; } @@ -347,33 +270,61 @@ public class HeadsUpManager implements ViewTreeObserver.OnComputeInternalInsetsL return false; } + /** + * Snoozes all current Heads Up Notifications. + */ public void snooze() { for (String key : mHeadsUpEntries.keySet()) { HeadsUpEntry entry = mHeadsUpEntries.get(key); String packageName = entry.entry.notification.getPackageName(); mSnoozedPackages.put(snoozeKey(packageName, mUser), - SystemClock.elapsedRealtime() + mSnoozeLengthMs); + mClock.currentTimeMillis() + mSnoozeLengthMs); } - mReleaseOnExpandFinish = true; } - private static String snoozeKey(String packageName, int user) { + private static String snoozeKey(@NonNull String packageName, int user) { return user + "," + packageName; } - private HeadsUpEntry getHeadsUpEntry(String key) { + protected HeadsUpEntry getHeadsUpEntry(@NonNull String key) { return mHeadsUpEntries.get(key); } - public NotificationData.Entry getEntry(String key) { - return mHeadsUpEntries.get(key).entry; + /** + * Returns the entry of given Heads Up Notification. + * + * @param key Key of heads up notification + */ + public NotificationData.Entry getEntry(@NonNull String key) { + HeadsUpEntry entry = mHeadsUpEntries.get(key); + return entry != null ? entry.entry : null; + } + + /** + * Returns the stream of all current Heads Up Notifications. + */ + @NonNull + public Stream<NotificationData.Entry> getAllEntries() { + return mHeadsUpEntries.values().stream().map(headsUpEntry -> headsUpEntry.entry); + } + + /** + * Returns the top Heads Up Notification, which appeares to show at first. + */ + @Nullable + public NotificationData.Entry getTopEntry() { + HeadsUpEntry topEntry = getTopHeadsUpEntry(); + return (topEntry != null) ? topEntry.entry : null; } - public Collection<HeadsUpEntry> getAllEntries() { - return mHeadsUpEntries.values(); + /** + * Returns if any heads up notification is available or not. + */ + public boolean hasHeadsUpNotifications() { + return !mHeadsUpEntries.isEmpty(); } - public HeadsUpEntry getTopEntry() { + protected HeadsUpEntry getTopHeadsUpEntry() { if (mHeadsUpEntries.isEmpty()) { return null; } @@ -387,56 +338,21 @@ public class HeadsUpManager implements ViewTreeObserver.OnComputeInternalInsetsL } /** - * Decides whether a click is invalid for a notification, i.e it has not been shown long enough - * that a user might have consciously clicked on it. - * - * @param key the key of the touched notification - * @return whether the touch is invalid and should be discarded + * Sets the current user. */ - public boolean shouldSwallowClick(String key) { - HeadsUpEntry entry = mHeadsUpEntries.get(key); - if (entry != null && mClock.currentTimeMillis() < entry.postTime) { - return true; - } - return false; - } - - public void onComputeInternalInsets(ViewTreeObserver.InternalInsetsInfo info) { - if (mIsExpanded || mBar.isBouncerShowing()) { - // The touchable region is always the full area when expanded - return; - } - if (mHasPinnedNotification) { - ExpandableNotificationRow topEntry = getTopEntry().entry.row; - if (topEntry.isChildInGroup()) { - final ExpandableNotificationRow groupSummary - = mGroupManager.getGroupSummary(topEntry.getStatusBarNotification()); - if (groupSummary != null) { - topEntry = groupSummary; - } - } - topEntry.getLocationOnScreen(mTmpTwoArray); - int minX = mTmpTwoArray[0]; - int maxX = mTmpTwoArray[0] + topEntry.getWidth(); - int maxY = topEntry.getIntrinsicHeight(); - - info.setTouchableInsets(ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION); - info.touchableRegion.set(minX, 0, maxX, maxY); - } else if (mHeadsUpGoingAway || mWaitingOnCollapseWhenGoingAway) { - info.setTouchableInsets(ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION); - info.touchableRegion.set(0, 0, mStatusBarWindowView.getWidth(), mStatusBarHeight); - } - } - public void setUser(int user) { mUser = user; } public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { pw.println("HeadsUpManager state:"); + dumpInternal(fd, pw, args); + } + + protected void dumpInternal(FileDescriptor fd, PrintWriter pw, String[] args) { pw.print(" mTouchAcceptanceDelay="); pw.println(mTouchAcceptanceDelay); pw.print(" mSnoozeLengthMs="); pw.println(mSnoozeLengthMs); - pw.print(" now="); pw.println(SystemClock.elapsedRealtime()); + pw.print(" now="); pw.println(mClock.currentTimeMillis()); pw.print(" mUser="); pw.println(mUser); for (HeadsUpEntry entry: mHeadsUpEntries.values()) { pw.print(" HeadsUpEntry="); pw.println(entry.entry); @@ -449,6 +365,9 @@ public class HeadsUpManager implements ViewTreeObserver.OnComputeInternalInsetsL } } + /** + * Returns if there are any pinned Heads Up Notifications or not. + */ public boolean hasPinnedHeadsUp() { return mHasPinnedNotification; } @@ -464,14 +383,8 @@ public class HeadsUpManager implements ViewTreeObserver.OnComputeInternalInsetsL } /** - * Notifies that a notification was swiped out and will be removed. - * - * @param key the notification key + * Unpins all pinned Heads Up Notifications. */ - public void addSwipedOutNotification(String key) { - mSwipedOutKeys.add(key); - } - public void unpinAll() { for (String key : mHeadsUpEntries.keySet()) { HeadsUpEntry entry = mHeadsUpEntries.get(key); @@ -481,60 +394,13 @@ public class HeadsUpManager implements ViewTreeObserver.OnComputeInternalInsetsL } } - public void onExpandingFinished() { - if (mReleaseOnExpandFinish) { - releaseAllImmediately(); - mReleaseOnExpandFinish = false; - } else { - for (NotificationData.Entry entry : mEntriesToRemoveAfterExpand) { - if (isHeadsUp(entry.key)) { - // Maybe the heads-up was removed already - removeHeadsUpEntry(entry); - } - } - } - mEntriesToRemoveAfterExpand.clear(); - } - - public void setTrackingHeadsUp(boolean trackingHeadsUp) { - mTrackingHeadsUp = trackingHeadsUp; - } - - public boolean isTrackingHeadsUp() { - return mTrackingHeadsUp; - } - - public void setIsExpanded(boolean isExpanded) { - if (isExpanded != mIsExpanded) { - mIsExpanded = isExpanded; - if (isExpanded) { - // make sure our state is sane - mWaitingOnCollapseWhenGoingAway = false; - mHeadsUpGoingAway = false; - updateTouchableRegionListener(); - } - } - } - /** - * @return the height of the top heads up notification when pinned. This is different from the - * intrinsic height, which also includes whether the notification is system expanded and - * is mainly used when dragging down from a heads up notification. + * Returns the value of the tracking-heads-up flag. See the doc of {@code setTrackingHeadsUp} as + * well. */ - public int getTopHeadsUpPinnedHeight() { - HeadsUpEntry topEntry = getTopEntry(); - if (topEntry == null || topEntry.entry == null) { - return 0; - } - ExpandableNotificationRow row = topEntry.entry.row; - if (row.isChildInGroup()) { - final ExpandableNotificationRow groupSummary - = mGroupManager.getGroupSummary(row.getStatusBarNotification()); - if (groupSummary != null) { - row = groupSummary; - } - } - return row.getPinnedHeadsUpHeight(); + public boolean isTrackingHeadsUp() { + // Might be implemented in subclass. + return false; } /** @@ -553,147 +419,67 @@ public class HeadsUpManager implements ViewTreeObserver.OnComputeInternalInsetsL } /** - * Set that we are exiting the headsUp pinned mode, but some notifications might still be - * animating out. This is used to keep the touchable regions in a sane state. + * Sets an entry to be expanded and therefore stick in the heads up area if it's pinned + * until it's collapsed again. */ - public void setHeadsUpGoingAway(boolean headsUpGoingAway) { - if (headsUpGoingAway != mHeadsUpGoingAway) { - mHeadsUpGoingAway = headsUpGoingAway; - if (!headsUpGoingAway) { - waitForStatusBarLayout(); - } - updateTouchableRegionListener(); - } - } - - /** - * We need to wait on the whole panel to collapse, before we can remove the touchable region - * listener. - */ - private void waitForStatusBarLayout() { - mWaitingOnCollapseWhenGoingAway = true; - mStatusBarWindowView.addOnLayoutChangeListener(new View.OnLayoutChangeListener() { - @Override - public void onLayoutChange(View v, int left, int top, int right, int bottom, - int oldLeft, - int oldTop, int oldRight, int oldBottom) { - if (mStatusBarWindowView.getHeight() <= mStatusBarHeight) { - mStatusBarWindowView.removeOnLayoutChangeListener(this); - mWaitingOnCollapseWhenGoingAway = false; - updateTouchableRegionListener(); - } - } - }); - } - - public static void setIsClickedNotification(View child, boolean clicked) { - child.setTag(TAG_CLICKED_NOTIFICATION, clicked ? true : null); - } - - public static boolean isClickedHeadsUpNotification(View child) { - Boolean clicked = (Boolean) child.getTag(TAG_CLICKED_NOTIFICATION); - return clicked != null && clicked; - } - - public void setRemoteInputActive(NotificationData.Entry entry, boolean remoteInputActive) { - HeadsUpEntry headsUpEntry = mHeadsUpEntries.get(entry.key); - if (headsUpEntry != null && headsUpEntry.remoteInputActive != remoteInputActive) { - headsUpEntry.remoteInputActive = remoteInputActive; - if (remoteInputActive) { - headsUpEntry.removeAutoRemovalCallbacks(); - } else { - headsUpEntry.updateEntry(false /* updatePostTime */); - } - } - } /** * Set an entry to be expanded and therefore stick in the heads up area if it's pinned * until it's collapsed again. */ - public void setExpanded(NotificationData.Entry entry, boolean expanded) { - HeadsUpEntry headsUpEntry = mHeadsUpEntries.get(entry.key); - if (headsUpEntry != null && headsUpEntry.expanded != expanded && entry.row.isPinned()) { - headsUpEntry.expanded = expanded; - if (expanded) { - headsUpEntry.removeAutoRemovalCallbacks(); - } else { - headsUpEntry.updateEntry(false /* updatePostTime */); - } - } - } - - @Override - public void onReorderingAllowed() { - mBar.getNotificationScrollLayout().setHeadsUpGoingAwayAnimationsAllowed(false); - for (NotificationData.Entry entry : mEntriesToRemoveWhenReorderingAllowed) { - if (isHeadsUp(entry.key)) { - // Maybe the heads-up was removed already - removeHeadsUpEntry(entry); - } + public void setExpanded(@NonNull NotificationData.Entry entry, boolean expanded) { + HeadsUpManager.HeadsUpEntry headsUpEntry = mHeadsUpEntries.get(entry.key); + if (headsUpEntry != null && entry.row.isPinned()) { + headsUpEntry.expanded(expanded); } - mEntriesToRemoveWhenReorderingAllowed.clear(); - mBar.getNotificationScrollLayout().setHeadsUpGoingAwayAnimationsAllowed(true); } - public void setVisualStabilityManager(VisualStabilityManager visualStabilityManager) { - mVisualStabilityManager = visualStabilityManager; + @NonNull + protected HeadsUpEntry createHeadsUpEntry() { + return new HeadsUpEntry(); } - public void setStatusBarState(int statusBarState) { - mStatusBarState = statusBarState; + protected void releaseHeadsUpEntry(@NonNull HeadsUpEntry entry) { + // Do nothing for HeadsUpEntry. } /** * This represents a notification and how long it is in a heads up mode. It also manages its * lifecycle automatically when created. */ - public class HeadsUpEntry implements Comparable<HeadsUpEntry> { - public NotificationData.Entry entry; + protected class HeadsUpEntry implements Comparable<HeadsUpEntry> { + @Nullable public NotificationData.Entry entry; public long postTime; - public long earliestRemovaltime; - private Runnable mRemoveHeadsUpRunnable; public boolean remoteInputActive; + public long earliestRemovaltime; public boolean expanded; - public void setEntry(final NotificationData.Entry entry) { + private Runnable mRemoveHeadsUpRunnable; + + public void setEntry(@Nullable final NotificationData.Entry entry) { + setEntry(entry, null); + } + + public void setEntry(@Nullable final NotificationData.Entry entry, + @Nullable Runnable removeHeadsUpRunnable) { this.entry = entry; + this.mRemoveHeadsUpRunnable = removeHeadsUpRunnable; // The actual post time will be just after the heads-up really slided in postTime = mClock.currentTimeMillis() + mTouchAcceptanceDelay; - mRemoveHeadsUpRunnable = new Runnable() { - @Override - public void run() { - if (!mVisualStabilityManager.isReorderingAllowed()) { - mEntriesToRemoveWhenReorderingAllowed.add(entry); - mVisualStabilityManager.addReorderingAllowedCallback(HeadsUpManager.this); - } else if (!mTrackingHeadsUp) { - removeHeadsUpEntry(entry); - } else { - mEntriesToRemoveAfterExpand.add(entry); - } - } - }; - updateEntry(); - } - - public void updateEntry() { - updateEntry(true); + updateEntry(true /* updatePostTime */); } public void updateEntry(boolean updatePostTime) { + if (DEBUG) Log.v(TAG, "updateEntry"); + long currentTime = mClock.currentTimeMillis(); earliestRemovaltime = currentTime + mMinimumDisplayTime; if (updatePostTime) { postTime = Math.max(postTime, currentTime); } removeAutoRemovalCallbacks(); - if (mEntriesToRemoveAfterExpand.contains(entry)) { - mEntriesToRemoveAfterExpand.remove(entry); - } - if (mEntriesToRemoveWhenReorderingAllowed.contains(entry)) { - mEntriesToRemoveWhenReorderingAllowed.remove(entry); - } + if (!isSticky()) { long finishTime = postTime + mHeadsUpNotificationDecay; long removeDelay = Math.max(finishTime - currentTime, mMinimumDisplayTime); @@ -707,7 +493,7 @@ public class HeadsUpManager implements ViewTreeObserver.OnComputeInternalInsetsL } @Override - public int compareTo(HeadsUpEntry o) { + public int compareTo(@NonNull HeadsUpEntry o) { boolean isPinned = entry.row.isPinned(); boolean otherPinned = o.entry.row.isPinned(); if (isPinned && !otherPinned) { @@ -734,26 +520,29 @@ public class HeadsUpManager implements ViewTreeObserver.OnComputeInternalInsetsL : -1; } - public void removeAutoRemovalCallbacks() { - mHandler.removeCallbacks(mRemoveHeadsUpRunnable); - } - - public boolean wasShownLongEnough() { - return earliestRemovaltime < mClock.currentTimeMillis(); - } - - public void removeAsSoonAsPossible() { - removeAutoRemovalCallbacks(); - mHandler.postDelayed(mRemoveHeadsUpRunnable, - earliestRemovaltime - mClock.currentTimeMillis()); + public void expanded(boolean expanded) { + this.expanded = expanded; } public void reset() { - removeAutoRemovalCallbacks(); entry = null; - mRemoveHeadsUpRunnable = null; expanded = false; remoteInputActive = false; + removeAutoRemovalCallbacks(); + mRemoveHeadsUpRunnable = null; + } + + public void removeAutoRemovalCallbacks() { + if (mRemoveHeadsUpRunnable != null) + mHandler.removeCallbacks(mRemoveHeadsUpRunnable); + } + + public void removeAsSoonAsPossible() { + if (mRemoveHeadsUpRunnable != null) { + removeAutoRemovalCallbacks(); + mHandler.postDelayed(mRemoveHeadsUpRunnable, + earliestRemovaltime - mClock.currentTimeMillis()); + } } } @@ -762,5 +551,4 @@ public class HeadsUpManager implements ViewTreeObserver.OnComputeInternalInsetsL return SystemClock.elapsedRealtime(); } } - } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpUtil.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpUtil.java new file mode 100644 index 000000000000..1e3c123cfbc6 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpUtil.java @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.policy; + +import android.view.View; + +import com.android.systemui.R; + +/** + * A class of utility static methods for heads up notifications. + */ +public final class HeadsUpUtil { + private static final int TAG_CLICKED_NOTIFICATION = R.id.is_clicked_heads_up_tag; + + /** + * Set the given view as clicked or not-clicked. + * @param view The view to be set the flag to. + * @param clicked True to set as clicked. False to not-clicked. + */ + public static void setIsClickedHeadsUpNotification(View view, boolean clicked) { + view.setTag(TAG_CLICKED_NOTIFICATION, clicked ? true : null); + } + + /** + * Check if the given view has the flag of "clicked notification" + * @param view The view to be checked. + * @return True if the view has clicked. False othrewise. + */ + public static boolean isClickedHeadsUpNotification(View view) { + Boolean clicked = (Boolean) view.getTag(TAG_CLICKED_NOTIFICATION); + return clicked != null && clicked; + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/AmbientState.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/AmbientState.java index 424858a86e58..d7a810eca02e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/AmbientState.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/AmbientState.java @@ -64,7 +64,7 @@ public class AmbientState { private boolean mPanelTracking; private boolean mExpansionChanging; private boolean mPanelFullWidth; - private Collection<HeadsUpManager.HeadsUpEntry> mPulsing; + private boolean mPulsing; private boolean mUnlockHintRunning; private boolean mQsCustomizerShowing; private int mIntrinsicPadding; @@ -315,23 +315,18 @@ public class AmbientState { } public boolean hasPulsingNotifications() { - return mPulsing != null; + return mPulsing; } - public void setPulsing(Collection<HeadsUpManager.HeadsUpEntry> hasPulsing) { + public void setPulsing(boolean hasPulsing) { mPulsing = hasPulsing; } public boolean isPulsing(NotificationData.Entry entry) { - if (mPulsing == null) { + if (!mPulsing || mHeadsUpManager == null) { return false; } - for (HeadsUpManager.HeadsUpEntry e : mPulsing) { - if (e.entry == entry) { - return true; - } - } - return false; + return mHeadsUpManager.getAllEntries().anyMatch(e -> (e == entry)); } public boolean isPanelTracking() { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java index ad8a0eb98ead..b28e1a9cafef 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java @@ -92,10 +92,11 @@ import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.notification.FakeShadowView; import com.android.systemui.statusbar.notification.NotificationUtils; import com.android.systemui.statusbar.notification.VisibilityLocationProvider; +import com.android.systemui.statusbar.phone.HeadsUpManagerPhone; import com.android.systemui.statusbar.phone.NotificationGroupManager; import com.android.systemui.statusbar.phone.StatusBar; import com.android.systemui.statusbar.phone.ScrimController; -import com.android.systemui.statusbar.policy.HeadsUpManager; +import com.android.systemui.statusbar.policy.HeadsUpUtil; import com.android.systemui.statusbar.policy.ScrollAdapter; import android.support.v4.graphics.ColorUtils; @@ -288,7 +289,7 @@ public class NotificationStackScrollLayout extends ViewGroup private HashSet<View> mClearOverlayViewsWhenFinished = new HashSet<>(); private HashSet<Pair<ExpandableNotificationRow, Boolean>> mHeadsUpChangeAnimations = new HashSet<>(); - private HeadsUpManager mHeadsUpManager; + private HeadsUpManagerPhone mHeadsUpManager; private boolean mTrackingHeadsUp; private ScrimController mScrimController; private boolean mForceNoOverlappingRendering; @@ -358,14 +359,14 @@ public class NotificationStackScrollLayout extends ViewGroup } }; private PorterDuffXfermode mSrcMode = new PorterDuffXfermode(PorterDuff.Mode.SRC); - private Collection<HeadsUpManager.HeadsUpEntry> mPulsing; + private boolean mPulsing; private boolean mDrawBackgroundAsSrc; private boolean mFadingOut; private boolean mParentNotFullyVisible; private boolean mGroupExpandedForMeasure; private boolean mScrollable; private View mForcedScroll; - private float mDarkAmount = 1.0f; + private float mDarkAmount = 0f; private static final Property<NotificationStackScrollLayout, Float> DARK_AMOUNT = new FloatProperty<NotificationStackScrollLayout>("darkAmount") { @Override @@ -523,9 +524,9 @@ public class NotificationStackScrollLayout extends ViewGroup setClipBounds(null); } else { float animProgress = Interpolators.FAST_OUT_SLOW_IN - .getInterpolation(mDarkAmount); + .getInterpolation(1f - mDarkAmount); float sidePaddingsProgress = Interpolators.FAST_OUT_SLOW_IN - .getInterpolation(mDarkAmount * 2); + .getInterpolation((1f - mDarkAmount) * 2); mTmpRect.set((int) MathUtils.lerp(darkLeft, lockScreenLeft, sidePaddingsProgress), (int) MathUtils.lerp(darkTop, lockScreenTop, animProgress), (int) MathUtils.lerp(darkRight, lockScreenRight, sidePaddingsProgress), @@ -548,7 +549,7 @@ public class NotificationStackScrollLayout extends ViewGroup } else { float alpha = BACKGROUND_ALPHA_DIMMED + (1 - BACKGROUND_ALPHA_DIMMED) * (1.0f - mDimAmount); - alpha *= mDarkAmount; + alpha *= 1f - mDarkAmount; // We need to manually blend in the background color int scrimColor = mScrimController.getBackgroundColor(); color = ColorUtils.blendARGB(scrimColor, mBgColor, alpha); @@ -689,7 +690,7 @@ public class NotificationStackScrollLayout extends ViewGroup } private void updateAlgorithmHeightAndPadding() { - if (mPulsing != null) { + if (mPulsing) { mTopPadding = mClockBottom; } else { mTopPadding = mAmbientState.isDark() ? mDarkTopPadding : mRegularTopPadding; @@ -919,6 +920,27 @@ public class NotificationStackScrollLayout extends ViewGroup } /** + * @return the height of the top heads up notification when pinned. This is different from the + * intrinsic height, which also includes whether the notification is system expanded and + * is mainly used when dragging down from a heads up notification. + */ + private int getTopHeadsUpPinnedHeight() { + NotificationData.Entry topEntry = mHeadsUpManager.getTopEntry(); + if (topEntry == null) { + return 0; + } + ExpandableNotificationRow row = topEntry.row; + if (row.isChildInGroup()) { + final ExpandableNotificationRow groupSummary + = mGroupManager.getGroupSummary(row.getStatusBarNotification()); + if (groupSummary != null) { + row = groupSummary; + } + } + return row.getPinnedHeadsUpHeight(); + } + + /** * @return the position from where the appear transition ends when expanding. * Measured in absolute height. */ @@ -929,7 +951,7 @@ public class NotificationStackScrollLayout extends ViewGroup int minNotificationsForShelf = 1; if (mTrackingHeadsUp || (mHeadsUpManager.hasPinnedHeadsUp() && !mAmbientState.isDark())) { - appearPosition = mHeadsUpManager.getTopHeadsUpPinnedHeight(); + appearPosition = getTopHeadsUpPinnedHeight(); minNotificationsForShelf = 2; } else { appearPosition = 0; @@ -1197,9 +1219,9 @@ public class NotificationStackScrollLayout extends ViewGroup if (slidingChild instanceof ExpandableNotificationRow) { ExpandableNotificationRow row = (ExpandableNotificationRow) slidingChild; if (!mIsExpanded && row.isHeadsUp() && row.isPinned() - && mHeadsUpManager.getTopEntry().entry.row != row + && mHeadsUpManager.getTopEntry().row != row && mGroupManager.getGroupSummary( - mHeadsUpManager.getTopEntry().entry.row.getStatusBarNotification()) + mHeadsUpManager.getTopEntry().row.getStatusBarNotification()) != row) { continue; } @@ -2119,7 +2141,7 @@ public class NotificationStackScrollLayout extends ViewGroup @Override public boolean hasPulsingNotifications() { - return mPulsing != null; + return mPulsing; } private void updateScrollability() { @@ -2304,8 +2326,9 @@ public class NotificationStackScrollLayout extends ViewGroup return; } + final boolean awake = mDarkAmount != 0 || mAmbientState.isDark(); mScrimController.setExcludedBackgroundArea( - mFadingOut || mParentNotFullyVisible || mDarkAmount != 1 || mIsClipped ? null + mFadingOut || mParentNotFullyVisible || awake || mIsClipped ? null : mCurrentBounds); invalidate(); } @@ -2751,7 +2774,7 @@ public class NotificationStackScrollLayout extends ViewGroup } private boolean isClickedHeadsUp(View child) { - return HeadsUpManager.isClickedHeadsUpNotification(child); + return HeadsUpUtil.isClickedHeadsUpNotification(child); } /** @@ -3858,17 +3881,12 @@ public class NotificationStackScrollLayout extends ViewGroup mDarkNeedsAnimation = true; mDarkAnimationOriginIndex = findDarkAnimationOriginIndex(touchWakeUpScreenLocation); mNeedsAnimation = true; - setDarkAmount(0.0f); - } else if (!dark) { - setDarkAmount(1.0f); - } - requestChildrenUpdate(); - if (dark) { - mScrimController.setExcludedBackgroundArea(null); } else { + setDarkAmount(dark ? 1f : 0f); updateBackground(); } - + requestChildrenUpdate(); + applyCurrentBackgroundBounds(); updateWillNotDraw(); updateContentHeight(); notifyHeightChangeListener(mShelf); @@ -3894,7 +3912,7 @@ public class NotificationStackScrollLayout extends ViewGroup } private void startBackgroundFadeIn() { - ObjectAnimator fadeAnimator = ObjectAnimator.ofFloat(this, DARK_AMOUNT, 0f, 1f); + ObjectAnimator fadeAnimator = ObjectAnimator.ofFloat(this, DARK_AMOUNT, mDarkAmount, 0f); fadeAnimator.setDuration(StackStateAnimator.ANIMATION_DURATION_WAKEUP); fadeAnimator.setInterpolator(Interpolators.ALPHA_IN); fadeAnimator.start(); @@ -4256,7 +4274,7 @@ public class NotificationStackScrollLayout extends ViewGroup mAnimationFinishedRunnables.add(runnable); } - public void setHeadsUpManager(HeadsUpManager headsUpManager) { + public void setHeadsUpManager(HeadsUpManagerPhone headsUpManager) { mHeadsUpManager = headsUpManager; mAmbientState.setHeadsUpManager(headsUpManager); } @@ -4324,8 +4342,8 @@ public class NotificationStackScrollLayout extends ViewGroup return mIsExpanded; } - public void setPulsing(Collection<HeadsUpManager.HeadsUpEntry> pulsing, int clockBottom) { - if (mPulsing == null && pulsing == null) { + public void setPulsing(boolean pulsing, int clockBottom) { + if (!mPulsing && !pulsing) { return; } mPulsing = pulsing; @@ -4463,7 +4481,7 @@ public class NotificationStackScrollLayout extends ViewGroup pw.println(String.format("[%s: pulsing=%s qsCustomizerShowing=%s visibility=%s" + " alpha:%f scrollY:%d]", this.getClass().getSimpleName(), - mPulsing != null ?"T":"f", + mPulsing ? "T":"f", mAmbientState.isQsCustomizerShowing() ? "T":"f", getVisibility() == View.VISIBLE ? "visible" : getVisibility() == View.GONE ? "gone" diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/ViewState.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/ViewState.java index 682b8493e913..04a7bd79c6ca 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/ViewState.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/ViewState.java @@ -30,7 +30,7 @@ import com.android.systemui.R; import com.android.systemui.statusbar.ExpandableView; import com.android.systemui.statusbar.notification.AnimatableProperty; import com.android.systemui.statusbar.notification.PropertyAnimator; -import com.android.systemui.statusbar.policy.HeadsUpManager; +import com.android.systemui.statusbar.policy.HeadsUpUtil; /** * A state of a view. This can be used to apply a set of view properties to a view with @@ -582,7 +582,7 @@ public class ViewState { animator.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { - HeadsUpManager.setIsClickedNotification(child, false); + HeadsUpUtil.setIsClickedHeadsUpNotification(child, false); child.setTag(TAG_ANIMATOR_TRANSLATION_Y, null); child.setTag(TAG_START_TRANSLATION_Y, null); child.setTag(TAG_END_TRANSLATION_Y, null); diff --git a/packages/SystemUI/src/com/android/systemui/volume/Events.java b/packages/SystemUI/src/com/android/systemui/volume/Events.java index e1376cad192a..2c85bb6e1644 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/Events.java +++ b/packages/SystemUI/src/com/android/systemui/volume/Events.java @@ -51,6 +51,7 @@ public class Events { public static final int EVENT_SUPPRESSOR_CHANGED = 14; // (component|string) (name|string) public static final int EVENT_MUTE_CHANGED = 15; // (stream|int) (muted|bool) public static final int EVENT_TOUCH_LEVEL_DONE = 16; // (stream|int) (level|bool) + public static final int EVENT_ZEN_CONFIG_CHANGED = 17; // (allow/disallow|string) private static final String[] EVENT_TAGS = { "show_dialog", @@ -70,6 +71,7 @@ public class Events { "suppressor_changed", "mute_changed", "touch_level_done", + "zen_mode_config_changed", }; public static final int DISMISS_REASON_UNKNOWN = 0; diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java index 2e23920a0798..9aee00ec145e 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java @@ -43,6 +43,7 @@ import android.os.RemoteException; import android.os.Vibrator; import android.provider.Settings; import android.service.notification.Condition; +import android.service.notification.ZenModeConfig; import android.util.ArrayMap; import android.util.Log; import android.view.accessibility.AccessibilityManager; @@ -58,7 +59,9 @@ import com.android.systemui.statusbar.phone.StatusBar; import java.io.FileDescriptor; import java.io.PrintWriter; +import java.util.ArrayList; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.Objects; @@ -75,7 +78,7 @@ public class VolumeDialogControllerImpl implements VolumeDialogController, Dumpa private static final int DYNAMIC_STREAM_START_INDEX = 100; private static final int VIBRATE_HINT_DURATION = 50; - private static final ArrayMap<Integer, Integer> STREAMS = new ArrayMap<>(); + static final ArrayMap<Integer, Integer> STREAMS = new ArrayMap<>(); static { STREAMS.put(AudioSystem.STREAM_ALARM, R.string.stream_alarm); STREAMS.put(AudioSystem.STREAM_BLUETOOTH_SCO, R.string.stream_bluetooth_sco); @@ -108,7 +111,10 @@ public class VolumeDialogControllerImpl implements VolumeDialogController, Dumpa private boolean mShowVolumeDialog; private boolean mShowSafetyWarning; private DeviceCallback mDeviceCallback = new DeviceCallback(); - private AudioDeviceInfo mConnectedDevice; + private final NotificationManager mNotificationManager; + @GuardedBy("mLock") + private List<AudioDeviceInfo> mConnectedDevices = new ArrayList<>(); + private Object mLock = new Object(); private boolean mDestroyed; private VolumePolicy mVolumePolicy; @@ -120,6 +126,8 @@ public class VolumeDialogControllerImpl implements VolumeDialogController, Dumpa public VolumeDialogControllerImpl(Context context) { mContext = context.getApplicationContext(); + mNotificationManager = (NotificationManager) mContext.getSystemService( + Context.NOTIFICATION_SERVICE); Events.writeEvent(mContext, Events.EVENT_COLLECTION_STARTED); mWorkerThread = new HandlerThread(VolumeDialogControllerImpl.class.getSimpleName()); mWorkerThread.start(); @@ -425,6 +433,7 @@ public class VolumeDialogControllerImpl implements VolumeDialogController, Dumpa } updateRingerModeExternalW(mAudio.getRingerMode()); updateZenModeW(); + updateZenConfig(); updateEffectsSuppressorW(mNoMan.getEffectsSuppressor()); mCallbacks.onStateChanged(mState); } @@ -510,6 +519,26 @@ public class VolumeDialogControllerImpl implements VolumeDialogController, Dumpa return true; } + private boolean updateZenConfig() { + final NotificationManager.Policy policy = mNotificationManager.getNotificationPolicy(); + boolean disallowAlarms = (policy.priorityCategories & NotificationManager.Policy + .PRIORITY_CATEGORY_ALARMS) == 0; + boolean disallowMedia = (policy.priorityCategories & NotificationManager.Policy + .PRIORITY_CATEGORY_MEDIA_SYSTEM_OTHER) == 0; + boolean disallowRinger = ZenModeConfig.areAllPriorityOnlyNotificationZenSoundsMuted(policy); + if (mState.disallowAlarms == disallowAlarms && mState.disallowMedia == disallowMedia + && mState.disallowRinger == disallowRinger) { + return false; + } + mState.disallowAlarms = disallowAlarms; + mState.disallowMedia = disallowMedia; + mState.disallowRinger = disallowRinger; + Events.writeEvent(mContext, Events.EVENT_ZEN_CONFIG_CHANGED, "disallowAlarms=" + + disallowAlarms + " disallowMedia=" + disallowMedia + " disallowRinger=" + + disallowRinger); + return true; + } + private boolean updateRingerModeExternalW(int rm) { if (rm == mState.ringerModeExternal) return false; mState.ringerModeExternal = rm; @@ -850,6 +879,10 @@ public class VolumeDialogControllerImpl implements VolumeDialogController, Dumpa if (ZEN_MODE_URI.equals(uri)) { changed = updateZenModeW(); } + if (ZEN_MODE_CONFIG_URI.equals(uri)) { + changed |= updateZenConfig(); + } + if (changed) { mCallbacks.onStateChanged(mState); } @@ -1026,26 +1059,25 @@ public class VolumeDialogControllerImpl implements VolumeDialogController, Dumpa protected final class DeviceCallback extends AudioDeviceCallback { public void onAudioDevicesAdded(AudioDeviceInfo[] addedDevices) { - for (AudioDeviceInfo info : addedDevices) { - if (info.isSink() - && (info.getType() == AudioDeviceInfo.TYPE_BLUETOOTH_A2DP - || info.getType() == AudioDeviceInfo.TYPE_BLUETOOTH_SCO)) { - mConnectedDevice = info; - mCallbacks.onConnectedDeviceChanged(info.getProductName().toString()); + synchronized (mLock) { + for (AudioDeviceInfo info : addedDevices) { + if (info.isSink() + && (info.getType() == AudioDeviceInfo.TYPE_BLUETOOTH_A2DP + || info.getType() == AudioDeviceInfo.TYPE_BLUETOOTH_SCO)) { + mConnectedDevices.add(info); + mCallbacks.onConnectedDeviceChanged(info.getProductName().toString()); + } } } } public void onAudioDevicesRemoved(AudioDeviceInfo[] removedDevices) { - if (mConnectedDevice == null) { - mCallbacks.onConnectedDeviceChanged(null); - return; - } - for (AudioDeviceInfo info : removedDevices) { - if (info.isSink() == mConnectedDevice.isSink() - && Objects.equals(info.getProductName(), mConnectedDevice.getProductName()) - && info.getType() == mConnectedDevice.getType()) { - mConnectedDevice = null; + synchronized (mLock) { + for (AudioDeviceInfo info : removedDevices) { + mConnectedDevices.remove(info); + } + + if (mConnectedDevices.size() == 0) { mCallbacks.onConnectedDeviceChanged(null); } } diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java index 3e13ddb1028a..001a582297af 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java +++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java @@ -63,7 +63,6 @@ import android.view.ViewGroup; import android.view.Window; import android.view.WindowManager; import android.view.accessibility.AccessibilityEvent; -import android.view.accessibility.AccessibilityManager; import android.view.accessibility.AccessibilityManager.AccessibilityStateChangeListener; import android.view.animation.DecelerateInterpolator; import android.widget.ImageButton; @@ -79,6 +78,7 @@ import com.android.systemui.plugins.VolumeDialog; import com.android.systemui.plugins.VolumeDialogController; import com.android.systemui.plugins.VolumeDialogController.State; import com.android.systemui.plugins.VolumeDialogController.StreamState; +import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper; import java.io.PrintWriter; import java.util.ArrayList; @@ -111,7 +111,7 @@ public class VolumeDialogImpl implements VolumeDialog { private ConfigurableTexts mConfigurableTexts; private final SparseBooleanArray mDynamic = new SparseBooleanArray(); private final KeyguardManager mKeyguard; - private final AccessibilityManager mAccessibilityMgr; + private final AccessibilityManagerWrapper mAccessibilityMgr; private final Object mSafetyWarningLock = new Object(); private final Object mOutputChooserLock = new Object(); private final Accessibility mAccessibility = new Accessibility(); @@ -134,8 +134,7 @@ public class VolumeDialogImpl implements VolumeDialog { mContext = new ContextThemeWrapper(context, com.android.systemui.R.style.qs_theme); mController = Dependency.get(VolumeDialogController.class); mKeyguard = (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE); - mAccessibilityMgr = - (AccessibilityManager) mContext.getSystemService(Context.ACCESSIBILITY_SERVICE); + mAccessibilityMgr = Dependency.get(AccessibilityManagerWrapper.class); mActiveSliderTint = ColorStateList.valueOf(Utils.getColorAccent(mContext)); mInactiveSliderTint = loadColorStateList(R.color.volume_slider_inactive); } @@ -231,6 +230,10 @@ public class VolumeDialogImpl implements VolumeDialog { initRingerH(); } + protected ViewGroup getDialogView() { + return mDialogView; + } + private ColorStateList loadColorStateList(int colorResId) { return ColorStateList.valueOf(mContext.getColor(colorResId)); } @@ -258,6 +261,7 @@ public class VolumeDialogImpl implements VolumeDialog { private void addRow(int stream, int iconRes, int iconMuteRes, boolean important, boolean defaultStream, boolean dynamic) { + if (D.BUG) Slog.d(TAG, "Adding row for stream " + stream); VolumeRow row = new VolumeRow(); initRow(row, stream, iconRes, iconMuteRes, important, defaultStream); int rowSize; @@ -621,7 +625,7 @@ public class VolumeDialogImpl implements VolumeDialog { } } - private void onStateChangedH(State state) { + protected void onStateChangedH(State state) { mState = state; mDynamic.clear(); // add any new dynamic rows @@ -669,10 +673,14 @@ public class VolumeDialogImpl implements VolumeDialog { && mState.ringerModeInternal == AudioManager.RINGER_MODE_VIBRATE; final boolean isRingSilent = isRingStream && mState.ringerModeInternal == AudioManager.RINGER_MODE_SILENT; + final boolean isZenPriorityOnly = mState.zenMode == Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS; final boolean isZenAlarms = mState.zenMode == Global.ZEN_MODE_ALARMS; final boolean isZenNone = mState.zenMode == Global.ZEN_MODE_NO_INTERRUPTIONS; final boolean zenMuted = isZenAlarms ? (isRingStream || isSystemStream) : isZenNone ? (isRingStream || isSystemStream || isAlarmStream || isMusicStream) + : isZenPriorityOnly ? ((isAlarmStream && mState.disallowAlarms) || + (isMusicStream && mState.disallowMedia) || + (isRingStream && mState.disallowRinger)) : false; // update slider max @@ -890,7 +898,7 @@ public class VolumeDialogImpl implements VolumeDialog { return ss.remoteLabel; } try { - return mContext.getString(ss.name); + return mContext.getResources().getString(ss.name); } catch (Resources.NotFoundException e) { Slog.e(TAG, "Can't find translation for stream " + ss); return ""; diff --git a/packages/SystemUI/tests/Android.mk b/packages/SystemUI/tests/Android.mk index 066cfe5862ef..936ff512b8cd 100644 --- a/packages/SystemUI/tests/Android.mk +++ b/packages/SystemUI/tests/Android.mk @@ -50,8 +50,8 @@ LOCAL_STATIC_ANDROID_LIBRARIES := \ android-slices-core \ android-slices-view \ android-slices-builders \ - apptoolkit-arch-core-runtime \ - apptoolkit-lifecycle-extensions \ + android-arch-core-runtime \ + android-arch-lifecycle-extensions \ LOCAL_STATIC_JAVA_LIBRARIES := \ metrics-helper-lib \ diff --git a/packages/SystemUI/tests/src/com/android/systemui/RoundedCornersTest.java b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java index 2a447715fb6b..2f05b06ae0b0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/RoundedCornersTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java @@ -14,9 +14,13 @@ package com.android.systemui; +import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY; + import static com.android.systemui.tuner.TunablePadding.FLAG_END; import static com.android.systemui.tuner.TunablePadding.FLAG_START; +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyString; @@ -29,6 +33,7 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.app.Fragment; +import android.content.res.Configuration; import android.support.test.filters.SmallTest; import android.testing.AndroidTestingRunner; import android.view.Display; @@ -36,7 +41,7 @@ import android.view.View; import android.view.WindowManager; import com.android.systemui.R.dimen; -import com.android.systemui.RoundedCorners.TunablePaddingTagListener; +import com.android.systemui.ScreenDecorations.TunablePaddingTagListener; import com.android.systemui.fragments.FragmentHostManager; import com.android.systemui.fragments.FragmentService; import com.android.systemui.statusbar.phone.StatusBar; @@ -51,9 +56,9 @@ import org.junit.runner.RunWith; @RunWith(AndroidTestingRunner.class) @SmallTest -public class RoundedCornersTest extends SysuiTestCase { +public class ScreenDecorationsTest extends SysuiTestCase { - private RoundedCorners mRoundedCorners; + private ScreenDecorations mScreenDecorations; private StatusBar mStatusBar; private WindowManager mWindowManager; private FragmentService mFragmentService; @@ -81,20 +86,22 @@ public class RoundedCornersTest extends SysuiTestCase { mTunerService = mDependency.injectMockDependency(TunerService.class); - mRoundedCorners = new RoundedCorners(); - mRoundedCorners.mContext = mContext; - mRoundedCorners.mComponents = mContext.getComponents(); + mScreenDecorations = new ScreenDecorations(); + mScreenDecorations.mContext = mContext; + mScreenDecorations.mComponents = mContext.getComponents(); mTunablePaddingService = mDependency.injectMockDependency(TunablePaddingService.class); } @Test - public void testNoRounding() { + public void testNoRounding_NoCutout() { + mContext.getOrCreateTestableResources().addOverride( + com.android.internal.R.bool.config_fillMainBuiltInDisplayCutout, false); mContext.getOrCreateTestableResources().addOverride(dimen.rounded_corner_radius, 0); mContext.getOrCreateTestableResources() .addOverride(dimen.rounded_corner_content_padding, 0); - mRoundedCorners.start(); + mScreenDecorations.start(); // No views added. verify(mWindowManager, never()).addView(any(), any()); // No Fragments watched. @@ -105,11 +112,13 @@ public class RoundedCornersTest extends SysuiTestCase { @Test public void testRounding() { + mContext.getOrCreateTestableResources().addOverride( + com.android.internal.R.bool.config_fillMainBuiltInDisplayCutout, false); mContext.getOrCreateTestableResources().addOverride(dimen.rounded_corner_radius, 20); mContext.getOrCreateTestableResources() .addOverride(dimen.rounded_corner_content_padding, 20); - mRoundedCorners.start(); + mScreenDecorations.start(); // Add 2 windows for rounded corners (top and bottom). verify(mWindowManager, times(2)).addView(any(), any()); @@ -122,6 +131,44 @@ public class RoundedCornersTest extends SysuiTestCase { } @Test + public void testCutout() { + mContext.getOrCreateTestableResources().addOverride( + com.android.internal.R.bool.config_fillMainBuiltInDisplayCutout, true); + mContext.getOrCreateTestableResources().addOverride(dimen.rounded_corner_radius, 0); + mContext.getOrCreateTestableResources() + .addOverride(dimen.rounded_corner_content_padding, 0); + + mScreenDecorations.start(); + // Add 2 windows for rounded corners (top and bottom). + verify(mWindowManager, times(2)).addView(any(), any()); + } + + @Test + public void testDelayedCutout() { + mContext.getOrCreateTestableResources().addOverride( + com.android.internal.R.bool.config_fillMainBuiltInDisplayCutout, false); + mContext.getOrCreateTestableResources().addOverride(dimen.rounded_corner_radius, 0); + mContext.getOrCreateTestableResources() + .addOverride(dimen.rounded_corner_content_padding, 0); + + mScreenDecorations.start(); + + mContext.getOrCreateTestableResources().addOverride( + com.android.internal.R.bool.config_fillMainBuiltInDisplayCutout, true); + mScreenDecorations.onConfigurationChanged(new Configuration()); + + // Add 2 windows for rounded corners (top and bottom). + verify(mWindowManager, times(2)).addView(any(), any()); + } + + @Test + public void hasRoundedCornerOverlayFlagSet() { + assertThat(mScreenDecorations.getWindowLayoutParams().privateFlags + & PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY, + is(PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY)); + } + + @Test public void testPaddingTagListener() { TunablePaddingTagListener tagListener = new TunablePaddingTagListener(14, 5); View v = mock(View.class); @@ -136,7 +183,7 @@ public class RoundedCornersTest extends SysuiTestCase { // Trigger callback and verify we get a TunablePadding created. tagListener.onFragmentViewCreated(null, f); - verify(mTunablePaddingService).add(eq(child), eq(RoundedCorners.PADDING), eq(14), + verify(mTunablePaddingService).add(eq(child), eq(ScreenDecorations.PADDING), eq(14), eq(FLAG_START | FLAG_END)); // Call again and verify destroy is called. diff --git a/packages/SystemUI/tests/src/com/android/systemui/chooser/ChooserHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/chooser/ChooserHelperTest.java deleted file mode 100644 index 8e0426a15eee..000000000000 --- a/packages/SystemUI/tests/src/com/android/systemui/chooser/ChooserHelperTest.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.chooser; - -import android.app.Activity; -import android.app.ActivityManager; -import android.content.Intent; -import android.os.Binder; -import android.support.test.runner.AndroidJUnit4; -import android.test.suitebuilder.annotation.SmallTest; -import android.util.Log; - -import com.android.systemui.chooser.ChooserHelper; -import com.android.systemui.SysuiTestCase; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; - -import static org.mockito.Mockito.any; -import static org.mockito.Mockito.anyBoolean; -import static org.mockito.Mockito.anyFloat; -import static org.mockito.Mockito.anyInt; -import static org.mockito.Mockito.anyString; -import static org.mockito.Mockito.eq; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -@SmallTest -@RunWith(AndroidJUnit4.class) -public class ChooserHelperTest extends SysuiTestCase { - - @Test - public void testOnChoose_CallsStartActivityAsCallerWithToken() { - final Intent intent = new Intent(); - final Binder token = new Binder(); - intent.putExtra(ActivityManager.EXTRA_PERMISSION_TOKEN, token); - - final Activity mockActivity = mock(Activity.class); - when(mockActivity.getIntent()).thenReturn(intent); - - ChooserHelper.onChoose(mockActivity); - verify(mockActivity, times(1)).startActivityAsCaller( - any(), any(), eq(token), anyBoolean(), anyInt()); - } -} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationTestHelper.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationTestHelper.java index 6e7477fbac38..f3c1171f650c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationTestHelper.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationTestHelper.java @@ -32,6 +32,7 @@ import com.android.systemui.statusbar.notification.AboveShelfChangedListener; import com.android.systemui.statusbar.notification.AboveShelfObserver; import com.android.systemui.statusbar.notification.InflationException; import com.android.systemui.statusbar.notification.NotificationInflaterTest; +import com.android.systemui.statusbar.phone.HeadsUpManagerPhone; import com.android.systemui.statusbar.phone.NotificationGroupManager; import com.android.systemui.statusbar.policy.HeadsUpManager; @@ -51,7 +52,7 @@ public class NotificationTestHelper { public NotificationTestHelper(Context context) { mContext = context; mInstrumentation = InstrumentationRegistry.getInstrumentation(); - mHeadsUpManager = new HeadsUpManager(mContext, null, mGroupManager); + mHeadsUpManager = new HeadsUpManagerPhone(mContext, null, mGroupManager, null, null); } public ExpandableNotificationRow createRow() throws Exception { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhoneTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhoneTest.java new file mode 100644 index 000000000000..28f941779043 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhoneTest.java @@ -0,0 +1,126 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.phone; + +import android.app.ActivityManager; +import android.app.Notification; +import android.content.Context; +import android.os.Handler; +import android.os.Looper; +import android.os.UserHandle; +import android.view.View; +import android.service.notification.StatusBarNotification; +import android.support.test.filters.SmallTest; +import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; + +import com.android.systemui.R; +import com.android.systemui.SysuiTestCase; +import com.android.systemui.statusbar.ExpandableNotificationRow; +import com.android.systemui.statusbar.NotificationData; +import com.android.systemui.statusbar.StatusBarIconView; +import com.android.systemui.statusbar.notification.VisualStabilityManager; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; + +import static junit.framework.Assert.assertNull; +import static junit.framework.Assert.assertTrue; +import static junit.framework.Assert.assertFalse; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +@SmallTest +@RunWith(AndroidTestingRunner.class) +@TestableLooper.RunWithLooper +public class HeadsUpManagerPhoneTest extends SysuiTestCase { + @Rule public MockitoRule rule = MockitoJUnit.rule(); + + private static final String TEST_PACKAGE_NAME = "test"; + private static final int TEST_UID = 0; + + private HeadsUpManagerPhone mHeadsUpManager; + + private NotificationData.Entry mEntry; + private StatusBarNotification mSbn; + + private final Handler mHandler = new Handler(Looper.getMainLooper()); + + @Mock private NotificationGroupManager mGroupManager; + @Mock private View mStatusBarWindowView; + @Mock private StatusBar mBar; + @Mock private ExpandableNotificationRow mRow; + @Mock private VisualStabilityManager mVSManager; + + @Before + public void setUp() { + when(mVSManager.isReorderingAllowed()).thenReturn(true); + + mHeadsUpManager = new HeadsUpManagerPhone(mContext, mStatusBarWindowView, mGroupManager, mBar, mVSManager); + + Notification.Builder n = new Notification.Builder(mContext, "") + .setSmallIcon(R.drawable.ic_person) + .setContentTitle("Title") + .setContentText("Text"); + mSbn = new StatusBarNotification(TEST_PACKAGE_NAME, TEST_PACKAGE_NAME, 0, null, TEST_UID, + 0, n.build(), new UserHandle(ActivityManager.getCurrentUser()), null, 0); + + mEntry = new NotificationData.Entry(mSbn); + mEntry.row = mRow; + mEntry.expandedIcon = mock(StatusBarIconView.class); + } + + @Test + public void testBasicOperations() { + // Check the initial state. + assertNull(mHeadsUpManager.getEntry(mEntry.key)); + assertNull(mHeadsUpManager.getTopEntry()); + assertEquals(0, mHeadsUpManager.getAllEntries().count()); + assertFalse(mHeadsUpManager.hasHeadsUpNotifications()); + + // Add a notification. + mHeadsUpManager.showNotification(mEntry); + + assertEquals(mEntry, mHeadsUpManager.getEntry(mEntry.key)); + assertEquals(mEntry, mHeadsUpManager.getTopEntry()); + assertEquals(1, mHeadsUpManager.getAllEntries().count()); + assertTrue(mHeadsUpManager.hasHeadsUpNotifications()); + + // Update the notification. + mHeadsUpManager.updateNotification(mEntry, false); + + assertEquals(mEntry, mHeadsUpManager.getEntry(mEntry.key)); + assertEquals(mEntry, mHeadsUpManager.getTopEntry()); + assertEquals(1, mHeadsUpManager.getAllEntries().count()); + assertTrue(mHeadsUpManager.hasHeadsUpNotifications()); + + // Remove but defer, since the notification is visible on display. + mHeadsUpManager.removeNotification(mEntry.key, false); + + assertEquals(mEntry, mHeadsUpManager.getEntry(mEntry.key)); + assertEquals(mEntry, mHeadsUpManager.getTopEntry()); + assertEquals(1, mHeadsUpManager.getAllEntries().count()); + assertTrue(mHeadsUpManager.hasHeadsUpNotifications()); + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java index bdf9b1f6da9e..31442af5a04c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java @@ -86,8 +86,8 @@ import com.android.systemui.statusbar.NotificationViewHierarchyManager; import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.notification.ActivityLaunchAnimator; import com.android.systemui.statusbar.notification.VisualStabilityManager; +import com.android.systemui.statusbar.phone.HeadsUpManagerPhone; import com.android.systemui.statusbar.policy.DeviceProvisionedController; -import com.android.systemui.statusbar.policy.HeadsUpManager; import com.android.systemui.statusbar.policy.KeyguardMonitor; import com.android.systemui.statusbar.policy.KeyguardMonitorImpl; import com.android.systemui.statusbar.stack.NotificationStackScrollLayout; @@ -110,7 +110,7 @@ public class StatusBarTest extends SysuiTestCase { @Mock private UnlockMethodCache mUnlockMethodCache; @Mock private KeyguardIndicationController mKeyguardIndicationController; @Mock private NotificationStackScrollLayout mStackScroller; - @Mock private HeadsUpManager mHeadsUpManager; + @Mock private HeadsUpManagerPhone mHeadsUpManager; @Mock private SystemServicesProxy mSystemServicesProxy; @Mock private NotificationPanelView mNotificationPanelView; @Mock private IStatusBarService mBarService; @@ -588,7 +588,7 @@ public class StatusBarTest extends SysuiTestCase { static class TestableStatusBar extends StatusBar { public TestableStatusBar(StatusBarKeyguardViewManager man, UnlockMethodCache unlock, KeyguardIndicationController key, - NotificationStackScrollLayout stack, HeadsUpManager hum, + NotificationStackScrollLayout stack, HeadsUpManagerPhone hum, PowerManager pm, NotificationPanelView panelView, IStatusBarService barService, NotificationListener notificationListener, NotificationLogger notificationLogger, @@ -650,7 +650,7 @@ public class StatusBarTest extends SysuiTestCase { public void setUpForTest(NotificationPresenter presenter, NotificationListContainer listContainer, Callback callback, - HeadsUpManager headsUpManager, + HeadsUpManagerPhone headsUpManager, NotificationData notificationData) { super.setUpWithPresenter(presenter, listContainer, callback, headsUpManager); mNotificationData = notificationData; diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java new file mode 100644 index 000000000000..2d28c9f214fb --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java @@ -0,0 +1,114 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.volume; + +import static com.android.systemui.volume.Events.DISMISS_REASON_UNKNOWN; +import static com.android.systemui.volume.Events.SHOW_REASON_UNKNOWN; +import static com.android.systemui.volume.VolumeDialogControllerImpl.STREAMS; + +import static junit.framework.Assert.assertTrue; + +import android.app.KeyguardManager; +import android.media.AudioManager; +import android.support.test.filters.SmallTest; +import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; +import android.text.TextUtils; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; + +import com.android.systemui.SysuiTestCase; +import com.android.systemui.plugins.VolumeDialogController; +import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.function.Predicate; + +@SmallTest +@RunWith(AndroidTestingRunner.class) +@TestableLooper.RunWithLooper +public class VolumeDialogImplTest extends SysuiTestCase { + + VolumeDialogImpl mDialog; + + @Mock + VolumeDialogController mController; + + @Mock + KeyguardManager mKeyguard; + + @Mock + AccessibilityManagerWrapper mAccessibilityMgr; + + @Before + public void setup() throws Exception { + MockitoAnnotations.initMocks(this); + + mController = mDependency.injectMockDependency(VolumeDialogController.class); + mAccessibilityMgr = mDependency.injectMockDependency(AccessibilityManagerWrapper.class); + getContext().addMockSystemService(KeyguardManager.class, mKeyguard); + + mDialog = new VolumeDialogImpl(getContext()); + mDialog.init(0, null); + VolumeDialogController.State state = new VolumeDialogController.State(); + for (int i = AudioManager.STREAM_VOICE_CALL; i <= AudioManager.STREAM_ACCESSIBILITY; i++) { + VolumeDialogController.StreamState ss = new VolumeDialogController.StreamState(); + ss.name = STREAMS.get(i); + state.states.append(i, ss); + } + mDialog.onStateChangedH(state); + } + + private void navigateViews(View view, Predicate<View> condition) { + if (view instanceof ViewGroup) { + ViewGroup viewGroup = (ViewGroup) view; + for (int i = 0; i < viewGroup.getChildCount(); i++) { + navigateViews(viewGroup.getChildAt(i), condition); + } + } else { + String resourceName = null; + try { + resourceName = getContext().getResources().getResourceName(view.getId()); + } catch (Exception e) {} + assertTrue("View " + resourceName != null ? resourceName : view.getId() + + " failed test", condition.test(view)); + } + } + + @Test + public void testContentDescriptions() { + mDialog.show(SHOW_REASON_UNKNOWN); + ViewGroup dialog = mDialog.getDialogView(); + + navigateViews(dialog, view -> { + if (view instanceof ImageView) { + return !TextUtils.isEmpty(view.getContentDescription()); + } else { + return true; + } + }); + + mDialog.dismiss(DISMISS_REASON_UNKNOWN); + } + +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/ZenModePanelTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/ZenModePanelTest.java index 0fdbfd1944fe..4ab2063196d4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/volume/ZenModePanelTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/volume/ZenModePanelTest.java @@ -30,15 +30,18 @@ import android.service.notification.ZenModeConfig; import android.support.test.annotation.UiThreadTest; import android.support.test.filters.SmallTest; import android.support.test.runner.AndroidJUnit4; +import android.test.FlakyTest; import android.view.LayoutInflater; import com.android.systemui.SysuiTestCase; import com.android.systemui.statusbar.policy.ZenModeController; import org.junit.Before; +import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; +@Ignore @SmallTest @RunWith(AndroidJUnit4.class) public class ZenModePanelTest extends SysuiTestCase { diff --git a/proto/src/gnss.proto b/proto/src/gnss.proto index c54ddadd07df..016839232255 100644 --- a/proto/src/gnss.proto +++ b/proto/src/gnss.proto @@ -42,4 +42,20 @@ message GnssLog { // Standard deviation of top 4 average CN0 (dB-Hz) optional double standard_deviation_top_four_average_cn0_db_hz = 11; -}
\ No newline at end of file + + // Power metrics + optional PowerMetrics power_metrics = 12; +} + +// Power metrics +message PowerMetrics { + + // Duration of power log (ms) + optional int64 logging_duration_ms = 1; + + // Energy consumed (mAh) + optional double energy_consumed_mah = 2; + + // Time spent in signal quality level (ms) + repeated int64 time_in_signal_quality_level_ms = 3; +} diff --git a/proto/src/metrics_constants.proto b/proto/src/metrics_constants.proto index 03dfd46ea8d1..7539d8847c2d 100644 --- a/proto/src/metrics_constants.proto +++ b/proto/src/metrics_constants.proto @@ -5168,6 +5168,11 @@ message MetricsEvent { // OS: P ROTATION_SUGGESTION_SHOWN = 1288; + // An autofill service was bound using an unofficial(but still supported) permission. + // Package: Package of the autofill service + // OS: P + AUTOFILL_INVALID_PERMISSION = 1289; + // ---- 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 0e2ca14c8417..2b73b33fffd3 100644 --- a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java +++ b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java @@ -617,6 +617,23 @@ public final class AutofillManagerService extends SystemService { } @Override + public String getUserDataId() throws RemoteException { + final int userId = UserHandle.getCallingUserId(); + + synchronized (mLock) { + final AutofillManagerServiceImpl service = peekServiceForUserLocked(userId); + if (service != null) { + final UserData userData = service.getUserData(getCallingUid()); + return userData == null ? null : userData.getId(); + } else if (sVerbose) { + Slog.v(TAG, "getUserDataId(): no service for " + userId); + } + } + + return null; + } + + @Override public void setUserData(UserData userData) throws RemoteException { final int userId = UserHandle.getCallingUserId(); diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java index 6b44fa5e37fb..6108afa195ea 100644 --- a/services/autofill/java/com/android/server/autofill/Session.java +++ b/services/autofill/java/com/android/server/autofill/Session.java @@ -111,7 +111,7 @@ import java.util.concurrent.atomic.AtomicInteger; * until the user authenticates or it times out. */ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState.Listener, - AutoFillUI.AutoFillUiCallback { + AutoFillUI.AutoFillUiCallback, ValueFinder { private static final String TAG = "AutofillSession"; private static final String EXTRA_REQUEST_ID = "android.service.autofill.extra.REQUEST_ID"; @@ -310,43 +310,56 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState return ids; } - /** - * Gets the value of a field, using either the {@code viewStates} or the {@code mContexts}, or - * {@code null} when not found on either of them. - */ + @Override @Nullable - private String getValueAsString(@NonNull AutofillId id) { - AutofillValue value = null; + public String findByAutofillId(@NonNull AutofillId id) { synchronized (mLock) { - final ViewState state = mViewStates.get(id); - if (state == null) { - if (sDebug) Slog.d(TAG, "getValue(): no view state for " + id); - return null; - } - value = state.getCurrentValue(); - if (value == null) { - if (sDebug) Slog.d(TAG, "getValue(): no current value for " + id); - value = getValueFromContextsLocked(id); - } - } - if (value != null) { - if (value.isText()) { - return value.getTextValue().toString(); - } - if (value.isList()) { - final CharSequence[] options = getAutofillOptionsFromContextsLocked(id); - if (options != null) { - final int index = value.getListValue(); - final CharSequence option = options[index]; - return option != null ? option.toString() : null; - } else { - Slog.w(TAG, "getValueAsString(): no autofill options for id " + id); + AutofillValue value = findValueLocked(id); + if (value != null) { + if (value.isText()) { + return value.getTextValue().toString(); + } + + if (value.isList()) { + final CharSequence[] options = getAutofillOptionsFromContextsLocked(id); + if (options != null) { + final int index = value.getListValue(); + final CharSequence option = options[index]; + return option != null ? option.toString() : null; + } else { + Slog.w(TAG, "findByAutofillId(): no autofill options for id " + id); + } } } } return null; } + @Override + public AutofillValue findRawValueByAutofillId(AutofillId id) { + synchronized (mLock) { + return findValueLocked(id); + } + } + + /** + * <p>Gets the value of a field, using either the {@code viewStates} or the {@code mContexts}, + * or {@code null} when not found on either of them. + */ + private AutofillValue findValueLocked(@NonNull AutofillId id) { + final ViewState state = mViewStates.get(id); + if (state == null) { + if (sDebug) Slog.d(TAG, "findValueLocked(): no view state for " + id); + return null; + } + AutofillValue value = state.getCurrentValue(); + if (value == null) { + if (sDebug) Slog.d(TAG, "findValueLocked(): no current value for " + id); + value = getValueFromContextsLocked(id); + } + return value; + } + /** * Updates values of the nodes in the context's structure so that: * - proper node is focused @@ -1355,14 +1368,12 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState if (sDebug) { Slog.d(TAG, "at least one field changed, validate fields for save UI"); } - final ValueFinder valueFinder = (id) -> {return getValueAsString(id);}; - final InternalValidator validator = saveInfo.getValidator(); if (validator != null) { final LogMaker log = newLogMaker(MetricsEvent.AUTOFILL_SAVE_VALIDATION); boolean isValid; try { - isValid = validator.isValid(valueFinder); + isValid = validator.isValid(this); if (sDebug) Slog.d(TAG, validator + " returned " + isValid); log.setType(isValid ? MetricsEvent.TYPE_SUCCESS @@ -1404,10 +1415,13 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState } final AutofillValue datasetValue = datasetValues.get(id); if (!currentValue.equals(datasetValue)) { - if (sDebug) Slog.d(TAG, "found a change on id " + id); + if (sDebug) { + Slog.d(TAG, "found a dataset change on id " + id + ": from " + + datasetValue + " to " + currentValue); + } continue datasets_loop; } - if (sVerbose) Slog.v(TAG, "no changes for id " + id); + if (sVerbose) Slog.v(TAG, "no dataset changes for id " + id); } if (sDebug) { Slog.d(TAG, "ignoring Save UI because all fields match contents of " @@ -1425,7 +1439,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState final IAutoFillManagerClient client = getClient(); mPendingSaveUi = new PendingUi(mActivityToken, id, client); getUiForShowing().showSaveUi(mService.getServiceLabel(), mService.getServiceIcon(), - mService.getServicePackageName(), saveInfo, valueFinder, + mService.getServicePackageName(), saveInfo, this, mComponentName.getPackageName(), this, mPendingSaveUi); if (client != null) { diff --git a/services/backup/java/com/android/server/backup/BackupManagerService.java b/services/backup/java/com/android/server/backup/BackupManagerService.java index 14b76ab64fa8..3b80f555d458 100644 --- a/services/backup/java/com/android/server/backup/BackupManagerService.java +++ b/services/backup/java/com/android/server/backup/BackupManagerService.java @@ -2638,7 +2638,7 @@ public class BackupManagerService implements BackupManagerServiceInterface { confIntent.setClassName("com.android.backupconfirm", "com.android.backupconfirm.BackupRestoreConfirmation"); confIntent.putExtra(FullBackup.CONF_TOKEN_INTENT_EXTRA, token); - confIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + confIntent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP); mContext.startActivityAsUser(confIntent, UserHandle.SYSTEM); } catch (ActivityNotFoundException e) { return false; diff --git a/services/core/java/com/android/server/AlarmManagerService.java b/services/core/java/com/android/server/AlarmManagerService.java index 342b48ee7f72..30dfee86403f 100644 --- a/services/core/java/com/android/server/AlarmManagerService.java +++ b/services/core/java/com/android/server/AlarmManagerService.java @@ -16,6 +16,7 @@ package com.android.server; +import android.annotation.UserIdInt; import android.app.Activity; import android.app.ActivityManager; import android.app.AlarmManager; @@ -26,6 +27,8 @@ import android.app.IAlarmListener; import android.app.IAlarmManager; import android.app.IUidObserver; import android.app.PendingIntent; +import android.app.usage.UsageStatsManager; +import android.app.usage.UsageStatsManagerInternal; import android.content.BroadcastReceiver; import android.content.ContentResolver; import android.content.Context; @@ -57,6 +60,7 @@ import android.text.format.DateFormat; import android.util.ArrayMap; import android.util.KeyValueListParser; import android.util.Log; +import android.util.Pair; import android.util.Slog; import android.util.SparseArray; import android.util.SparseBooleanArray; @@ -75,6 +79,7 @@ import java.util.Collections; import java.util.Comparator; import java.util.Date; import java.util.HashMap; +import java.util.HashSet; import java.util.LinkedList; import java.util.Locale; import java.util.Random; @@ -120,6 +125,7 @@ class AlarmManagerService extends SystemService { static final boolean DEBUG_LISTENER_CALLBACK = localLOGV || false; static final boolean DEBUG_WAKELOCK = localLOGV || false; static final boolean DEBUG_BG_LIMIT = localLOGV || false; + static final boolean DEBUG_STANDBY = localLOGV || false; static final boolean RECORD_ALARMS_IN_HISTORY = true; static final boolean RECORD_DEVICE_IDLE_ALARMS = false; static final int ALARM_EVENT = 1; @@ -140,6 +146,7 @@ class AlarmManagerService extends SystemService { AppOpsManager mAppOps; DeviceIdleController.LocalService mLocalDeviceIdleController; + private UsageStatsManagerInternal mUsageStatsManagerInternal; final Object mLock = new Object(); @@ -215,6 +222,14 @@ class AlarmManagerService extends SystemService { } final ArrayList<IdleDispatchEntry> mAllowWhileIdleDispatches = new ArrayList(); + interface Stats { + int REBATCH_ALL_ALARMS = 0; + } + + private final StatLogger mStatLogger = new StatLogger(new String[] { + "REBATCH_ALL_ALARMS", + }); + /** * Broadcast options to use for FLAG_ALLOW_WHILE_IDLE. */ @@ -233,6 +248,8 @@ class AlarmManagerService extends SystemService { new SparseArray<>(); private final ForceAppStandbyTracker mForceAppStandbyTracker; + private boolean mAppStandbyParole; + private ArrayMap<Pair<String, Integer>, Long> mLastAlarmDeliveredForPackage = new ArrayMap<>(); /** * All times are in milliseconds. These constants are kept synchronized with the system @@ -249,13 +266,28 @@ class AlarmManagerService extends SystemService { = "allow_while_idle_whitelist_duration"; private static final String KEY_LISTENER_TIMEOUT = "listener_timeout"; + // Keys for specifying throttling delay based on app standby bucketing + private final String[] KEYS_APP_STANDBY_DELAY = { + "standby_active_delay", + "standby_working_delay", + "standby_frequent_delay", + "standby_rare_delay", + "standby_never_delay", + }; + private static final long DEFAULT_MIN_FUTURITY = 5 * 1000; private static final long DEFAULT_MIN_INTERVAL = 60 * 1000; private static final long DEFAULT_ALLOW_WHILE_IDLE_SHORT_TIME = DEFAULT_MIN_FUTURITY; private static final long DEFAULT_ALLOW_WHILE_IDLE_LONG_TIME = 9*60*1000; private static final long DEFAULT_ALLOW_WHILE_IDLE_WHITELIST_DURATION = 10*1000; - private static final long DEFAULT_LISTENER_TIMEOUT = 5 * 1000; + private final long[] DEFAULT_APP_STANDBY_DELAYS = { + 0, // Active + 6 * 60_000, // Working + 30 * 60_000, // Frequent + 2 * 60 * 60_000, // Rare + 10 * 24 * 60 * 60_000 // Never + }; // Minimum futurity of a new alarm public long MIN_FUTURITY = DEFAULT_MIN_FUTURITY; @@ -276,6 +308,8 @@ class AlarmManagerService extends SystemService { // Direct alarm listener callback timeout public long LISTENER_TIMEOUT = DEFAULT_LISTENER_TIMEOUT; + public long[] APP_STANDBY_MIN_DELAYS = new long[DEFAULT_APP_STANDBY_DELAYS.length]; + private ContentResolver mResolver; private final KeyValueListParser mParser = new KeyValueListParser(','); private long mLastAllowWhileIdleWhitelistDuration = -1; @@ -328,7 +362,12 @@ class AlarmManagerService extends SystemService { DEFAULT_ALLOW_WHILE_IDLE_WHITELIST_DURATION); LISTENER_TIMEOUT = mParser.getLong(KEY_LISTENER_TIMEOUT, DEFAULT_LISTENER_TIMEOUT); - + APP_STANDBY_MIN_DELAYS[0] = mParser.getDurationMillis(KEYS_APP_STANDBY_DELAY[0], + DEFAULT_APP_STANDBY_DELAYS[0]); + for (int i = 1; i < KEYS_APP_STANDBY_DELAY.length; i++) { + APP_STANDBY_MIN_DELAYS[i] = mParser.getDurationMillis(KEYS_APP_STANDBY_DELAY[i], + Math.max(APP_STANDBY_MIN_DELAYS[i-1], DEFAULT_APP_STANDBY_DELAYS[i])); + } updateAllowWhileIdleWhitelistDurationLocked(); } } @@ -359,6 +398,12 @@ class AlarmManagerService extends SystemService { pw.print(" "); pw.print(KEY_ALLOW_WHILE_IDLE_WHITELIST_DURATION); pw.print("="); TimeUtils.formatDuration(ALLOW_WHILE_IDLE_WHITELIST_DURATION, pw); pw.println(); + + for (int i = 0; i < KEYS_APP_STANDBY_DELAY.length; i++) { + pw.print(" "); pw.print(KEYS_APP_STANDBY_DELAY[i]); pw.print("="); + TimeUtils.formatDuration(APP_STANDBY_MIN_DELAYS[i], pw); + pw.println(); + } } void dumpProto(ProtoOutputStream proto, long fieldId) { @@ -618,9 +663,7 @@ class AlarmManagerService extends SystemService { } PriorityClass packagePrio = a.priorityClass; - String alarmPackage = (a.operation != null) - ? a.operation.getCreatorPackage() - : a.packageName; + String alarmPackage = a.sourcePackage; if (packagePrio == null) packagePrio = mPriorities.get(alarmPackage); if (packagePrio == null) { packagePrio = a.priorityClass = new PriorityClass(); // lowest prio & stale sequence @@ -751,6 +794,7 @@ class AlarmManagerService extends SystemService { } void rebatchAllAlarmsLocked(boolean doValidate) { + long start = mStatLogger.getTime(); final int oldCount = getAlarmCount(mAlarmBatches) + ArrayUtils.size(mPendingWhileIdleAlarms); final boolean oldHasTick = haveBatchesTimeTickAlarm(mAlarmBatches) @@ -790,6 +834,7 @@ class AlarmManagerService extends SystemService { rescheduleKernelAlarmsLocked(); updateNextAlarmClockLocked(); + mStatLogger.logDurationStat(Stats.REBATCH_ALL_ALARMS, start); } void reAddAlarmLocked(Alarm a, long nowElapsed, boolean doValidate) { @@ -905,7 +950,7 @@ class AlarmManagerService extends SystemService { // Recurring alarms may have passed several alarm intervals while the // alarm was kept pending. Send the appropriate trigger count. if (alarm.repeatInterval > 0) { - alarm.count += (nowELAPSED - alarm.whenElapsed) / alarm.repeatInterval; + alarm.count += (nowELAPSED - alarm.requestedWhenElapsed) / alarm.repeatInterval; // Also schedule its next recurrence final long delta = alarm.count * alarm.repeatInterval; final long nextElapsed = alarm.whenElapsed + delta; @@ -1228,6 +1273,8 @@ class AlarmManagerService extends SystemService { mAppOps = (AppOpsManager) getContext().getSystemService(Context.APP_OPS_SERVICE); mLocalDeviceIdleController = LocalServices.getService(DeviceIdleController.LocalService.class); + mUsageStatsManagerInternal = LocalServices.getService(UsageStatsManagerInternal.class); + mUsageStatsManagerInternal.addAppIdleStateChangeListener(new AppStandbyTracker()); } } @@ -1377,6 +1424,51 @@ class AlarmManagerService extends SystemService { setImplLocked(a, false, doValidate); } + private long getMinDelayForBucketLocked(int bucket) { + // Return the minimum time that should elapse before an app in the specified bucket + // can receive alarms again + if (bucket == UsageStatsManager.STANDBY_BUCKET_NEVER) { + return mConstants.APP_STANDBY_MIN_DELAYS[4]; + } + else if (bucket >= UsageStatsManager.STANDBY_BUCKET_RARE) { + return mConstants.APP_STANDBY_MIN_DELAYS[3]; + } + else if (bucket >= UsageStatsManager.STANDBY_BUCKET_FREQUENT) { + return mConstants.APP_STANDBY_MIN_DELAYS[2]; + } + else if (bucket >= UsageStatsManager.STANDBY_BUCKET_WORKING_SET) { + return mConstants.APP_STANDBY_MIN_DELAYS[1]; + } + else return mConstants.APP_STANDBY_MIN_DELAYS[0]; + } + + private void adjustDeliveryTimeBasedOnStandbyBucketLocked(Alarm alarm) { + if (alarm.alarmClock != null || UserHandle.isCore(alarm.creatorUid)) { + return; + } + if (mAppStandbyParole) { + if (alarm.whenElapsed > alarm.requestedWhenElapsed) { + // We did throttle this alarm earlier, restore original requirements + alarm.whenElapsed = alarm.requestedWhenElapsed; + alarm.maxWhenElapsed = alarm.requestedMaxWhenElapsed; + } + return; + } + final String sourcePackage = alarm.sourcePackage; + final int sourceUserId = UserHandle.getUserId(alarm.creatorUid); + final int standbyBucket = mUsageStatsManagerInternal.getAppStandbyBucket( + sourcePackage, sourceUserId, SystemClock.elapsedRealtime()); + + final Pair<String, Integer> packageUser = Pair.create(sourcePackage, sourceUserId); + final long lastElapsed = mLastAlarmDeliveredForPackage.getOrDefault(packageUser, 0L); + if (lastElapsed > 0) { + final long minElapsed = lastElapsed + getMinDelayForBucketLocked(standbyBucket); + if (alarm.requestedWhenElapsed < minElapsed) { + alarm.whenElapsed = alarm.maxWhenElapsed = minElapsed; + } + } + } + private void setImplLocked(Alarm a, boolean rebatching, boolean doValidate) { if ((a.flags&AlarmManager.FLAG_IDLE_UNTIL) != 0) { // This is a special alarm that will put the system into idle until it goes off. @@ -1428,6 +1520,7 @@ class AlarmManagerService extends SystemService { mAllowWhileIdleDispatches.add(ent); } } + adjustDeliveryTimeBasedOnStandbyBucketLocked(a); int whichBatch = ((a.flags&AlarmManager.FLAG_STANDALONE) != 0) ? -1 : attemptCoalesceLocked(a.whenElapsed, a.maxWhenElapsed); @@ -1655,6 +1748,9 @@ class AlarmManagerService extends SystemService { mForceAppStandbyTracker.dump(pw, " "); pw.println(); + pw.println(" App Standby Parole: " + mAppStandbyParole); + pw.println(); + final long nowRTC = System.currentTimeMillis(); final long nowELAPSED = SystemClock.elapsedRealtime(); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS"); @@ -1753,6 +1849,15 @@ class AlarmManagerService extends SystemService { } pw.println("]"); + pw.println(" mLastAlarmDeliveredForPackage:"); + for (int i = 0; i < mLastAlarmDeliveredForPackage.size(); i++) { + Pair<String, Integer> packageUser = mLastAlarmDeliveredForPackage.keyAt(i); + pw.print(" Package " + packageUser.first + ", User " + packageUser.second + ":"); + TimeUtils.formatDuration(mLastAlarmDeliveredForPackage.valueAt(i), nowELAPSED, pw); + pw.println(); + } + pw.println(); + if (mPendingIdleUntil != null || mPendingWhileIdleAlarms.size() > 0) { pw.println(); pw.println(" Idle mode state:"); @@ -1913,6 +2018,8 @@ class AlarmManagerService extends SystemService { } } } + pw.println(); + mStatLogger.dump(pw, " "); if (RECORD_DEVICE_IDLE_ALARMS) { pw.println(); @@ -2746,8 +2853,7 @@ class AlarmManagerService extends SystemService { // Don't block starting foreground components return false; } - final String sourcePackage = - (alarm.operation != null) ? alarm.operation.getCreatorPackage() : alarm.packageName; + final String sourcePackage = alarm.sourcePackage; final int sourceUid = alarm.creatorUid; return mForceAppStandbyTracker.areAlarmsRestricted(sourceUid, sourcePackage, allowWhileIdle); @@ -2856,7 +2962,7 @@ class AlarmManagerService extends SystemService { if (alarm.repeatInterval > 0) { // this adjustment will be zero if we're late by // less than one full repeat interval - alarm.count += (nowELAPSED - alarm.whenElapsed) / alarm.repeatInterval; + alarm.count += (nowELAPSED - alarm.requestedWhenElapsed) / alarm.repeatInterval; // Also schedule its next recurrence final long delta = alarm.count * alarm.repeatInterval; @@ -2925,11 +3031,14 @@ class AlarmManagerService extends SystemService { public final int uid; public final int creatorUid; public final String packageName; + public final String sourcePackage; public int count; public long when; public long windowLength; public long whenElapsed; // 'when' in the elapsed time base public long maxWhenElapsed; // also in the elapsed time base + public final long requestedWhenElapsed; // original expiry time requested by the app + public final long requestedMaxWhenElapsed; public long repeatInterval; public PriorityClass priorityClass; @@ -2943,8 +3052,10 @@ class AlarmManagerService extends SystemService { || _type == AlarmManager.RTC_WAKEUP; when = _when; whenElapsed = _whenElapsed; + requestedWhenElapsed = _whenElapsed; windowLength = _windowLength; maxWhenElapsed = _maxWhen; + requestedMaxWhenElapsed = _maxWhen; repeatInterval = _interval; operation = _op; listener = _rec; @@ -2955,7 +3066,7 @@ class AlarmManagerService extends SystemService { alarmClock = _info; uid = _uid; packageName = _pkgName; - + sourcePackage = (operation != null) ? operation.getCreatorPackage() : packageName; creatorUid = (operation != null) ? operation.getCreatorUid() : uid; } @@ -2980,9 +3091,7 @@ class AlarmManagerService extends SystemService { } public boolean matches(String packageName) { - return (operation != null) - ? packageName.equals(operation.getTargetPackage()) - : packageName.equals(this.packageName); + return packageName.equals(sourcePackage); } @Override @@ -2995,11 +3104,7 @@ class AlarmManagerService extends SystemService { sb.append(" when "); sb.append(when); sb.append(" "); - if (operation != null) { - sb.append(operation.getTargetPackage()); - } else { - sb.append(packageName); - } + sb.append(sourcePackage); sb.append('}'); return sb.toString(); } @@ -3009,6 +3114,8 @@ class AlarmManagerService extends SystemService { final boolean isRtc = (type == RTC || type == RTC_WAKEUP); pw.print(prefix); pw.print("tag="); pw.println(statsTag); pw.print(prefix); pw.print("type="); pw.print(type); + pw.print(" requestedWhenELapsed="); TimeUtils.formatDuration( + requestedWhenElapsed, nowELAPSED, pw); pw.print(" whenElapsed="); TimeUtils.formatDuration(whenElapsed, nowELAPSED, pw); pw.print(" when="); @@ -3249,8 +3356,6 @@ class AlarmManagerService extends SystemService { // alarms, we need to merge them in to the list. note we don't // just deliver them first because we generally want non-wakeup // alarms delivered after wakeup alarms. - rescheduleKernelAlarmsLocked(); - updateNextAlarmClockLocked(); if (mPendingNonWakeupAlarms.size() > 0) { calculateDeliveryPriorities(mPendingNonWakeupAlarms); triggerList.addAll(mPendingNonWakeupAlarms); @@ -3262,6 +3367,27 @@ class AlarmManagerService extends SystemService { } mPendingNonWakeupAlarms.clear(); } + boolean needRebatch = false; + final HashSet<String> triggerPackages = new HashSet<>(); + for (int i = triggerList.size() - 1; i >= 0; i--) { + triggerPackages.add(triggerList.get(i).sourcePackage); + } + outer: + for (int i = 0; i < mAlarmBatches.size(); i++) { + final Batch batch = mAlarmBatches.get(i); + for (int j = 0; j < batch.size(); j++) { + if (triggerPackages.contains(batch.get(j))) { + needRebatch = true; + break outer; + } + } + } + if (needRebatch) { + rebatchAllAlarmsLocked(false); + } else { + rescheduleKernelAlarmsLocked(); + updateNextAlarmClockLocked(); + } deliverAlarmsLocked(triggerList, nowELAPSED); } } @@ -3318,6 +3444,8 @@ class AlarmManagerService extends SystemService { public static final int SEND_NEXT_ALARM_CLOCK_CHANGED = 2; public static final int LISTENER_TIMEOUT = 3; public static final int REPORT_ALARMS_ACTIVE = 4; + public static final int APP_STANDBY_BUCKET_CHANGED = 5; + public static final int APP_STANDBY_PAROLE_CHANGED = 6; public AlarmHandler() { } @@ -3363,6 +3491,19 @@ class AlarmManagerService extends SystemService { } break; + case APP_STANDBY_PAROLE_CHANGED: + synchronized (mLock) { + mAppStandbyParole = (Boolean) msg.obj; + rebatchAllAlarmsLocked(false); + } + break; + + case APP_STANDBY_BUCKET_CHANGED: + synchronized (mLock) { + rebatchAllAlarmsLocked(false); + } + break; + default: // nope, just ignore it break; @@ -3489,6 +3630,13 @@ class AlarmManagerService extends SystemService { int userHandle = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1); if (userHandle >= 0) { removeUserLocked(userHandle); + for (int i = mLastAlarmDeliveredForPackage.size() - 1; i >= 0; i--) { + final Pair<String, Integer> packageUser = + mLastAlarmDeliveredForPackage.keyAt(i); + if (packageUser.second == userHandle) { + mLastAlarmDeliveredForPackage.removeAt(i); + } + } } } else if (Intent.ACTION_UID_REMOVED.equals(action)) { if (uid >= 0) { @@ -3509,6 +3657,13 @@ class AlarmManagerService extends SystemService { } } if (pkgList != null && (pkgList.length > 0)) { + for (int i = mLastAlarmDeliveredForPackage.size() - 1; i >= 0; i--) { + Pair<String, Integer> packageUser = mLastAlarmDeliveredForPackage.keyAt(i); + if (ArrayUtils.contains(pkgList, packageUser.first) + && packageUser.second == UserHandle.getUserId(uid)) { + mLastAlarmDeliveredForPackage.removeAt(i); + } + } for (String pkg : pkgList) { if (uid >= 0) { // package-removed case @@ -3563,6 +3718,33 @@ class AlarmManagerService extends SystemService { } }; + /** + * Tracking of app assignments to standby buckets + */ + final class AppStandbyTracker extends UsageStatsManagerInternal.AppIdleStateChangeListener { + @Override + public void onAppIdleStateChanged(final String packageName, final @UserIdInt int userId, + boolean idle, int bucket) { + if (DEBUG_STANDBY) { + Slog.d(TAG, "Package " + packageName + " for user " + userId + " now in bucket " + + bucket); + } + mHandler.removeMessages(AlarmHandler.APP_STANDBY_BUCKET_CHANGED); + mHandler.sendEmptyMessage(AlarmHandler.APP_STANDBY_BUCKET_CHANGED); + } + + @Override + public void onParoleStateChanged(boolean isParoleOn) { + if (DEBUG_STANDBY) { + Slog.d(TAG, "Global parole state now " + (isParoleOn ? "ON" : "OFF")); + } + mHandler.removeMessages(AlarmHandler.APP_STANDBY_BUCKET_CHANGED); + mHandler.removeMessages(AlarmHandler.APP_STANDBY_PAROLE_CHANGED); + mHandler.obtainMessage(AlarmHandler.APP_STANDBY_PAROLE_CHANGED, + Boolean.valueOf(isParoleOn)).sendToTarget(); + } + }; + private final Listener mForceAppStandbyListener = new Listener() { @Override public void unblockAllUnrestrictedAlarms() { @@ -3841,7 +4023,6 @@ class AlarmManagerService extends SystemService { alarm.packageName, alarm.type, alarm.statsTag, nowELAPSED); mInFlight.add(inflight); mBroadcastRefCount++; - if (allowWhileIdle) { // Record the last time this uid handled an ALLOW_WHILE_IDLE alarm. mLastAllowWhileIdleDispatch.put(alarm.creatorUid, nowELAPSED); @@ -3860,6 +4041,11 @@ class AlarmManagerService extends SystemService { mAllowWhileIdleDispatches.add(ent); } } + if (!UserHandle.isCore(alarm.creatorUid)) { + final Pair<String, Integer> packageUser = Pair.create(alarm.sourcePackage, + UserHandle.getUserId(alarm.creatorUid)); + mLastAlarmDeliveredForPackage.put(packageUser, nowELAPSED); + } final BroadcastStats bs = inflight.mBroadcastStats; bs.count++; diff --git a/services/core/java/com/android/server/AppOpsService.java b/services/core/java/com/android/server/AppOpsService.java index f4675fd3316d..894106afc30a 100644 --- a/services/core/java/com/android/server/AppOpsService.java +++ b/services/core/java/com/android/server/AppOpsService.java @@ -39,6 +39,7 @@ import android.os.ResultReceiver; import android.os.ServiceManager; import android.os.ShellCallback; import android.os.ShellCommand; +import android.os.SystemClock; import android.os.UserHandle; import android.os.UserManager; import android.os.storage.StorageManagerInternal; @@ -248,7 +249,7 @@ public class AppOpsService extends IAppOpsService.Stub { public AppOpsService(File storagePath, Handler handler) { LockGuard.installLock(this, LockGuard.INDEX_APP_OPS); - mFile = new AtomicFile(storagePath); + mFile = new AtomicFile(storagePath, "appops"); mHandler = handler; readState(); } @@ -1666,8 +1667,6 @@ public class AppOpsService extends IAppOpsService.Stub { void writeState() { synchronized (mFile) { - List<AppOpsManager.PackageOps> allOps = getPackagesForOps(null); - FileOutputStream stream; try { stream = mFile.startWrite(); @@ -1676,6 +1675,8 @@ public class AppOpsService extends IAppOpsService.Stub { return; } + List<AppOpsManager.PackageOps> allOps = getPackagesForOps(null); + try { XmlSerializer out = new FastXmlSerializer(); out.setOutput(stream, StandardCharsets.UTF_8.name()); diff --git a/services/core/java/com/android/server/BatteryService.java b/services/core/java/com/android/server/BatteryService.java index ba9883b4de59..8265262d3824 100644 --- a/services/core/java/com/android/server/BatteryService.java +++ b/services/core/java/com/android/server/BatteryService.java @@ -835,6 +835,9 @@ public final class BatteryService extends SystemService { case "level": mHealthInfo.batteryLevel = Integer.parseInt(value); break; + case "counter": + mHealthInfo.batteryChargeCounter = Integer.parseInt(value); + break; case "temp": mHealthInfo.batteryTemperature = Integer.parseInt(value); break; @@ -1164,6 +1167,20 @@ public final class BatteryService extends SystemService { } @Override + public int getBatteryChargeCounter() { + synchronized (mLock) { + return mHealthInfo.batteryChargeCounter; + } + } + + @Override + public int getBatteryFullCharge() { + synchronized (mLock) { + return mHealthInfo.batteryFullCharge; + } + } + + @Override public boolean getBatteryLevelLow() { synchronized (mLock) { return mBatteryLevelLow; diff --git a/services/core/java/com/android/server/DeviceIdleController.java b/services/core/java/com/android/server/DeviceIdleController.java index fd3f708c6bc5..44974ffd3509 100644 --- a/services/core/java/com/android/server/DeviceIdleController.java +++ b/services/core/java/com/android/server/DeviceIdleController.java @@ -1352,14 +1352,21 @@ public class DeviceIdleController extends SystemService public int[] getPowerSaveTempWhitelistAppIds() { return DeviceIdleController.this.getAppIdTempWhitelistInternal(); } - - public void keyguardShowing(boolean showing) { - synchronized (DeviceIdleController.this) { - DeviceIdleController.this.keyguardShowingLocked(showing); - } - } } + private ActivityManagerInternal.ScreenObserver mScreenObserver = + new ActivityManagerInternal.ScreenObserver() { + @Override + public void onAwakeStateChanged(boolean isAwake) { } + + @Override + public void onKeyguardStateChanged(boolean isShowing) { + synchronized (DeviceIdleController.this) { + DeviceIdleController.this.keyguardShowingLocked(isShowing); + } + } + }; + public DeviceIdleController(Context context) { super(context); mConfigFile = new AtomicFile(new File(getSystemDir(), "deviceidle.xml")); @@ -1522,6 +1529,8 @@ public class DeviceIdleController extends SystemService mLocalActivityManager.setDeviceIdleWhitelist(mPowerSaveWhitelistAllAppIdArray); mLocalPowerManager.setDeviceIdleWhitelist(mPowerSaveWhitelistAllAppIdArray); + mLocalActivityManager.registerScreenObserver(mScreenObserver); + passWhiteListToForceAppStandbyTrackerLocked(); updateInteractivityLocked(); } @@ -2028,7 +2037,6 @@ public class DeviceIdleController extends SystemService } } - void scheduleReportActiveLocked(String activeReason, int activeUid) { Message msg = mHandler.obtainMessage(MSG_REPORT_ACTIVE, activeUid, 0, activeReason); mHandler.sendMessage(msg); diff --git a/services/core/java/com/android/server/EventLogTags.logtags b/services/core/java/com/android/server/EventLogTags.logtags index 732ac66b41de..219facd0b002 100644 --- a/services/core/java/com/android/server/EventLogTags.logtags +++ b/services/core/java/com/android/server/EventLogTags.logtags @@ -34,6 +34,7 @@ option java_package com.android.server 2731 power_soft_sleep_requested (savedwaketimems|2) # Power save state has changed. See BatterySaverController.java for the details. 2739 battery_saver_mode (prevOffOrOn|1|5),(nowOffOrOn|1|5),(interactive|1|5),(features|3|5) +27390 battery_saving_stats (batterySaver|1|5),(interactive|1|5),(doze|1|5),(delta_duration|2|3),(delta_battery_drain|1|6),(total_duration|2|3),(total_battery_drain|1|6) # # Leave IDs through 2740 for more power logs (2730 used by battery_discharge above) diff --git a/services/core/java/com/android/server/InputMethodManagerService.java b/services/core/java/com/android/server/InputMethodManagerService.java index 21137adce69a..fc91d0d7abf1 100644 --- a/services/core/java/com/android/server/InputMethodManagerService.java +++ b/services/core/java/com/android/server/InputMethodManagerService.java @@ -4306,7 +4306,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub Slog.w(TAG, "Couldn't create dir.: " + inputMethodDir.getAbsolutePath()); } final File subtypeFile = new File(inputMethodDir, ADDITIONAL_SUBTYPES_FILE_NAME); - mAdditionalInputMethodSubtypeFile = new AtomicFile(subtypeFile); + mAdditionalInputMethodSubtypeFile = new AtomicFile(subtypeFile, "input-subtypes"); if (!subtypeFile.exists()) { // If "subtypes.xml" doesn't exist, create a blank file. writeAdditionalInputMethodSubtypes( diff --git a/services/core/java/com/android/server/PinnerService.java b/services/core/java/com/android/server/PinnerService.java index 3d7408ee6a54..2869114601b7 100644 --- a/services/core/java/com/android/server/PinnerService.java +++ b/services/core/java/com/android/server/PinnerService.java @@ -372,15 +372,19 @@ public final class PinnerService extends SystemService { @Override protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return; + long totalSize = 0; pw.println("Pinned Files:"); synchronized(this) { for (int i = 0; i < mPinnedFiles.size(); i++) { pw.println(mPinnedFiles.get(i).mFilename); + totalSize += mPinnedFiles.get(i).mLength; } for (int i = 0; i < mPinnedCameraFiles.size(); i++) { pw.println(mPinnedCameraFiles.get(i).mFilename); + totalSize += mPinnedCameraFiles.get(i).mLength; } } + pw.println("Total size: " + totalSize); } } diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java index 7361e70a5bde..84b93e36c602 100644 --- a/services/core/java/com/android/server/StorageManagerService.java +++ b/services/core/java/com/android/server/StorageManagerService.java @@ -36,6 +36,7 @@ import android.app.ActivityManagerInternal.ScreenObserver; import android.app.AppOpsManager; import android.app.IActivityManager; import android.app.KeyguardManager; +import android.app.admin.SecurityLog; import android.app.usage.StorageStatsManager; import android.content.BroadcastReceiver; import android.content.ComponentName; @@ -50,7 +51,6 @@ import android.content.pm.UserInfo; import android.content.res.Configuration; import android.content.res.ObbInfo; import android.database.ContentObserver; -import android.net.TrafficStats; import android.net.Uri; import android.os.Binder; import android.os.DropBoxManager; @@ -150,7 +150,6 @@ import java.util.Locale; import java.util.Map; import java.util.Map.Entry; import java.util.Objects; -import java.util.concurrent.CompletableFuture; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; @@ -1275,6 +1274,29 @@ class StorageManagerService extends IStorageManager.Stub mObbActionHandler.sendMessage(mObbActionHandler.obtainMessage( OBB_FLUSH_MOUNT_STATE, vol.path)); } + maybeLogMediaMount(vol, newState); + } + + private void maybeLogMediaMount(VolumeInfo vol, int newState) { + if (!SecurityLog.isLoggingEnabled()) { + return; + } + + final DiskInfo disk = vol.getDisk(); + if (disk == null || (disk.flags & (DiskInfo.FLAG_SD | DiskInfo.FLAG_USB)) == 0) { + return; + } + + // Sometimes there is a newline character. + final String label = disk.label != null ? disk.label.trim() : ""; + + if (newState == VolumeInfo.STATE_MOUNTED + || newState == VolumeInfo.STATE_MOUNTED_READ_ONLY) { + SecurityLog.writeEvent(SecurityLog.TAG_MEDIA_MOUNT, vol.path, label); + } else if (newState == VolumeInfo.STATE_UNMOUNTED + || newState == VolumeInfo.STATE_BAD_REMOVAL) { + SecurityLog.writeEvent(SecurityLog.TAG_MEDIA_UNMOUNT, vol.path, label); + } } private void onMoveStatusLocked(int status) { @@ -1386,7 +1408,7 @@ class StorageManagerService extends IStorageManager.Stub } mSettingsFile = new AtomicFile( - new File(Environment.getDataSystemDirectory(), "storage.xml")); + new File(Environment.getDataSystemDirectory(), "storage.xml"), "storage-settings"); synchronized (mLock) { readSettingsLocked(); diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index b7048989ab2a..5b8b6912cd19 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -26,7 +26,6 @@ import static android.Manifest.permission.INTERNAL_SYSTEM_WINDOW; import static android.Manifest.permission.MANAGE_ACTIVITY_STACKS; import static android.Manifest.permission.READ_FRAME_BUFFER; import static android.Manifest.permission.REMOVE_TASKS; -import static android.Manifest.permission.START_ACTIVITY_AS_CALLER; import static android.Manifest.permission.START_TASKS_FROM_RECENTS; import static android.app.ActivityManager.LOCK_TASK_MODE_NONE; import static android.app.ActivityManager.RESIZE_MODE_PRESERVE_WINDOW; @@ -571,23 +570,6 @@ public class ActivityManagerService extends IActivityManager.Stub // could take much longer than usual. static final int PROC_START_TIMEOUT_WITH_WRAPPER = 1200*1000; - // Permission tokens are used to temporarily granted a trusted app the ability to call - // #startActivityAsCaller. A client is expected to dump its token after this time has elapsed, - // showing any appropriate error messages to the user. - private static final long START_AS_CALLER_TOKEN_TIMEOUT = - 10 * DateUtils.MINUTE_IN_MILLIS; - - // How long before the service actually expires a token. This is slightly longer than - // START_AS_CALLER_TOKEN_TIMEOUT, to provide a buffer so clients will rarely encounter the - // expiration exception. - private static final long START_AS_CALLER_TOKEN_TIMEOUT_IMPL = - START_AS_CALLER_TOKEN_TIMEOUT + 2*1000; - - // How long the service will remember expired tokens, for the purpose of providing error - // messaging when a client uses an expired token. - private static final long START_AS_CALLER_TOKEN_EXPIRED_TIMEOUT = - START_AS_CALLER_TOKEN_TIMEOUT_IMPL + 20 * DateUtils.MINUTE_IN_MILLIS; - // How long we allow a receiver to run before giving up on it. static final int BROADCAST_FG_TIMEOUT = 10*1000; static final int BROADCAST_BG_TIMEOUT = 60*1000; @@ -696,13 +678,6 @@ public class ActivityManagerService extends IActivityManager.Stub final ArrayList<ActiveInstrumentation> mActiveInstrumentation = new ArrayList<>(); - // Activity tokens of system activities that are delegating their call to - // #startActivityByCaller, keyed by the permissionToken granted to the delegate. - final HashMap<IBinder, IBinder> mStartActivitySources = new HashMap<>(); - - // Permission tokens that have expired, but we remember for error reporting. - final ArrayList<IBinder> mExpiredStartAsCallerTokens = new ArrayList<>(); - public final IntentFirewall mIntentFirewall; // Whether we should show our dialogs (ANR, crash, etc) or just perform their @@ -1656,7 +1631,7 @@ public class ActivityManagerService extends IActivityManager.Stub String mTrackAllocationApp = null; String mNativeDebuggingApp = null; - final long[] mTmpLong = new long[2]; + final long[] mTmpLong = new long[3]; private final ArraySet<BroadcastQueue> mTmpBroadcastQueue = new ArraySet(); @@ -1881,8 +1856,6 @@ public class ActivityManagerService extends IActivityManager.Stub static final int PUSH_TEMP_WHITELIST_UI_MSG = 68; static final int SERVICE_FOREGROUND_CRASH_MSG = 69; static final int DISPATCH_OOM_ADJ_OBSERVER_MSG = 70; - static final int EXPIRE_START_AS_CALLER_TOKEN_MSG = 75; - static final int FORGET_START_AS_CALLER_TOKEN_MSG = 76; static final int FIRST_ACTIVITY_STACK_MSG = 100; static final int FIRST_BROADCAST_QUEUE_MSG = 200; @@ -2547,19 +2520,6 @@ public class ActivityManagerService extends IActivityManager.Stub } } } break; - case EXPIRE_START_AS_CALLER_TOKEN_MSG: { - synchronized (ActivityManagerService.this) { - final IBinder permissionToken = (IBinder)msg.obj; - mStartActivitySources.remove(permissionToken); - mExpiredStartAsCallerTokens.add(permissionToken); - } - } break; - case FORGET_START_AS_CALLER_TOKEN_MSG: { - synchronized (ActivityManagerService.this) { - final IBinder permissionToken = (IBinder)msg.obj; - mExpiredStartAsCallerTokens.remove(permissionToken); - } - } break; } } }; @@ -2614,22 +2574,24 @@ public class ActivityManagerService extends IActivityManager.Stub } int num = 0; - long[] tmp = new long[2]; + long[] tmp = new long[3]; do { ProcessRecord proc; int procState; + int statType; int pid; long lastPssTime; synchronized (ActivityManagerService.this) { if (mPendingPssProcesses.size() <= 0) { if (mTestPssMode || DEBUG_PSS) Slog.d(TAG_PSS, - "Collected PSS of " + num + " processes in " + "Collected pss of " + num + " processes in " + (SystemClock.uptimeMillis() - start) + "ms"); mPendingPssProcesses.clear(); return; } proc = mPendingPssProcesses.remove(0); procState = proc.pssProcState; + statType = proc.pssStatType; lastPssTime = proc.lastPssTime; if (proc.thread != null && procState == proc.setProcState && (lastPssTime+ProcessList.PSS_SAFE_TIME_FROM_STATE_CHANGE) @@ -2648,8 +2610,17 @@ public class ActivityManagerService extends IActivityManager.Stub if (pss != 0 && proc.thread != null && proc.setProcState == procState && proc.pid == pid && proc.lastPssTime == lastPssTime) { num++; - recordPssSampleLocked(proc, procState, pss, tmp[0], tmp[1], - endTime-startTime, SystemClock.uptimeMillis()); + ProcessList.commitNextPssTime(proc.procStateMemTracker); + recordPssSampleLocked(proc, procState, pss, tmp[0], tmp[1], tmp[2], + statType, endTime-startTime, SystemClock.uptimeMillis()); + } else { + ProcessList.abortNextPssTime(proc.procStateMemTracker); + if (DEBUG_PSS) Slog.d(TAG_PSS, "Skipped pss collection of " + pid + + ": " + (proc.thread == null ? "NO_THREAD " : "") + + (proc.pid != pid ? "PID_CHANGED " : "") + + " initState=" + procState + " curState=" + + proc.setProcState + " " + + (proc.lastPssTime != lastPssTime ? "TIME_CHANGED" : "")); } } } @@ -2946,7 +2917,7 @@ public class ActivityManagerService extends IActivityManager.Stub } }); - mGrantFile = new AtomicFile(new File(systemDir, "urigrants.xml")); + mGrantFile = new AtomicFile(new File(systemDir, "urigrants.xml"), "uri-grants"); mUserController = new UserController(this); @@ -4834,54 +4805,16 @@ public class ActivityManagerService extends IActivityManager.Stub } - /** - * Only callable from the system. This token grants a temporary permission to call - * #startActivityAsCallerWithToken. The token will time out after - * START_AS_CALLER_TOKEN_TIMEOUT if it is not used. - * - * @param delegatorToken The Binder token referencing the system Activity that wants to delegate - * the #startActivityAsCaller to another app. The "caller" will be the caller of this - * activity's token, not the delegate's caller (which is probably the delegator itself). - * - * @return Returns a token that can be given to a "delegate" app that may call - * #startActivityAsCaller - */ @Override - public IBinder requestStartActivityPermissionToken(IBinder delegatorToken) { - int callingUid = Binder.getCallingUid(); - if (UserHandle.getAppId(callingUid) != SYSTEM_UID) { - throw new SecurityException("Only the system process can request a permission token, " + - "received request from uid: " + callingUid); - } - IBinder permissionToken = new Binder(); - synchronized (this) { - mStartActivitySources.put(permissionToken, delegatorToken); - } - - Message expireMsg = mHandler.obtainMessage(EXPIRE_START_AS_CALLER_TOKEN_MSG, - permissionToken); - mHandler.sendMessageDelayed(expireMsg, START_AS_CALLER_TOKEN_TIMEOUT_IMPL); - - Message forgetMsg = mHandler.obtainMessage(FORGET_START_AS_CALLER_TOKEN_MSG, - permissionToken); - mHandler.sendMessageDelayed(forgetMsg, START_AS_CALLER_TOKEN_EXPIRED_TIMEOUT); - - return permissionToken; - } + public final int startActivityAsCaller(IApplicationThread caller, String callingPackage, + Intent intent, String resolvedType, IBinder resultTo, String resultWho, int requestCode, + int startFlags, ProfilerInfo profilerInfo, Bundle bOptions, boolean ignoreTargetSecurity, + int userId) { - @Override - public final int startActivityAsCaller(IApplicationThread caller, - String callingPackage, Intent intent, String resolvedType, IBinder resultTo, - String resultWho, int requestCode, int startFlags, ProfilerInfo profilerInfo, - Bundle bOptions, IBinder permissionToken, boolean ignoreTargetSecurity, int userId) { // This is very dangerous -- it allows you to perform a start activity (including - // permission grants) as any app that may launch one of your own activities. So we only - // allow this in two cases: - // 1) The caller is an activity that is part of the core framework, and then only when it - // is running as the system. - // 2) The caller provides a valid permissionToken. Permission tokens are one-time use and - // can only be requested by a system activity, which may then delegate this call to - // another app. + // permission grants) as any app that may launch one of your own activities. So + // we will only allow this to be done from activities that are part of the core framework, + // and then only when they are running as the system. final ActivityRecord sourceRecord; final int targetUid; final String targetPackage; @@ -4889,47 +4822,17 @@ public class ActivityManagerService extends IActivityManager.Stub if (resultTo == null) { throw new SecurityException("Must be called from an activity"); } - - final IBinder sourceToken; - if (permissionToken != null) { - // To even attempt to use a permissionToken, an app must also have this signature - // permission. - enforceCallingPermission(android.Manifest.permission.START_ACTIVITY_AS_CALLER, - "startActivityAsCaller"); - // If called with a permissionToken, we want the sourceRecord from the delegator - // activity that requested this token. - sourceToken = - mStartActivitySources.remove(permissionToken); - if (sourceToken == null) { - // Invalid permissionToken, check if it recently expired. - if (mExpiredStartAsCallerTokens.contains(permissionToken)) { - throw new SecurityException("Called with expired permission token: " - + permissionToken); - } else { - throw new SecurityException("Called with invalid permission token: " - + permissionToken); - } - } - } else { - // This method was called directly by the source. - sourceToken = resultTo; - } - - sourceRecord = mStackSupervisor.isInAnyStackLocked(sourceToken); + sourceRecord = mStackSupervisor.isInAnyStackLocked(resultTo); if (sourceRecord == null) { - throw new SecurityException("Called with bad activity token: " + sourceToken); + throw new SecurityException("Called with bad activity token: " + resultTo); + } + if (!sourceRecord.info.packageName.equals("android")) { + throw new SecurityException( + "Must be called from an activity that is declared in the android package"); } if (sourceRecord.app == null) { throw new SecurityException("Called without a process attached to activity"); } - - // Whether called directly or from a delegate, the source activity must be from the - // android package. - if (!sourceRecord.info.packageName.equals("android")) { - throw new SecurityException("Must be called from an activity that is " + - "declared in the android package"); - } - if (UserHandle.getAppId(sourceRecord.app.uid) != SYSTEM_UID) { // This is still okay, as long as this activity is running under the // uid of the original calling activity. @@ -4940,7 +4843,6 @@ public class ActivityManagerService extends IActivityManager.Stub + sourceRecord.launchedFromUid); } } - if (ignoreTargetSecurity) { if (intent.getComponent() == null) { throw new SecurityException( @@ -6435,7 +6337,8 @@ public class ActivityManagerService extends IActivityManager.Stub if (!keepState) { synchronized (this) { // Remove all permissions granted from/to this package - removeUriPermissionsForPackageLocked(packageName, resolvedUserId, true); + removeUriPermissionsForPackageLocked(packageName, resolvedUserId, true, + false); } // Reset notification state @@ -6763,7 +6666,7 @@ public class ActivityManagerService extends IActivityManager.Stub if (proc.thread != null && proc.setAdj == oomAdj) { // Record this for posterity if the process has been stable. proc.baseProcessTracker.addPss(infos[i].getTotalPss(), - infos[i].getTotalUss(), false, + infos[i].getTotalUss(), infos[i].getTotalRss(), false, ProcessStats.ADD_PSS_EXTERNAL_SLOW, endTime-startTime, proc.pkgList); } @@ -6786,7 +6689,7 @@ public class ActivityManagerService extends IActivityManager.Stub oomAdj = proc != null ? proc.setAdj : 0; } } - long[] tmpUss = new long[1]; + long[] tmpUss = new long[3]; long startTime = SystemClock.currentThreadTimeMillis(); pss[i] = Debug.getPss(pids[i], tmpUss, null); long endTime = SystemClock.currentThreadTimeMillis(); @@ -6794,7 +6697,7 @@ public class ActivityManagerService extends IActivityManager.Stub synchronized (this) { if (proc.thread != null && proc.setAdj == oomAdj) { // Record this for posterity if the process has been stable. - proc.baseProcessTracker.addPss(pss[i], tmpUss[0], false, + proc.baseProcessTracker.addPss(pss[i], tmpUss[0], tmpUss[2], false, ProcessStats.ADD_PSS_EXTERNAL, endTime-startTime, proc.pkgList); } } @@ -7090,7 +6993,7 @@ public class ActivityManagerService extends IActivityManager.Stub } // Remove transient permissions granted from/to this package/user - removeUriPermissionsForPackageLocked(packageName, userId, false); + removeUriPermissionsForPackageLocked(packageName, userId, false, false); if (doit) { for (i = mBroadcastQueues.length - 1; i >= 0; i--) { @@ -9813,9 +9716,11 @@ public class ActivityManagerService extends IActivityManager.Stub * @param userHandle User to match, or {@link UserHandle#USER_ALL} to apply * to all users. * @param persistable If persistable grants should be removed. + * @param targetOnly When {@code true}, only remove permissions where the app is the target, + * not source. */ private void removeUriPermissionsForPackageLocked( - String packageName, int userHandle, boolean persistable) { + String packageName, int userHandle, boolean persistable, boolean targetOnly) { if (userHandle == UserHandle.USER_ALL && packageName == null) { throw new IllegalArgumentException("Must narrow by either package or user"); } @@ -9834,7 +9739,7 @@ public class ActivityManagerService extends IActivityManager.Stub final UriPermission perm = it.next(); // Only inspect grants matching package - if (packageName == null || perm.sourcePkg.equals(packageName) + if (packageName == null || (!targetOnly && perm.sourcePkg.equals(packageName)) || perm.targetPkg.equals(packageName)) { // Hacky solution as part of fixing a security bug; ignore // grants associated with DownloadManager so we don't have @@ -9953,6 +9858,8 @@ public class ActivityManagerService extends IActivityManager.Stub private void writeGrantedUriPermissions() { if (DEBUG_URI_PERMISSION) Slog.v(TAG_URI_PERMISSION, "writeGrantedUriPermissions()"); + final long startTime = SystemClock.uptimeMillis(); + // Snapshot permissions so we can persist without lock ArrayList<UriPermission.Snapshot> persist = Lists.newArrayList(); synchronized (this) { @@ -9969,7 +9876,7 @@ public class ActivityManagerService extends IActivityManager.Stub FileOutputStream fos = null; try { - fos = mGrantFile.startWrite(); + fos = mGrantFile.startWrite(startTime); XmlSerializer out = new FastXmlSerializer(); out.setOutput(fos, StandardCharsets.UTF_8.name()); @@ -10086,8 +9993,7 @@ public class ActivityManagerService extends IActivityManager.Stub boolean persistChanged = false; GrantUri grantUri = new GrantUri(userId, uri, false); - UriPermission exactPerm = findUriPermissionLocked(callingUid, - new GrantUri(userId, uri, false)); + UriPermission exactPerm = findUriPermissionLocked(callingUid, grantUri); UriPermission prefixPerm = findUriPermissionLocked(callingUid, new GrantUri(userId, uri, true)); @@ -10270,7 +10176,9 @@ public class ActivityManagerService extends IActivityManager.Stub public void clearGrantedUriPermissions(String packageName, int userId) { enforceCallingPermission(android.Manifest.permission.CLEAR_APP_GRANTED_URI_PERMISSIONS, "clearGrantedUriPermissions"); - removeUriPermissionsForPackageLocked(packageName, userId, true); + synchronized(this) { + removeUriPermissionsForPackageLocked(packageName, userId, true, true); + } } @Override @@ -13004,7 +12912,6 @@ public class ActivityManagerService extends IActivityManager.Stub long ident = Binder.clearCallingIdentity(); try { mKeyguardController.setKeyguardShown(showing, secondaryDisplayShowing); - mLocalDeviceIdleController.keyguardShowing(showing); } finally { Binder.restoreCallingIdentity(ident); } @@ -14523,7 +14430,7 @@ public class ActivityManagerService extends IActivityManager.Stub && proc.setProcState >= ActivityManager.PROCESS_STATE_PERSISTENT) { proc.notCachedSinceIdle = true; proc.initialIdlePss = 0; - proc.nextPssTime = ProcessList.computeNextPssTime(proc.setProcState, true, + proc.nextPssTime = ProcessList.computeNextPssTime(proc.setProcState, null, mTestPssMode, isSleepingLocked(), now); } } @@ -14836,6 +14743,8 @@ public class ActivityManagerService extends IActivityManager.Stub mUserController.sendUserSwitchBroadcasts(-1, currentUserId); BinderInternal.nSetBinderProxyCountEnabled(true); + //STOPSHIP: Temporary BinderProxy Threshold for b/71353150 + BinderInternal.nSetBinderProxyCountWatermarks(1500, 1200); BinderInternal.setBinderProxyCountCallback( new BinderInternal.BinderProxyLimitListener() { @Override @@ -18453,12 +18362,13 @@ public class ActivityManagerService extends IActivityManager.Stub final long myTotalPss = mi.getTotalPss(); final long myTotalUss = mi.getTotalUss(); + final long myTotalRss = mi.getTotalRss(); final long myTotalSwapPss = mi.getTotalSwappedOutPss(); synchronized (this) { if (r.thread != null && oomAdj == r.getSetAdjWithServices()) { // Record this for posterity if the process has been stable. - r.baseProcessTracker.addPss(myTotalPss, myTotalUss, true, + r.baseProcessTracker.addPss(myTotalPss, myTotalUss, myTotalRss, true, reportType, endTime-startTime, r.pkgList); } } @@ -18949,12 +18859,13 @@ public class ActivityManagerService extends IActivityManager.Stub final long myTotalPss = mi.getTotalPss(); final long myTotalUss = mi.getTotalUss(); + final long myTotalRss = mi.getTotalRss(); final long myTotalSwapPss = mi.getTotalSwappedOutPss(); synchronized (this) { if (r.thread != null && oomAdj == r.getSetAdjWithServices()) { // Record this for posterity if the process has been stable. - r.baseProcessTracker.addPss(myTotalPss, myTotalUss, true, + r.baseProcessTracker.addPss(myTotalPss, myTotalUss, myTotalRss, true, reportType, endTime-startTime, r.pkgList); } } @@ -20861,7 +20772,8 @@ public class ActivityManagerService extends IActivityManager.Stub intent.getIntExtra(Intent.EXTRA_UID, -1), ssp); // Remove all permissions granted from/to this package - removeUriPermissionsForPackageLocked(ssp, userId, true); + removeUriPermissionsForPackageLocked(ssp, userId, true, + false); mRecentTasks.removeTasksByPackageName(ssp, userId); @@ -23230,14 +23142,13 @@ public class ActivityManagerService extends IActivityManager.Stub * Record new PSS sample for a process. */ void recordPssSampleLocked(ProcessRecord proc, int procState, long pss, long uss, long swapPss, - long pssDuration, long now) { + long rss, int statType, long pssDuration, long now) { EventLogTags.writeAmPss(proc.pid, proc.uid, proc.processName, pss * 1024, uss * 1024, - swapPss * 1024); + swapPss * 1024, rss * 1024, statType, procState, pssDuration); proc.lastPssTime = now; - proc.baseProcessTracker.addPss(pss, uss, true, ProcessStats.ADD_PSS_INTERNAL, - pssDuration, proc.pkgList); + proc.baseProcessTracker.addPss(pss, uss, rss, true, statType, pssDuration, proc.pkgList); if (DEBUG_PSS) Slog.d(TAG_PSS, - "PSS of " + proc.toShortString() + ": " + pss + " lastPss=" + proc.lastPss + "pss of " + proc.toShortString() + ": " + pss + " lastPss=" + proc.lastPss + " state=" + ProcessList.makeProcStateString(procState)); if (proc.initialIdlePss == 0) { proc.initialIdlePss = pss; @@ -23336,8 +23247,9 @@ public class ActivityManagerService extends IActivityManager.Stub if (mPendingPssProcesses.size() == 0) { mBgHandler.sendEmptyMessage(COLLECT_PSS_BG_MSG); } - if (DEBUG_PSS) Slog.d(TAG_PSS, "Requesting PSS of: " + proc); + if (DEBUG_PSS) Slog.d(TAG_PSS, "Requesting pss of: " + proc); proc.pssProcState = procState; + proc.pssStatType = ProcessStats.ADD_PSS_INTERNAL_SINGLE; mPendingPssProcesses.add(proc); } @@ -23352,7 +23264,7 @@ public class ActivityManagerService extends IActivityManager.Stub return; } } - if (DEBUG_PSS) Slog.d(TAG_PSS, "Requesting PSS of all procs! memLowered=" + memLowered); + if (DEBUG_PSS) Slog.d(TAG_PSS, "Requesting pss of all procs! memLowered=" + memLowered); mLastFullPssTime = now; mFullPssPending = true; mPendingPssProcesses.ensureCapacity(mLruProcesses.size()); @@ -23365,8 +23277,10 @@ public class ActivityManagerService extends IActivityManager.Stub } if (memLowered || now > (app.lastStateTime+ProcessList.PSS_ALL_INTERVAL)) { app.pssProcState = app.setProcState; - app.nextPssTime = ProcessList.computeNextPssTime(app.curProcState, true, - mTestPssMode, isSleepingLocked(), now); + app.pssStatType = always ? ProcessStats.ADD_PSS_INTERNAL_ALL_POLL + : ProcessStats.ADD_PSS_INTERNAL_ALL_MEM; + app.nextPssTime = ProcessList.computeNextPssTime(app.curProcState, + app.procStateMemTracker, mTestPssMode, isSleepingLocked(), now); mPendingPssProcesses.add(app); } } @@ -23733,16 +23647,16 @@ public class ActivityManagerService extends IActivityManager.Stub long startTime = SystemClock.currentThreadTimeMillis(); long pss = Debug.getPss(app.pid, mTmpLong, null); long endTime = SystemClock.currentThreadTimeMillis(); - recordPssSampleLocked(app, app.curProcState, pss, endTime-startTime, - mTmpLong[0], mTmpLong[1], now); + recordPssSampleLocked(app, app.curProcState, pss, mTmpLong[0], mTmpLong[1], + mTmpLong[2], ProcessStats.ADD_PSS_INTERNAL_SINGLE, endTime-startTime, now); mPendingPssProcesses.remove(app); Slog.i(TAG, "Recorded pss for " + app + " state " + app.setProcState + " to " + app.curProcState + ": " + (SystemClock.uptimeMillis()-start) + "ms"); } app.lastStateTime = now; - app.nextPssTime = ProcessList.computeNextPssTime(app.curProcState, true, - mTestPssMode, isSleepingLocked(), now); + app.nextPssTime = ProcessList.computeNextPssTime(app.curProcState, + app.procStateMemTracker, mTestPssMode, isSleepingLocked(), now); if (DEBUG_PSS) Slog.d(TAG_PSS, "Process state change from " + ProcessList.makeProcStateString(app.setProcState) + " to " + ProcessList.makeProcStateString(app.curProcState) + " next pss in " @@ -23752,10 +23666,10 @@ public class ActivityManagerService extends IActivityManager.Stub && now > (app.lastStateTime+ProcessList.minTimeFromStateChange( mTestPssMode)))) { requestPssLocked(app, app.setProcState); - app.nextPssTime = ProcessList.computeNextPssTime(app.curProcState, false, - mTestPssMode, isSleepingLocked(), now); + app.nextPssTime = ProcessList.computeNextPssTime(app.curProcState, + app.procStateMemTracker, mTestPssMode, isSleepingLocked(), now); } else if (false && DEBUG_PSS) Slog.d(TAG_PSS, - "Not requesting PSS of " + app + ": next=" + (app.nextPssTime-now)); + "Not requesting pss of " + app + ": next=" + (app.nextPssTime-now)); } if (app.setProcState != app.curProcState) { if (DEBUG_SWITCH || DEBUG_OOM_ADJ || mCurOomAdjUid == app.uid) { @@ -25510,6 +25424,7 @@ public class ActivityManagerService extends IActivityManager.Stub public void notifyAppTransitionFinished() { synchronized (ActivityManagerService.this) { mStackSupervisor.notifyAppTransitionDone(); + mKeyguardController.notifyAppTransitionDone(); } } diff --git a/services/core/java/com/android/server/am/ActivityRecord.java b/services/core/java/com/android/server/am/ActivityRecord.java index 3bef87794c24..cae0d2bc5dd9 100644 --- a/services/core/java/com/android/server/am/ActivityRecord.java +++ b/services/core/java/com/android/server/am/ActivityRecord.java @@ -1695,7 +1695,7 @@ final class ActivityRecord extends ConfigurationContainer implements AppWindowCo resumeKeyDispatchingLocked(); final ActivityStack stack = getStack(); - stack.mNoAnimActivities.clear(); + mStackSupervisor.mNoAnimActivities.clear(); // Mark the point when the activity is resuming // TODO: To be more accurate, the mark should be before the onCreate, diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java index ec8cf91a44cf..ab2dc36d66d0 100644 --- a/services/core/java/com/android/server/am/ActivityStack.java +++ b/services/core/java/com/android/server/am/ActivityStack.java @@ -277,12 +277,6 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai final ArrayList<ActivityRecord> mLRUActivities = new ArrayList<>(); /** - * Animations that for the current transition have requested not to - * be considered for the transition animation. - */ - final ArrayList<ActivityRecord> mNoAnimActivities = new ArrayList<>(); - - /** * When we are in the process of pausing an activity, before starting the * next one, this variable holds the activity that is currently being paused. */ @@ -453,6 +447,9 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai mStackId = stackId; mCurrentUser = mService.mUserController.getCurrentUserId(); mTmpRect2.setEmpty(); + // Set display id before setting activity and window type to make sure it won't affect + // stacks on a wrong display. + mDisplayId = display.mDisplayId; setActivityType(activityType); setWindowingMode(windowingMode); mWindowContainerController = createStackWindowController(display.mDisplayId, onTop, @@ -547,7 +544,7 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai wm.deferSurfaceLayout(); try { if (!animate && topActivity != null) { - mNoAnimActivities.add(topActivity); + mStackSupervisor.mNoAnimActivities.add(topActivity); } super.setWindowingMode(windowingMode); @@ -2457,7 +2454,7 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai if (prev.finishing) { if (DEBUG_TRANSITION) Slog.v(TAG_TRANSITION, "Prepare close transition: prev=" + prev); - if (mNoAnimActivities.contains(prev)) { + if (mStackSupervisor.mNoAnimActivities.contains(prev)) { anim = false; mWindowManager.prepareAppTransition(TRANSIT_NONE, false); } else { @@ -2469,7 +2466,7 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai } else { if (DEBUG_TRANSITION) Slog.v(TAG_TRANSITION, "Prepare open transition: prev=" + prev); - if (mNoAnimActivities.contains(next)) { + if (mStackSupervisor.mNoAnimActivities.contains(next)) { anim = false; mWindowManager.prepareAppTransition(TRANSIT_NONE, false); } else { @@ -2482,7 +2479,7 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai } } else { if (DEBUG_TRANSITION) Slog.v(TAG_TRANSITION, "Prepare open transition: no previous"); - if (mNoAnimActivities.contains(next)) { + if (mStackSupervisor.mNoAnimActivities.contains(next)) { anim = false; mWindowManager.prepareAppTransition(TRANSIT_NONE, false); } else { @@ -2490,17 +2487,14 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai } } - Bundle resumeAnimOptions = null; if (anim) { - ActivityOptions opts = next.getOptionsForTargetActivityLocked(); - if (opts != null) { - resumeAnimOptions = opts.toBundle(); - } next.applyOptionsLocked(); } else { next.clearOptionsLocked(); } + mStackSupervisor.mNoAnimActivities.clear(); + ActivityStack lastStack = mStackSupervisor.getLastStack(); if (next.app != null && next.app.thread != null) { if (DEBUG_SWITCH) Slog.v(TAG_SWITCH, "Resume running: " + next @@ -2856,7 +2850,7 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai "Prepare open transition: starting " + r); if ((r.intent.getFlags() & Intent.FLAG_ACTIVITY_NO_ANIMATION) != 0) { mWindowManager.prepareAppTransition(TRANSIT_NONE, keepCurTransition); - mNoAnimActivities.add(r); + mStackSupervisor.mNoAnimActivities.add(r); } else { int transit = TRANSIT_ACTIVITY_OPEN; if (newTask) { @@ -2875,7 +2869,7 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai } } mWindowManager.prepareAppTransition(transit, keepCurTransition); - mNoAnimActivities.remove(r); + mStackSupervisor.mNoAnimActivities.remove(r); } boolean doShow = true; if (newTask) { @@ -4494,7 +4488,7 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai if (noAnimation) { mWindowManager.prepareAppTransition(TRANSIT_NONE, false); if (r != null) { - mNoAnimActivities.add(r); + mStackSupervisor.mNoAnimActivities.add(r); } ActivityOptions.abort(options); } else { @@ -5187,7 +5181,6 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai void executeAppTransition(ActivityOptions options) { mWindowManager.executeAppTransition(); - mNoAnimActivities.clear(); ActivityOptions.abort(options); } diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java index 510a3fa47ec5..6beafcb94ead 100644 --- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java +++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java @@ -357,6 +357,12 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D * application */ final ArrayList<ActivityRecord> mPipModeChangedActivities = new ArrayList<>(); + /** + * Animations that for the current transition have requested not to + * be considered for the transition animation. + */ + final ArrayList<ActivityRecord> mNoAnimActivities = new ArrayList<>(); + /** The target stack bounds for the picture-in-picture mode changed that we need to report to * the application */ Rect mPipModeChangedTargetStackBounds; diff --git a/services/core/java/com/android/server/am/ActivityStartInterceptor.java b/services/core/java/com/android/server/am/ActivityStartInterceptor.java index 0480646d4b0e..b86a8a6f2a8d 100644 --- a/services/core/java/com/android/server/am/ActivityStartInterceptor.java +++ b/services/core/java/com/android/server/am/ActivityStartInterceptor.java @@ -17,6 +17,7 @@ package com.android.server.am; import static android.app.ActivityManager.INTENT_SENDER_ACTIVITY; +import static android.app.ActivityOptions.ANIM_OPEN_CROSS_PROFILE_APPS; import static android.app.PendingIntent.FLAG_CANCEL_CURRENT; import static android.app.PendingIntent.FLAG_IMMUTABLE; import static android.app.PendingIntent.FLAG_ONE_SHOT; @@ -41,6 +42,7 @@ import android.content.pm.ActivityInfo; import android.content.pm.ResolveInfo; import android.content.pm.UserInfo; import android.os.Binder; +import android.os.Bundle; import android.os.RemoteException; import android.os.UserHandle; import android.os.UserManager; @@ -119,11 +121,12 @@ class ActivityStartInterceptor { } private IntentSender createIntentSenderForOriginalIntent(int callingUid, int flags) { + Bundle activityOptions = deferCrossProfileAppsAnimationIfNecessary(); final IIntentSender target = mService.getIntentSenderLocked( INTENT_SENDER_ACTIVITY, mCallingPackage, callingUid, mUserId, null /*token*/, null /*resultCode*/, 0 /*requestCode*/, new Intent[] { mIntent }, new String[] { mResolvedType }, - flags, null /*bOptions*/); + flags, activityOptions); return new IntentSender(target); } @@ -164,11 +167,27 @@ class ActivityStartInterceptor { return interceptWorkProfileChallengeIfNeeded(); } + /** + * If the activity option is the {@link ActivityOptions#ANIM_OPEN_CROSS_PROFILE_APPS} one, + * defer the animation until the original intent is started. + * + * @return the activity option used to start the original intent. + */ + private Bundle deferCrossProfileAppsAnimationIfNecessary() { + if (mActivityOptions != null + && mActivityOptions.getAnimationType() == ANIM_OPEN_CROSS_PROFILE_APPS) { + mActivityOptions = null; + return ActivityOptions.makeOpenCrossProfileAppsAnimation().toBundle(); + } + return null; + } + private boolean interceptQuietProfileIfNeeded() { // Do not intercept if the user has not turned off the profile if (!mUserManager.isQuietModeEnabled(UserHandle.of(mUserId))) { return false; } + IntentSender target = createIntentSenderForOriginalIntent(mCallingUid, FLAG_CANCEL_CURRENT | FLAG_ONE_SHOT); @@ -210,8 +229,7 @@ class ActivityStartInterceptor { } private boolean interceptWorkProfileChallengeIfNeeded() { - final Intent interceptingIntent = interceptWithConfirmCredentialsIfNeeded(mIntent, - mResolvedType, mAInfo, mCallingPackage, mUserId); + final Intent interceptingIntent = interceptWithConfirmCredentialsIfNeeded(mAInfo, mUserId); if (interceptingIntent == null) { return false; } @@ -248,8 +266,7 @@ class ActivityStartInterceptor { * * @return The intercepting intent if needed. */ - private Intent interceptWithConfirmCredentialsIfNeeded(Intent intent, String resolvedType, - ActivityInfo aInfo, String callingPackage, int userId) { + private Intent interceptWithConfirmCredentialsIfNeeded(ActivityInfo aInfo, int userId) { if (!mUserController.shouldConfirmCredentials(userId)) { return null; } @@ -296,5 +313,4 @@ class ActivityStartInterceptor { mAInfo = mSupervisor.resolveActivity(mIntent, mRInfo, mStartFlags, null /*profilerInfo*/); return true; } - } diff --git a/services/core/java/com/android/server/am/AppWarnings.java b/services/core/java/com/android/server/am/AppWarnings.java index 806e95d71cd1..4b43bd9db201 100644 --- a/services/core/java/com/android/server/am/AppWarnings.java +++ b/services/core/java/com/android/server/am/AppWarnings.java @@ -81,7 +81,7 @@ class AppWarnings { mUiContext = uiContext; mAmsHandler = new ConfigHandler(amsHandler.getLooper()); mUiHandler = new UiHandler(uiHandler.getLooper()); - mConfigFile = new AtomicFile(new File(systemDir, CONFIG_FILE_NAME)); + mConfigFile = new AtomicFile(new File(systemDir, CONFIG_FILE_NAME), "warnings-config"); readConfigFromFileAmsThread(); } diff --git a/services/core/java/com/android/server/am/CompatModePackages.java b/services/core/java/com/android/server/am/CompatModePackages.java index 65c4a42108ec..d84f4879b00f 100644 --- a/services/core/java/com/android/server/am/CompatModePackages.java +++ b/services/core/java/com/android/server/am/CompatModePackages.java @@ -82,7 +82,7 @@ public final class CompatModePackages { public CompatModePackages(ActivityManagerService service, File systemDir, Handler handler) { mService = service; - mFile = new AtomicFile(new File(systemDir, "packages-compat.xml")); + mFile = new AtomicFile(new File(systemDir, "packages-compat.xml"), "compat-mode"); mHandler = new CompatHandler(handler.getLooper()); FileInputStream fis = null; diff --git a/services/core/java/com/android/server/am/EventLogTags.logtags b/services/core/java/com/android/server/am/EventLogTags.logtags index a131db5c73b0..cb2957daee65 100644 --- a/services/core/java/com/android/server/am/EventLogTags.logtags +++ b/services/core/java/com/android/server/am/EventLogTags.logtags @@ -102,7 +102,7 @@ option java_package com.android.server.am # Report collection of global memory state 30046 am_meminfo (Cached|2|2),(Free|2|2),(Zram|2|2),(Kernel|2|2),(Native|2|2) # Report collection of memory used by a process -30047 am_pss (Pid|1|5),(UID|1|5),(Process Name|3),(Pss|2|2),(Uss|2|2),(SwapPss|2|2) +30047 am_pss (Pid|1|5),(UID|1|5),(Process Name|3),(Pss|2|2),(Uss|2|2),(SwapPss|2|2),(Rss|2|2),(StatType|1|5),(ProcState|1|5),(TimeToCollect|2|2) # Attempting to stop an activity 30048 am_stop_activity (User|1|5),(Token|1|5),(Component Name|3) diff --git a/services/core/java/com/android/server/am/KeyguardController.java b/services/core/java/com/android/server/am/KeyguardController.java index 79f3fe3ffd9f..05305f300e61 100644 --- a/services/core/java/com/android/server/am/KeyguardController.java +++ b/services/core/java/com/android/server/am/KeyguardController.java @@ -384,4 +384,8 @@ class KeyguardController { proto.write(KEYGUARD_OCCLUDED, mOccluded); proto.end(token); } + + public void notifyAppTransitionDone() { + setKeyguardGoingAway(false); + } } diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java index a50d069f4fef..08ee23723df4 100644 --- a/services/core/java/com/android/server/am/ProcessList.java +++ b/services/core/java/com/android/server/am/ProcessList.java @@ -21,6 +21,7 @@ import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NA import java.io.IOException; import java.io.OutputStream; +import java.io.PrintWriter; import java.nio.ByteBuffer; import android.app.ActivityManager; @@ -482,7 +483,7 @@ public final class ProcessList { public static final int PSS_MIN_TIME_FROM_STATE_CHANGE = 15*1000; // The maximum amount of time we want to go between PSS collections. - public static final int PSS_MAX_INTERVAL = 40*60*1000; + public static final int PSS_MAX_INTERVAL = 60*60*1000; // The minimum amount of time between successive PSS requests for *all* processes. public static final int PSS_ALL_INTERVAL = 20*60*1000; @@ -497,7 +498,10 @@ public final class ProcessList { private static final int PSS_FIRST_BACKGROUND_INTERVAL = 20*1000; // The amount of time until PSS when a process first becomes cached. - private static final int PSS_FIRST_CACHED_INTERVAL = 30*1000; + private static final int PSS_FIRST_CACHED_INTERVAL = 20*1000; + + // The amount of time until PSS when an important process stays in the same state. + private static final int PSS_SAME_PERSISTENT_INTERVAL = 20*60*1000; // The amount of time until PSS when the top process stays in the same state. private static final int PSS_SAME_TOP_INTERVAL = 5*60*1000; @@ -509,7 +513,7 @@ public final class ProcessList { private static final int PSS_SAME_SERVICE_INTERVAL = 20*60*1000; // The amount of time until PSS when a cached process stays in the same state. - private static final int PSS_SAME_CACHED_INTERVAL = 30*60*1000; + private static final int PSS_SAME_CACHED_INTERVAL = 20*60*1000; // The amount of time until PSS when a persistent process first appears. private static final int PSS_FIRST_ASLEEP_PERSISTENT_INTERVAL = 1*60*1000; @@ -543,7 +547,9 @@ public final class ProcessList { public static final int PROC_MEM_IMPORTANT = 2; public static final int PROC_MEM_SERVICE = 3; public static final int PROC_MEM_CACHED = 4; + public static final int PROC_MEM_NUM = 5; + // Map large set of system process states to private static final int[] sProcStateToProcMem = new int[] { PROC_MEM_PERSISTENT, // ActivityManager.PROCESS_STATE_PERSISTENT PROC_MEM_PERSISTENT, // ActivityManager.PROCESS_STATE_PERSISTENT_UI @@ -567,138 +573,96 @@ public final class ProcessList { }; private static final long[] sFirstAwakePssTimes = new long[] { - PSS_FIRST_PERSISTENT_INTERVAL, // ActivityManager.PROCESS_STATE_PERSISTENT - PSS_FIRST_PERSISTENT_INTERVAL, // ActivityManager.PROCESS_STATE_PERSISTENT_UI - PSS_FIRST_TOP_INTERVAL, // ActivityManager.PROCESS_STATE_TOP - PSS_FIRST_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE - PSS_FIRST_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE - PSS_FIRST_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND - PSS_FIRST_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND - PSS_FIRST_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND - PSS_FIRST_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_BACKUP - PSS_FIRST_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_SERVICE - PSS_FIRST_CACHED_INTERVAL, // ActivityManager.PROCESS_STATE_RECEIVER - PSS_FIRST_CACHED_INTERVAL, // ActivityManager.PROCESS_STATE_TOP_SLEEPING - PSS_FIRST_CACHED_INTERVAL, // ActivityManager.PROCESS_STATE_HEAVY_WEIGHT - PSS_FIRST_CACHED_INTERVAL, // ActivityManager.PROCESS_STATE_HOME - PSS_FIRST_CACHED_INTERVAL, // ActivityManager.PROCESS_STATE_LAST_ACTIVITY - PSS_FIRST_CACHED_INTERVAL, // ActivityManager.PROCESS_STATE_CACHED_ACTIVITY - PSS_FIRST_CACHED_INTERVAL, // ActivityManager.PROCESS_STATE_CACHED_ACTIVITY_CLIENT - PSS_FIRST_CACHED_INTERVAL, // ActivityManager.PROCESS_STATE_CACHED_RECENT - PSS_FIRST_CACHED_INTERVAL, // ActivityManager.PROCESS_STATE_CACHED_EMPTY + PSS_FIRST_PERSISTENT_INTERVAL, // PROC_MEM_PERSISTENT + PSS_FIRST_TOP_INTERVAL, // PROC_MEM_TOP + PSS_FIRST_BACKGROUND_INTERVAL, // PROC_MEM_IMPORTANT + PSS_FIRST_BACKGROUND_INTERVAL, // PROC_MEM_SERVICE + PSS_FIRST_CACHED_INTERVAL, // PROC_MEM_CACHED }; private static final long[] sSameAwakePssTimes = new long[] { - PSS_SAME_IMPORTANT_INTERVAL, // ActivityManager.PROCESS_STATE_PERSISTENT - PSS_SAME_IMPORTANT_INTERVAL, // ActivityManager.PROCESS_STATE_PERSISTENT_UI - PSS_SAME_TOP_INTERVAL, // ActivityManager.PROCESS_STATE_TOP - PSS_SAME_IMPORTANT_INTERVAL, // ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE - PSS_SAME_IMPORTANT_INTERVAL, // ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE - PSS_SAME_IMPORTANT_INTERVAL, // ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND - PSS_SAME_IMPORTANT_INTERVAL, // ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND - PSS_SAME_IMPORTANT_INTERVAL, // ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND - PSS_SAME_IMPORTANT_INTERVAL, // ActivityManager.PROCESS_STATE_BACKUP - PSS_SAME_SERVICE_INTERVAL, // ActivityManager.PROCESS_STATE_SERVICE - PSS_SAME_SERVICE_INTERVAL, // ActivityManager.PROCESS_STATE_RECEIVER - PSS_SAME_CACHED_INTERVAL, // ActivityManager.PROCESS_STATE_TOP_SLEEPING - PSS_SAME_CACHED_INTERVAL, // ActivityManager.PROCESS_STATE_HEAVY_WEIGHT - PSS_SAME_CACHED_INTERVAL, // ActivityManager.PROCESS_STATE_HOME - PSS_SAME_CACHED_INTERVAL, // ActivityManager.PROCESS_STATE_LAST_ACTIVITY - PSS_SAME_CACHED_INTERVAL, // ActivityManager.PROCESS_STATE_CACHED_ACTIVITY - PSS_SAME_CACHED_INTERVAL, // ActivityManager.PROCESS_STATE_CACHED_ACTIVITY_CLIENT - PSS_SAME_CACHED_INTERVAL, // ActivityManager.PROCESS_STATE_CACHED_RECENT - PSS_SAME_CACHED_INTERVAL, // ActivityManager.PROCESS_STATE_CACHED_EMPTY + PSS_SAME_PERSISTENT_INTERVAL, // PROC_MEM_PERSISTENT + PSS_SAME_TOP_INTERVAL, // PROC_MEM_TOP + PSS_SAME_IMPORTANT_INTERVAL, // PROC_MEM_IMPORTANT + PSS_SAME_SERVICE_INTERVAL, // PROC_MEM_SERVICE + PSS_SAME_CACHED_INTERVAL, // PROC_MEM_CACHED }; private static final long[] sFirstAsleepPssTimes = new long[] { - PSS_FIRST_ASLEEP_PERSISTENT_INTERVAL, // ActivityManager.PROCESS_STATE_PERSISTENT - PSS_FIRST_ASLEEP_PERSISTENT_INTERVAL, // ActivityManager.PROCESS_STATE_PERSISTENT_UI - PSS_FIRST_ASLEEP_TOP_INTERVAL, // ActivityManager.PROCESS_STATE_TOP - PSS_FIRST_ASLEEP_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE - PSS_FIRST_ASLEEP_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE - PSS_FIRST_ASLEEP_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND - PSS_FIRST_ASLEEP_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND - PSS_FIRST_ASLEEP_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND - PSS_FIRST_ASLEEP_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_BACKUP - PSS_FIRST_ASLEEP_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_SERVICE - PSS_FIRST_ASLEEP_CACHED_INTERVAL, // ActivityManager.PROCESS_STATE_RECEIVER - PSS_FIRST_ASLEEP_CACHED_INTERVAL, // ActivityManager.PROCESS_STATE_TOP_SLEEPING - PSS_FIRST_ASLEEP_CACHED_INTERVAL, // ActivityManager.PROCESS_STATE_HEAVY_WEIGHT - PSS_FIRST_ASLEEP_CACHED_INTERVAL, // ActivityManager.PROCESS_STATE_HOME - PSS_FIRST_ASLEEP_CACHED_INTERVAL, // ActivityManager.PROCESS_STATE_LAST_ACTIVITY - PSS_FIRST_ASLEEP_CACHED_INTERVAL, // ActivityManager.PROCESS_STATE_CACHED_ACTIVITY - PSS_FIRST_ASLEEP_CACHED_INTERVAL, // ActivityManager.PROCESS_STATE_CACHED_ACTIVITY_CLIENT - PSS_FIRST_ASLEEP_CACHED_INTERVAL, // ActivityManager.PROCESS_STATE_CACHED_RECENT - PSS_FIRST_ASLEEP_CACHED_INTERVAL, // ActivityManager.PROCESS_STATE_CACHED_EMPTY + PSS_FIRST_ASLEEP_PERSISTENT_INTERVAL, // PROC_MEM_PERSISTENT + PSS_FIRST_ASLEEP_TOP_INTERVAL, // PROC_MEM_TOP + PSS_FIRST_ASLEEP_BACKGROUND_INTERVAL, // PROC_MEM_IMPORTANT + PSS_FIRST_ASLEEP_BACKGROUND_INTERVAL, // PROC_MEM_SERVICE + PSS_FIRST_ASLEEP_CACHED_INTERVAL, // PROC_MEM_CACHED }; private static final long[] sSameAsleepPssTimes = new long[] { - PSS_SAME_IMPORTANT_INTERVAL, // ActivityManager.PROCESS_STATE_PERSISTENT - PSS_SAME_IMPORTANT_INTERVAL, // ActivityManager.PROCESS_STATE_PERSISTENT_UI - PSS_SAME_TOP_INTERVAL, // ActivityManager.PROCESS_STATE_TOP - PSS_SAME_IMPORTANT_INTERVAL, // ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE - PSS_SAME_IMPORTANT_INTERVAL, // ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE - PSS_SAME_IMPORTANT_INTERVAL, // ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE - PSS_SAME_IMPORTANT_INTERVAL, // ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND - PSS_SAME_IMPORTANT_INTERVAL, // ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND - PSS_SAME_IMPORTANT_INTERVAL, // ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND - PSS_SAME_IMPORTANT_INTERVAL, // ActivityManager.PROCESS_STATE_BACKUP - PSS_SAME_SERVICE_INTERVAL, // ActivityManager.PROCESS_STATE_SERVICE - PSS_SAME_SERVICE_INTERVAL, // ActivityManager.PROCESS_STATE_RECEIVER - PSS_SAME_CACHED_INTERVAL, // ActivityManager.PROCESS_STATE_TOP_SLEEPING - PSS_SAME_CACHED_INTERVAL, // ActivityManager.PROCESS_STATE_HEAVY_WEIGHT - PSS_SAME_CACHED_INTERVAL, // ActivityManager.PROCESS_STATE_HOME - PSS_SAME_CACHED_INTERVAL, // ActivityManager.PROCESS_STATE_LAST_ACTIVITY - PSS_SAME_CACHED_INTERVAL, // ActivityManager.PROCESS_STATE_CACHED_ACTIVITY - PSS_SAME_CACHED_INTERVAL, // ActivityManager.PROCESS_STATE_CACHED_ACTIVITY_CLIENT - PSS_SAME_CACHED_INTERVAL, // ActivityManager.PROCESS_STATE_CACHED_RECENT - PSS_SAME_CACHED_INTERVAL, // ActivityManager.PROCESS_STATE_CACHED_EMPTY + PSS_SAME_PERSISTENT_INTERVAL, // PROC_MEM_PERSISTENT + PSS_SAME_TOP_INTERVAL, // PROC_MEM_TOP + PSS_SAME_IMPORTANT_INTERVAL, // PROC_MEM_IMPORTANT + PSS_SAME_SERVICE_INTERVAL, // PROC_MEM_SERVICE + PSS_SAME_CACHED_INTERVAL, // PROC_MEM_CACHED }; private static final long[] sTestFirstPssTimes = new long[] { - PSS_TEST_FIRST_TOP_INTERVAL, // ActivityManager.PROCESS_STATE_PERSISTENT - PSS_TEST_FIRST_TOP_INTERVAL, // ActivityManager.PROCESS_STATE_PERSISTENT_UI - PSS_TEST_FIRST_TOP_INTERVAL, // ActivityManager.PROCESS_STATE_TOP - PSS_TEST_FIRST_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE - PSS_TEST_FIRST_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE - PSS_TEST_FIRST_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND - PSS_TEST_FIRST_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND - PSS_TEST_FIRST_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND - PSS_TEST_FIRST_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_BACKUP - PSS_TEST_FIRST_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_SERVICE - PSS_TEST_FIRST_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_RECEIVER - PSS_TEST_FIRST_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_TOP_SLEEPING - PSS_TEST_FIRST_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_HEAVY_WEIGHT - PSS_TEST_FIRST_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_HOME - PSS_TEST_FIRST_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_LAST_ACTIVITY - PSS_TEST_FIRST_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_CACHED_ACTIVITY - PSS_TEST_FIRST_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_CACHED_ACTIVITY_CLIENT - PSS_TEST_FIRST_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_CACHED_RECENT - PSS_TEST_FIRST_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_CACHED_EMPTY + PSS_TEST_FIRST_TOP_INTERVAL, // PROC_MEM_PERSISTENT + PSS_TEST_FIRST_TOP_INTERVAL, // PROC_MEM_TOP + PSS_TEST_FIRST_BACKGROUND_INTERVAL, // PROC_MEM_IMPORTANT + PSS_TEST_FIRST_BACKGROUND_INTERVAL, // PROC_MEM_SERVICE + PSS_TEST_FIRST_BACKGROUND_INTERVAL, // PROC_MEM_CACHED }; private static final long[] sTestSamePssTimes = new long[] { - PSS_TEST_SAME_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_PERSISTENT - PSS_TEST_SAME_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_PERSISTENT_UI - PSS_TEST_SAME_IMPORTANT_INTERVAL, // ActivityManager.PROCESS_STATE_TOP - PSS_TEST_SAME_IMPORTANT_INTERVAL, // ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE - PSS_TEST_SAME_IMPORTANT_INTERVAL, // ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE - PSS_TEST_SAME_IMPORTANT_INTERVAL, // ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND - PSS_TEST_SAME_IMPORTANT_INTERVAL, // ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND - PSS_TEST_SAME_IMPORTANT_INTERVAL, // ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND - PSS_TEST_SAME_IMPORTANT_INTERVAL, // ActivityManager.PROCESS_STATE_BACKUP - PSS_TEST_SAME_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_SERVICE - PSS_TEST_SAME_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_RECEIVER - PSS_TEST_SAME_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_TOP_SLEEPING - PSS_TEST_SAME_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_HEAVY_WEIGHT - PSS_TEST_SAME_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_HOME - PSS_TEST_SAME_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_LAST_ACTIVITY - PSS_TEST_SAME_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_CACHED_ACTIVITY - PSS_TEST_SAME_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_CACHED_ACTIVITY_CLIENT - PSS_TEST_SAME_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_CACHED_RECENT - PSS_TEST_SAME_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_CACHED_EMPTY + PSS_TEST_SAME_BACKGROUND_INTERVAL, // PROC_MEM_PERSISTENT + PSS_TEST_SAME_IMPORTANT_INTERVAL, // PROC_MEM_TOP + PSS_TEST_SAME_IMPORTANT_INTERVAL, // PROC_MEM_IMPORTANT + PSS_TEST_SAME_BACKGROUND_INTERVAL, // PROC_MEM_SERVICE + PSS_TEST_SAME_BACKGROUND_INTERVAL, // PROC_MEM_CACHED }; + public static final class ProcStateMemTracker { + final int[] mHighestMem = new int[PROC_MEM_NUM]; + int mTotalHighestMem = PROC_MEM_CACHED; + float mCurFactor = 1.0f; + + int mPendingMemState; + int mPendingHighestMemState; + boolean mPendingSame; + + public ProcStateMemTracker() { + for (int i = PROC_MEM_PERSISTENT; i < PROC_MEM_NUM; i++) { + mHighestMem[i] = PROC_MEM_NUM; + } + mPendingMemState = -1; + } + + public void dumpLine(PrintWriter pw) { + pw.print("best="); + pw.print(mTotalHighestMem); + pw.print(" "); + pw.print(mCurFactor); + pw.print("x ("); + for (int i = 0; i < PROC_MEM_NUM; i++) { + if (i != 0) { + pw.print(", "); + } + pw.print(i); + pw.print("="); + pw.print(mHighestMem[i]); + } + pw.print(")"); + if (mPendingMemState >= 0) { + pw.print(" / pending state="); + pw.print(mPendingMemState); + pw.print(" highest="); + pw.print(mPendingHighestMemState); + pw.print(" same="); + pw.print(mPendingSame); + } + pw.println(); + } + } + public static boolean procStatesDifferForMem(int procState1, int procState2) { return sProcStateToProcMem[procState1] != sProcStateToProcMem[procState2]; } @@ -707,16 +671,50 @@ public final class ProcessList { return test ? PSS_TEST_MIN_TIME_FROM_STATE_CHANGE : PSS_MIN_TIME_FROM_STATE_CHANGE; } - public static long computeNextPssTime(int procState, boolean first, boolean test, + public static void commitNextPssTime(ProcStateMemTracker tracker) { + if (tracker.mPendingMemState >= 0) { + tracker.mHighestMem[tracker.mPendingMemState] = tracker.mPendingHighestMemState; + tracker.mTotalHighestMem = tracker.mPendingHighestMemState; + if (tracker.mPendingSame) { + tracker.mCurFactor *= 1.5f; + } else { + tracker.mCurFactor = 1; + } + tracker.mPendingMemState = -1; + } + } + + public static void abortNextPssTime(ProcStateMemTracker tracker) { + tracker.mPendingMemState = -1; + } + + public static long computeNextPssTime(int procState, ProcStateMemTracker tracker, boolean test, boolean sleeping, long now) { + boolean first; + final int memState = sProcStateToProcMem[procState]; + if (tracker != null) { + final int highestMemState = memState < tracker.mTotalHighestMem + ? memState : tracker.mTotalHighestMem; + first = highestMemState < tracker.mHighestMem[memState]; + tracker.mPendingMemState = memState; + tracker.mPendingHighestMemState = highestMemState; + tracker.mPendingSame = !first; + } else { + first = true; + } final long[] table = test ? (first - ? sTestFirstPssTimes - : sTestSamePssTimes) + ? sTestFirstPssTimes + : sTestSamePssTimes) : (first - ? (sleeping ? sFirstAsleepPssTimes : sFirstAwakePssTimes) - : (sleeping ? sSameAsleepPssTimes : sSameAwakePssTimes)); - return now + table[procState]; + ? (sleeping ? sFirstAsleepPssTimes : sFirstAwakePssTimes) + : (sleeping ? sSameAsleepPssTimes : sSameAwakePssTimes)); + long delay = (long)(table[memState] * (tracker != null && !first + ? tracker.mCurFactor : 1.0f)); + if (delay > PSS_MAX_INTERVAL) { + delay = PSS_MAX_INTERVAL; + } + return now + delay; } long getMemLevel(int adjustment) { diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java index 03e140de4ee0..1f6075530412 100644 --- a/services/core/java/com/android/server/am/ProcessRecord.java +++ b/services/core/java/com/android/server/am/ProcessRecord.java @@ -66,6 +66,8 @@ final class ProcessRecord { final String processName; // name of the process // List of packages running in the process final ArrayMap<String, ProcessStats.ProcessStateHolder> pkgList = new ArrayMap<>(); + final ProcessList.ProcStateMemTracker procStateMemTracker + = new ProcessList.ProcStateMemTracker(); UidRecord uidRecord; // overall state of process's uid. ArraySet<String> pkgDeps; // additional packages we have a dependency on IApplicationThread thread; // the actual proc... may be null only if @@ -102,6 +104,7 @@ final class ProcessRecord { int repProcState = PROCESS_STATE_NONEXISTENT; // Last reported process state int setProcState = PROCESS_STATE_NONEXISTENT; // Last set process state in process tracker int pssProcState = PROCESS_STATE_NONEXISTENT; // Currently requesting pss for + int pssStatType; // The type of stat collection that we are currently requesting int savedPriority; // Previous priority value if we're switching to non-SCHED_OTHER int renderThreadTid; // TID for RenderThread boolean serviceb; // Process currently is on the service B list @@ -285,6 +288,7 @@ final class ProcessRecord { TimeUtils.formatDuration(lastActivityTime, nowUptime, pw); pw.print(" lastPssTime="); TimeUtils.formatDuration(lastPssTime, nowUptime, pw); + pw.print(" pssStatType="); pw.print(pssStatType); pw.print(" nextPssTime="); TimeUtils.formatDuration(nextPssTime, nowUptime, pw); pw.println(); @@ -295,6 +299,8 @@ final class ProcessRecord { pw.print(" lastCachedPss="); DebugUtils.printSizeValue(pw, lastCachedPss*1024); pw.print(" lastCachedSwapPss="); DebugUtils.printSizeValue(pw, lastCachedSwapPss*1024); pw.println(); + pw.print(prefix); pw.print("procStateMemTracker: "); + procStateMemTracker.dumpLine(pw); pw.print(prefix); pw.print("cached="); pw.print(cached); pw.print(" empty="); pw.println(empty); if (serviceb) { diff --git a/services/core/java/com/android/server/am/ProcessStatsService.java b/services/core/java/com/android/server/am/ProcessStatsService.java index af42997131d3..8bf320e0c387 100644 --- a/services/core/java/com/android/server/am/ProcessStatsService.java +++ b/services/core/java/com/android/server/am/ProcessStatsService.java @@ -220,8 +220,9 @@ public final class ProcessStatsService extends IProcessStats.Stub { } public void writeStateLocked(boolean sync, final boolean commit) { + final long totalTime; synchronized (mPendingWriteLock) { - long now = SystemClock.uptimeMillis(); + final long now = SystemClock.uptimeMillis(); if (mPendingWrite == null || !mPendingWriteCommitted) { mPendingWrite = Parcel.obtain(); mProcessStats.mTimePeriodEndRealtime = SystemClock.elapsedRealtime(); @@ -238,19 +239,19 @@ public final class ProcessStatsService extends IProcessStats.Stub { updateFile(); } mLastWriteTime = SystemClock.uptimeMillis(); - if (DEBUG) Slog.d(TAG, "Prepared write state in " - + (SystemClock.uptimeMillis()-now) + "ms"); + totalTime = SystemClock.uptimeMillis() - now; + if (DEBUG) Slog.d(TAG, "Prepared write state in " + now + "ms"); if (!sync) { BackgroundThread.getHandler().post(new Runnable() { @Override public void run() { - performWriteState(); + performWriteState(totalTime); } }); return; } } - performWriteState(); + performWriteState(totalTime); } private void updateFile() { @@ -259,7 +260,7 @@ public final class ProcessStatsService extends IProcessStats.Stub { mLastWriteTime = SystemClock.uptimeMillis(); } - void performWriteState() { + void performWriteState(long initialTime) { if (DEBUG) Slog.d(TAG, "Performing write to " + mFile.getBaseFile()); Parcel data; AtomicFile file; @@ -275,12 +276,15 @@ public final class ProcessStatsService extends IProcessStats.Stub { mWriteLock.lock(); } + final long startTime = SystemClock.uptimeMillis(); FileOutputStream stream = null; try { stream = file.startWrite(); stream.write(data.marshall()); stream.flush(); file.finishWrite(stream); + com.android.internal.logging.EventLogTags.writeCommitSysConfigFile( + "procstats", SystemClock.uptimeMillis() - startTime + initialTime); if (DEBUG) Slog.d(TAG, "Write completed successfully!"); } catch (IOException e) { Slog.w(TAG, "Error writing process statistics", e); diff --git a/services/core/java/com/android/server/am/RecentsAnimation.java b/services/core/java/com/android/server/am/RecentsAnimation.java index fe576fdaacbe..e7b067b1ab73 100644 --- a/services/core/java/com/android/server/am/RecentsAnimation.java +++ b/services/core/java/com/android/server/am/RecentsAnimation.java @@ -70,55 +70,61 @@ class RecentsAnimation implements RecentsAnimationCallbacks { void startRecentsActivity(Intent intent, IRecentsAnimationRunner recentsAnimationRunner, ComponentName recentsComponent, int recentsUid) { + mWindowManager.deferSurfaceLayout(); + try { + // Cancel the previous recents animation if necessary + mWindowManager.cancelRecentsAnimation(); - // Cancel the previous recents animation if necessary - mWindowManager.cancelRecentsAnimation(); - - final boolean hasExistingHomeActivity = mStackSupervisor.getHomeActivity() != null; - if (!hasExistingHomeActivity) { - // No home activity - final ActivityOptions opts = ActivityOptions.makeBasic(); - opts.setLaunchActivityType(ACTIVITY_TYPE_HOME); - opts.setAvoidMoveToFront(); - intent.addFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_NO_ANIMATION); - - mActivityStartController.obtainStarter(intent, "startRecentsActivity_noHomeActivity") - .setCallingUid(recentsUid) - .setCallingPackage(recentsComponent.getPackageName()) - .setActivityOptions(SafeActivityOptions.fromBundle(opts.toBundle())) - .setMayWait(mUserController.getCurrentUserId()) - .execute(); - mWindowManager.prepareAppTransition(TRANSIT_NONE, false); - - // TODO: Maybe wait for app to draw in this particular case? - } + final boolean hasExistingHomeActivity = mStackSupervisor.getHomeActivity() != null; + if (!hasExistingHomeActivity) { + // No home activity + final ActivityOptions opts = ActivityOptions.makeBasic(); + opts.setLaunchActivityType(ACTIVITY_TYPE_HOME); + opts.setAvoidMoveToFront(); + intent.addFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_NO_ANIMATION); + + mActivityStartController + .obtainStarter(intent, "startRecentsActivity_noHomeActivity") + .setCallingUid(recentsUid) + .setCallingPackage(recentsComponent.getPackageName()) + .setActivityOptions(SafeActivityOptions.fromBundle(opts.toBundle())) + .setMayWait(mUserController.getCurrentUserId()) + .execute(); + mWindowManager.prepareAppTransition(TRANSIT_NONE, false); - final ActivityRecord homeActivity = mStackSupervisor.getHomeActivity(); - final ActivityDisplay display = homeActivity.getDisplay(); + // TODO: Maybe wait for app to draw in this particular case? + } - // Save the initial position of the home activity stack to be restored to after the - // animation completes - mRestoreHomeBehindStack = hasExistingHomeActivity - ? display.getStackAboveHome() - : null; + final ActivityRecord homeActivity = mStackSupervisor.getHomeActivity(); + final ActivityDisplay display = homeActivity.getDisplay(); - // Move the home activity into place for the animation - display.moveHomeStackBehindBottomMostVisibleStack(); + // Save the initial position of the home activity stack to be restored to after the + // animation completes + mRestoreHomeBehindStack = hasExistingHomeActivity + ? display.getStackAboveHome() + : null; - // Mark the home activity as launch-behind to bump its visibility for the - // duration of the gesture that is driven by the recents component - homeActivity.mLaunchTaskBehind = true; + // Move the home activity into place for the animation + display.moveHomeStackBehindBottomMostVisibleStack(); - // Fetch all the surface controls and pass them to the client to get the animation - // started - mWindowManager.initializeRecentsAnimation(recentsAnimationRunner, this, display.mDisplayId); + // Mark the home activity as launch-behind to bump its visibility for the + // duration of the gesture that is driven by the recents component + homeActivity.mLaunchTaskBehind = true; - // If we updated the launch-behind state, update the visibility of the activities after we - // fetch the visible tasks to be controlled by the animation - mStackSupervisor.ensureActivitiesVisibleLocked(null, 0, PRESERVE_WINDOWS); + // Fetch all the surface controls and pass them to the client to get the animation + // started + mWindowManager.initializeRecentsAnimation(recentsAnimationRunner, this, + display.mDisplayId); - // Post a timeout for the animation - mHandler.postDelayed(mCancelAnimationRunnable, RECENTS_ANIMATION_TIMEOUT); + // If we updated the launch-behind state, update the visibility of the activities after + // we fetch the visible tasks to be controlled by the animation + mStackSupervisor.ensureActivitiesVisibleLocked(null, 0, PRESERVE_WINDOWS); + + // Post a timeout for the animation + mHandler.postDelayed(mCancelAnimationRunnable, RECENTS_ANIMATION_TIMEOUT); + } finally { + mWindowManager.continueSurfaceLayout(); + } } @Override @@ -128,31 +134,40 @@ class RecentsAnimation implements RecentsAnimationCallbacks { if (mWindowManager.getRecentsAnimationController() == null) return; mWindowManager.inSurfaceTransaction(() -> { - mWindowManager.cleanupRecentsAnimation(); - - // Move the home stack to the front - final ActivityRecord homeActivity = mStackSupervisor.getHomeActivity(); - if (homeActivity == null) { - return; - } - - // Restore the launched-behind state - homeActivity.mLaunchTaskBehind = false; - - if (moveHomeToTop) { - // Bring the home stack to the front - final ActivityStack homeStack = homeActivity.getStack(); - homeStack.mNoAnimActivities.add(homeActivity); - homeStack.moveToFront("RecentsAnimation.onAnimationFinished()"); - } else { - // Restore the home stack to its previous position - final ActivityDisplay display = homeActivity.getDisplay(); - display.moveHomeStackBehindStack(mRestoreHomeBehindStack); + mWindowManager.deferSurfaceLayout(); + try { + mWindowManager.cleanupRecentsAnimation(); + + // Move the home stack to the front + final ActivityRecord homeActivity = mStackSupervisor.getHomeActivity(); + if (homeActivity == null) { + return; + } + + // Restore the launched-behind state + homeActivity.mLaunchTaskBehind = false; + + if (moveHomeToTop) { + // Bring the home stack to the front + final ActivityStack homeStack = homeActivity.getStack(); + mStackSupervisor.mNoAnimActivities.add(homeActivity); + homeStack.moveToFront("RecentsAnimation.onAnimationFinished()"); + } else { + // Restore the home stack to its previous position + final ActivityDisplay display = homeActivity.getDisplay(); + display.moveHomeStackBehindStack(mRestoreHomeBehindStack); + } + + mWindowManager.prepareAppTransition(TRANSIT_NONE, false); + mStackSupervisor.ensureActivitiesVisibleLocked(null, 0, false); + mStackSupervisor.resumeFocusedStackTopActivityLocked(); + + // No reason to wait for the pausing activity in this case, as the hiding of + // surfaces needs to be done immediately. + mWindowManager.executeAppTransition(); + } finally { + mWindowManager.continueSurfaceLayout(); } - - mWindowManager.prepareAppTransition(TRANSIT_NONE, false); - mStackSupervisor.ensureActivitiesVisibleLocked(null, 0, false); - mStackSupervisor.resumeFocusedStackTopActivityLocked(); }); } } diff --git a/services/core/java/com/android/server/am/TaskRecord.java b/services/core/java/com/android/server/am/TaskRecord.java index 809f19f64317..d679439d3b7d 100644 --- a/services/core/java/com/android/server/am/TaskRecord.java +++ b/services/core/java/com/android/server/am/TaskRecord.java @@ -695,7 +695,7 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi wasPaused, reason); } if (!animate) { - toStack.mNoAnimActivities.add(topActivity); + mService.mStackSupervisor.mNoAnimActivities.add(topActivity); } // We might trigger a configuration change. Save the current task bounds for freezing. diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index edeee3e590ea..1825db8647bf 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -16,11 +16,6 @@ package com.android.server.audio; -import com.android.server.audio.AudioServiceEvents.ForceUseEvent; -import com.android.server.audio.AudioServiceEvents.PhoneStateEvent; -import com.android.server.audio.AudioServiceEvents.VolumeEvent; -import com.android.server.audio.AudioServiceEvents.WiredDevConnectEvent; - import static android.Manifest.permission.REMOTE_AUDIO_PLAYBACK; import static android.media.AudioManager.RINGER_MODE_NORMAL; import static android.media.AudioManager.RINGER_MODE_SILENT; @@ -64,14 +59,14 @@ import android.media.AudioAttributes; import android.media.AudioDevicePort; import android.media.AudioFocusInfo; import android.media.AudioFocusRequest; -import android.media.AudioSystem; import android.media.AudioFormat; import android.media.AudioManager; import android.media.AudioManagerInternal; -import android.media.AudioPort; import android.media.AudioPlaybackConfiguration; +import android.media.AudioPort; import android.media.AudioRecordingConfiguration; import android.media.AudioRoutesInfo; +import android.media.AudioSystem; import android.media.IAudioFocusDispatcher; import android.media.IAudioRoutesObserver; import android.media.IAudioService; @@ -80,12 +75,12 @@ import android.media.IRecordingConfigDispatcher; import android.media.IRingtonePlayer; import android.media.IVolumeController; import android.media.MediaPlayer; -import android.media.SoundPool; -import android.media.VolumePolicy; -import android.media.audiofx.AudioEffect; import android.media.MediaPlayer.OnCompletionListener; import android.media.MediaPlayer.OnErrorListener; import android.media.PlayerBase; +import android.media.SoundPool; +import android.media.VolumePolicy; +import android.media.audiofx.AudioEffect; import android.media.audiopolicy.AudioMix; import android.media.audiopolicy.AudioPolicy; import android.media.audiopolicy.AudioPolicyConfig; @@ -110,6 +105,7 @@ import android.os.UserManagerInternal.UserRestrictionsListener; import android.os.Vibrator; import android.provider.Settings; import android.provider.Settings.System; +import android.service.notification.ZenModeConfig; import android.telecom.TelecomManager; import android.text.TextUtils; import android.util.AndroidRuntimeException; @@ -129,6 +125,10 @@ import com.android.internal.util.XmlUtils; import com.android.server.EventLogTags; import com.android.server.LocalServices; import com.android.server.SystemService; +import com.android.server.audio.AudioServiceEvents.ForceUseEvent; +import com.android.server.audio.AudioServiceEvents.PhoneStateEvent; +import com.android.server.audio.AudioServiceEvents.VolumeEvent; +import com.android.server.audio.AudioServiceEvents.WiredDevConnectEvent; import com.android.server.pm.UserManagerService; import org.xmlpull.v1.XmlPullParserException; @@ -1329,8 +1329,20 @@ public class AudioService extends IAudioService.Stub /** @see AudioManager#adjustVolume(int, int) */ public void adjustSuggestedStreamVolume(int direction, int suggestedStreamType, int flags, String callingPackage, String caller) { - adjustSuggestedStreamVolume(direction, suggestedStreamType, flags, callingPackage, - caller, Binder.getCallingUid()); + final IAudioPolicyCallback extVolCtlr; + synchronized (mExtVolumeControllerLock) { + extVolCtlr = mExtVolumeController; + } + if (extVolCtlr != null) { + try { + mExtVolumeController.notifyVolumeAdjust(direction); + } catch(RemoteException e) { + // nothing we can do about this. Do not log error, too much potential for spam + } + } else { + adjustSuggestedStreamVolume(direction, suggestedStreamType, flags, callingPackage, + caller, Binder.getCallingUid()); + } } private void adjustSuggestedStreamVolume(int direction, int suggestedStreamType, int flags, @@ -1853,10 +1865,12 @@ public class AudioService extends IAudioService.Stub sendVolumeUpdate(streamType, oldIndex, index, flags); } - // No ringer affected streams can be changed in total silence mode except those that - // will cause the device to exit total silence mode. + // No ringer affected streams can be changed in total silence mode or priority-only + // (with alarms/media toggled off) except those that will cause the device to exit + // the mode. private boolean volumeAdjustmentAllowedByDnd(int streamTypeAlias, int flags) { - if (mNm.getZenMode() == Settings.Global.ZEN_MODE_NO_INTERRUPTIONS + if ((mNm.getZenMode() == Settings.Global.ZEN_MODE_NO_INTERRUPTIONS + || mNm.getZenMode() == Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS) && isStreamMutedByRingerMode(streamTypeAlias)) { if (!(((flags & AudioManager.FLAG_ALLOW_RINGER_MODES) != 0) || (streamTypeAlias == getUiSoundsStreamType()))) { @@ -2278,7 +2292,9 @@ public class AudioService extends IAudioService.Stub // only mute for the current user if (getCurrentUserId() == userId) { final boolean currentMute = AudioSystem.isMicrophoneMuted(); + final long identity = Binder.clearCallingIdentity(); AudioSystem.muteMicrophone(on); + Binder.restoreCallingIdentity(identity); if (on != currentMute) { mContext.sendBroadcast(new Intent(AudioManager.ACTION_MICROPHONE_MUTE_CHANGED) .setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY)); @@ -2381,11 +2397,30 @@ public class AudioService extends IAudioService.Stub // Unmute stream if previously muted by ringer mode and ringer mode // is RINGER_MODE_NORMAL or stream is not affected by ringer mode. int numStreamTypes = AudioSystem.getNumStreamTypes(); + + if (mNm == null) { + mNm = (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE); + } + + // in priority only dnd, alarms and media streams can be muted when ringer is not muted + boolean isZenPriorityMode = mNm.getZenMode() == + Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS; + NotificationManager.Policy zenPolicy = mNm.getNotificationPolicy(); + boolean muteAlarms = isZenPriorityMode && ((zenPolicy.priorityCategories + & NotificationManager.Policy.PRIORITY_CATEGORY_ALARMS) == 0); + boolean muteMedia = isZenPriorityMode && ((zenPolicy.priorityCategories + & NotificationManager.Policy.PRIORITY_CATEGORY_MEDIA_SYSTEM_OTHER) == 0); + final boolean ringerModeMute = mRingerMode == AudioManager.RINGER_MODE_VIBRATE || mRingerMode == AudioManager.RINGER_MODE_SILENT; + for (int streamType = numStreamTypes - 1; streamType >= 0; streamType--) { final boolean isMuted = isStreamMutedByRingerMode(streamType); - final boolean shouldMute = ringerModeMute && isStreamAffectedByRingerMode(streamType); + + final boolean shouldZenMute = (isAlarm(streamType) && muteAlarms) + || (isMedia(streamType) && muteMedia); + final boolean shouldMute = (shouldZenMute || ringerModeMute) + && isStreamAffectedByRingerMode(streamType); if (isMuted == shouldMute) continue; if (!shouldMute) { // unmute @@ -2421,6 +2456,19 @@ public class AudioService extends IAudioService.Stub } } + private boolean isAlarm(int streamType) { + return streamType == AudioSystem.STREAM_ALARM; + } + + private boolean isNotificationOrRinger(int streamType) { + return streamType == AudioSystem.STREAM_NOTIFICATION + || streamType == AudioSystem.STREAM_RING; + } + + private boolean isMedia(int streamType) { + return streamType == AudioSystem.STREAM_SYSTEM || streamType == AudioSystem.STREAM_MUSIC; + } + private void setRingerModeInt(int ringerMode, boolean persist) { final boolean change; synchronized(mSettingsLock) { @@ -2620,7 +2668,9 @@ public class AudioService extends IAudioService.Stub } if (actualMode != mMode) { + final long identity = Binder.clearCallingIdentity(); status = AudioSystem.setPhoneState(actualMode); + Binder.restoreCallingIdentity(identity); if (status == AudioSystem.AUDIO_STATUS_OK) { if (DEBUG_MODE) { Log.v(TAG, " mode successfully set to " + actualMode); } mMode = actualMode; @@ -6930,7 +6980,7 @@ public class AudioService extends IAudioService.Stub // Audio policy management //========================================================================================== public String registerAudioPolicy(AudioPolicyConfig policyConfig, IAudioPolicyCallback pcb, - boolean hasFocusListener, boolean isFocusPolicy) { + boolean hasFocusListener, boolean isFocusPolicy, boolean isVolumeController) { AudioSystem.setDynamicPolicyCallback(mDynPolicyCallback); if (DEBUG_AP) Log.d(TAG, "registerAudioPolicy for " + pcb.asBinder() @@ -6953,7 +7003,7 @@ public class AudioService extends IAudioService.Stub return null; } AudioPolicyProxy app = new AudioPolicyProxy(policyConfig, pcb, hasFocusListener, - isFocusPolicy); + isFocusPolicy, isVolumeController); pcb.asBinder().linkToDeath(app, 0/*flags*/); regId = app.getRegistrationId(); mAudioPolicies.put(pcb.asBinder(), app); @@ -7019,6 +7069,23 @@ public class AudioService extends IAudioService.Stub return AudioManager.SUCCESS; } + private final Object mExtVolumeControllerLock = new Object(); + private IAudioPolicyCallback mExtVolumeController; + private void setExtVolumeController(IAudioPolicyCallback apc) { + if (!mContext.getResources().getBoolean( + com.android.internal.R.bool.config_handleVolumeKeysInWindowManager)) { + Log.e(TAG, "Cannot set external volume controller: device not set for volume keys" + + " handled in PhoneWindowManager"); + return; + } + synchronized (mExtVolumeControllerLock) { + if (mExtVolumeController != null && !mExtVolumeController.asBinder().pingBinder()) { + Log.e(TAG, "Cannot set external volume controller: existing controller"); + } + mExtVolumeController = apc; + } + } + private void dumpAudioPolicies(PrintWriter pw) { pw.println("\nAudio policies:"); synchronized (mAudioPolicies) { @@ -7151,8 +7218,9 @@ public class AudioService extends IAudioService.Stub */ public class AudioPolicyProxy extends AudioPolicyConfig implements IBinder.DeathRecipient { private static final String TAG = "AudioPolicyProxy"; - IAudioPolicyCallback mPolicyCallback; - boolean mHasFocusListener; + final IAudioPolicyCallback mPolicyCallback; + final boolean mHasFocusListener; + final boolean mIsVolumeController; /** * Audio focus ducking behavior for an audio policy. * This variable reflects the value that was successfully set in @@ -7164,11 +7232,12 @@ public class AudioService extends IAudioService.Stub boolean mIsFocusPolicy = false; AudioPolicyProxy(AudioPolicyConfig config, IAudioPolicyCallback token, - boolean hasFocusListener, boolean isFocusPolicy) { + boolean hasFocusListener, boolean isFocusPolicy, boolean isVolumeController) { super(config); setRegistration(new String(config.hashCode() + ":ap:" + mAudioPolicyCounter++)); mPolicyCallback = token; mHasFocusListener = hasFocusListener; + mIsVolumeController = isVolumeController; if (mHasFocusListener) { mMediaFocusControl.addFocusFollower(mPolicyCallback); // can only ever be true if there is a focus listener @@ -7177,6 +7246,9 @@ public class AudioService extends IAudioService.Stub mMediaFocusControl.setFocusPolicy(mPolicyCallback); } } + if (mIsVolumeController) { + setExtVolumeController(mPolicyCallback); + } connectMixes(); } @@ -7186,6 +7258,11 @@ public class AudioService extends IAudioService.Stub release(); mAudioPolicies.remove(mPolicyCallback.asBinder()); } + if (mIsVolumeController) { + synchronized (mExtVolumeControllerLock) { + mExtVolumeController = null; + } + } } String getRegistrationId() { @@ -7202,11 +7279,15 @@ public class AudioService extends IAudioService.Stub if (mHasFocusListener) { mMediaFocusControl.removeFocusFollower(mPolicyCallback); } + final long identity = Binder.clearCallingIdentity(); AudioSystem.registerPolicyMixes(mMixes, false); + Binder.restoreCallingIdentity(identity); } void connectMixes() { + final long identity = Binder.clearCallingIdentity(); AudioSystem.registerPolicyMixes(mMixes, true); + Binder.restoreCallingIdentity(identity); } }; diff --git a/services/core/java/com/android/server/connectivity/DefaultNetworkMetrics.java b/services/core/java/com/android/server/connectivity/DefaultNetworkMetrics.java index bd2e96ed4ef2..e43d1526f760 100644 --- a/services/core/java/com/android/server/connectivity/DefaultNetworkMetrics.java +++ b/services/core/java/com/android/server/connectivity/DefaultNetworkMetrics.java @@ -150,7 +150,8 @@ public class DefaultNetworkMetrics { fillLinkInfo(ev, newNai); ev.initialScore = newNai.getCurrentScore(); if (newNai.lastValidated) { - logDefaultNetworkValidity(timeMs, true); + mIsCurrentlyValid = true; + mLastValidationTimeMs = timeMs; } } else { mIsCurrentlyValid = false; diff --git a/services/core/java/com/android/server/connectivity/NetdEventListenerService.java b/services/core/java/com/android/server/connectivity/NetdEventListenerService.java index 57258a8ca0bc..f1a806bb4074 100644 --- a/services/core/java/com/android/server/connectivity/NetdEventListenerService.java +++ b/services/core/java/com/android/server/connectivity/NetdEventListenerService.java @@ -219,11 +219,7 @@ public class NetdEventListenerService extends INetdEventListener.Stub { for (INetdEventCallback callback : mNetdEventCallbackList) { if (callback != null) { - // TODO(rickywai): Remove this checking to collect ip in watchlist. - if (callback == - mNetdEventCallbackList[INetdEventCallback.CALLBACK_CALLER_DEVICE_POLICY]) { - callback.onConnectEvent(ipAddr, port, timestamp, uid); - } + callback.onConnectEvent(ipAddr, port, timestamp, uid); } } } diff --git a/services/core/java/com/android/server/content/SyncStorageEngine.java b/services/core/java/com/android/server/content/SyncStorageEngine.java index e498666073ed..5a37ee235219 100644 --- a/services/core/java/com/android/server/content/SyncStorageEngine.java +++ b/services/core/java/com/android/server/content/SyncStorageEngine.java @@ -481,9 +481,9 @@ public class SyncStorageEngine { maybeDeleteLegacyPendingInfoLocked(syncDir); - mAccountInfoFile = new AtomicFile(new File(syncDir, "accounts.xml")); - mStatusFile = new AtomicFile(new File(syncDir, "status.bin")); - mStatisticsFile = new AtomicFile(new File(syncDir, "stats.bin")); + mAccountInfoFile = new AtomicFile(new File(syncDir, "accounts.xml"), "sync-accounts"); + mStatusFile = new AtomicFile(new File(syncDir, "status.bin"), "sync-status"); + mStatisticsFile = new AtomicFile(new File(syncDir, "stats.bin"), "sync-stats"); readAccountInfoLocked(); readStatusLocked(); diff --git a/services/core/java/com/android/server/display/PersistentDataStore.java b/services/core/java/com/android/server/display/PersistentDataStore.java index cbf46f832186..1af03de7de2f 100644 --- a/services/core/java/com/android/server/display/PersistentDataStore.java +++ b/services/core/java/com/android/server/display/PersistentDataStore.java @@ -771,7 +771,8 @@ final class PersistentDataStore { private final AtomicFile mAtomicFile; public Injector() { - mAtomicFile = new AtomicFile(new File("/data/system/display-manager-state.xml")); + mAtomicFile = new AtomicFile(new File("/data/system/display-manager-state.xml"), + "display-state"); } public InputStream openRead() throws FileNotFoundException { diff --git a/services/core/java/com/android/server/input/PersistentDataStore.java b/services/core/java/com/android/server/input/PersistentDataStore.java index f67e0fdee0fb..372db416e756 100644 --- a/services/core/java/com/android/server/input/PersistentDataStore.java +++ b/services/core/java/com/android/server/input/PersistentDataStore.java @@ -82,7 +82,8 @@ final class PersistentDataStore { private boolean mDirty; public PersistentDataStore() { - mAtomicFile = new AtomicFile(new File("/data/system/input-manager-state.xml")); + mAtomicFile = new AtomicFile(new File("/data/system/input-manager-state.xml"), + "input-state"); } public void saveIfNeeded() { diff --git a/services/core/java/com/android/server/job/JobSchedulerService.java b/services/core/java/com/android/server/job/JobSchedulerService.java index 8fa331880abc..e0baeee01c69 100644 --- a/services/core/java/com/android/server/job/JobSchedulerService.java +++ b/services/core/java/com/android/server/job/JobSchedulerService.java @@ -2206,12 +2206,7 @@ public final class JobSchedulerService extends com.android.server.SystemService Slog.i(TAG, "Moving uid " + uid + " to bucketIndex " + bucketIndex); } synchronized (mLock) { - // TODO: update to be more efficient once we can slice by source UID - mJobs.forEachJob((JobStatus job) -> { - if (job.getSourceUid() == uid) { - job.setStandbyBucket(bucketIndex); - } - }); + mJobs.forEachJobForSourceUid(uid, job -> job.setStandbyBucket(bucketIndex)); onControllerStateChanged(); } }); diff --git a/services/core/java/com/android/server/job/JobStore.java b/services/core/java/com/android/server/job/JobStore.java index a24a4ac3823b..cf2788255843 100644 --- a/services/core/java/com/android/server/job/JobStore.java +++ b/services/core/java/com/android/server/job/JobStore.java @@ -29,6 +29,7 @@ import android.os.Environment; import android.os.Handler; import android.os.PersistableBundle; import android.os.Process; +import android.os.SystemClock; import android.os.UserHandle; import android.text.format.DateUtils; import android.util.ArraySet; @@ -61,6 +62,7 @@ import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.List; import java.util.Set; +import java.util.function.Predicate; /** * Maintains the master list of jobs that the job scheduler is tracking. These jobs are compared by @@ -84,7 +86,7 @@ public final class JobStore { private static final int MAX_OPS_BEFORE_WRITE = 1; final Object mLock; - final JobSet mJobSet; // per-caller-uid tracking + final JobSet mJobSet; // per-caller-uid and per-source-uid tracking final Context mContext; // Bookkeeping around incorrect boot-time system clock @@ -133,7 +135,7 @@ public final class JobStore { File systemDir = new File(dataDir, "system"); File jobDir = new File(systemDir, "job"); jobDir.mkdirs(); - mJobsFile = new AtomicFile(new File(jobDir, "jobs.xml")); + mJobsFile = new AtomicFile(new File(jobDir, "jobs.xml"), "jobs"); mJobSet = new JobSet(); @@ -361,6 +363,7 @@ public final class JobStore { int numSystemJobs = 0; int numSyncJobs = 0; try { + final long startTime = SystemClock.uptimeMillis(); ByteArrayOutputStream baos = new ByteArrayOutputStream(); XmlSerializer out = new FastXmlSerializer(); out.setOutput(baos, StandardCharsets.UTF_8.name()); @@ -393,7 +396,7 @@ public final class JobStore { out.endDocument(); // Write out to disk in one fell swoop. - FileOutputStream fos = mJobsFile.startWrite(); + FileOutputStream fos = mJobsFile.startWrite(startTime); fos.write(baos.toByteArray()); mJobsFile.finishWrite(fos); mDirtyOperations = 0; @@ -998,10 +1001,11 @@ public final class JobStore { } static final class JobSet { - // Key is the getUid() originator of the jobs in each sheaf - private SparseArray<ArraySet<JobStatus>> mJobs; - // Same data but with the key as getSourceUid() of the jobs in each sheaf - private SparseArray<ArraySet<JobStatus>> mJobsPerSourceUid; + @VisibleForTesting // Key is the getUid() originator of the jobs in each sheaf + final SparseArray<ArraySet<JobStatus>> mJobs; + + @VisibleForTesting // Same data but with the key as getSourceUid() of the jobs in each sheaf + final SparseArray<ArraySet<JobStatus>> mJobsPerSourceUid; public JobSet() { mJobs = new SparseArray<ArraySet<JobStatus>>(); @@ -1044,7 +1048,13 @@ public final class JobStore { jobsForSourceUid = new ArraySet<>(); mJobsPerSourceUid.put(sourceUid, jobsForSourceUid); } - return jobs.add(job) && jobsForSourceUid.add(job); + final boolean added = jobs.add(job); + final boolean addedInSource = jobsForSourceUid.add(job); + if (added != addedInSource) { + Slog.wtf(TAG, "mJobs and mJobsPerSourceUid mismatch; caller= " + added + + " source= " + addedInSource); + } + return added || addedInSource; } public boolean remove(JobStatus job) { @@ -1073,27 +1083,40 @@ public final class JobStore { /** * Removes the jobs of all users not specified by the whitelist of user ids. - * The jobs scheduled by non existent users will not be removed if they were + * This will remove jobs scheduled *by* non-existent users as well as jobs scheduled *for* + * non-existent users */ - public void removeJobsOfNonUsers(int[] whitelist) { - for (int jobSetIndex = mJobsPerSourceUid.size() - 1; jobSetIndex >= 0; jobSetIndex--) { - final int jobUserId = UserHandle.getUserId(mJobsPerSourceUid.keyAt(jobSetIndex)); - if (!ArrayUtils.contains(whitelist, jobUserId)) { - mJobsPerSourceUid.removeAt(jobSetIndex); - } - } + public void removeJobsOfNonUsers(final int[] whitelist) { + final Predicate<JobStatus> noSourceUser = + job -> !ArrayUtils.contains(whitelist, job.getSourceUserId()); + final Predicate<JobStatus> noCallingUser = + job -> !ArrayUtils.contains(whitelist, job.getUserId()); + removeAll(noSourceUser.or(noCallingUser)); + } + + private void removeAll(Predicate<JobStatus> predicate) { for (int jobSetIndex = mJobs.size() - 1; jobSetIndex >= 0; jobSetIndex--) { - final ArraySet<JobStatus> jobsForUid = mJobs.valueAt(jobSetIndex); - for (int jobIndex = jobsForUid.size() - 1; jobIndex >= 0; jobIndex--) { - final int jobUserId = jobsForUid.valueAt(jobIndex).getUserId(); - if (!ArrayUtils.contains(whitelist, jobUserId)) { - jobsForUid.removeAt(jobIndex); + final ArraySet<JobStatus> jobs = mJobs.valueAt(jobSetIndex); + for (int jobIndex = jobs.size() - 1; jobIndex >= 0; jobIndex--) { + if (predicate.test(jobs.valueAt(jobIndex))) { + jobs.removeAt(jobIndex); } } - if (jobsForUid.size() == 0) { + if (jobs.size() == 0) { mJobs.removeAt(jobSetIndex); } } + for (int jobSetIndex = mJobsPerSourceUid.size() - 1; jobSetIndex >= 0; jobSetIndex--) { + final ArraySet<JobStatus> jobs = mJobsPerSourceUid.valueAt(jobSetIndex); + for (int jobIndex = jobs.size() - 1; jobIndex >= 0; jobIndex--) { + if (predicate.test(jobs.valueAt(jobIndex))) { + jobs.removeAt(jobIndex); + } + } + if (jobs.size() == 0) { + mJobsPerSourceUid.removeAt(jobSetIndex); + } + } } public boolean contains(JobStatus job) { diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java index cfa1a79afa16..ec72b221931f 100644 --- a/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java +++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java @@ -458,28 +458,22 @@ public class RecoverableKeyStoreManager { throws RemoteException, ServiceSpecificException { byte[] locallyEncryptedKey; try { - locallyEncryptedKey = KeySyncUtils.decryptRecoveryClaimResponse( - sessionEntry.getKeyClaimant(), - sessionEntry.getVaultParams(), - encryptedClaimResponse); - } catch (InvalidKeyException e) { // TODO: Remove the extraneous logging here - Log.e(TAG, "Got InvalidKeyException during decrypting recovery claim response", e); Log.e(TAG, constructLoggingMessage("sessionEntry.getKeyClaimant()", sessionEntry.getKeyClaimant())); Log.e(TAG, constructLoggingMessage("sessionEntry.getVaultParams()", sessionEntry.getVaultParams())); Log.e(TAG, constructLoggingMessage("encryptedClaimResponse", encryptedClaimResponse)); + locallyEncryptedKey = KeySyncUtils.decryptRecoveryClaimResponse( + sessionEntry.getKeyClaimant(), + sessionEntry.getVaultParams(), + encryptedClaimResponse); + } catch (InvalidKeyException e) { + Log.e(TAG, "Got InvalidKeyException during decrypting recovery claim response", e); throw new ServiceSpecificException(ERROR_DECRYPTION_FAILED, "Failed to decrypt recovery key " + e.getMessage()); } catch (AEADBadTagException e) { - // TODO: Remove the extraneous logging here Log.e(TAG, "Got AEADBadTagException during decrypting recovery claim response", e); - Log.e(TAG, constructLoggingMessage("sessionEntry.getKeyClaimant()", - sessionEntry.getKeyClaimant())); - Log.e(TAG, constructLoggingMessage("sessionEntry.getVaultParams()", - sessionEntry.getVaultParams())); - Log.e(TAG, constructLoggingMessage("encryptedClaimResponse", encryptedClaimResponse)); throw new ServiceSpecificException(ERROR_DECRYPTION_FAILED, "Failed to decrypt recovery key " + e.getMessage()); } catch (NoSuchAlgorithmException e) { @@ -488,21 +482,17 @@ public class RecoverableKeyStoreManager { } try { - return KeySyncUtils.decryptRecoveryKey(sessionEntry.getLskfHash(), locallyEncryptedKey); - } catch (InvalidKeyException e) { // TODO: Remove the extraneous logging here - Log.e(TAG, "Got InvalidKeyException during decrypting recovery key", e); Log.e(TAG, constructLoggingMessage("sessionEntry.getLskfHash()", sessionEntry.getLskfHash())); Log.e(TAG, constructLoggingMessage("locallyEncryptedKey", locallyEncryptedKey)); + return KeySyncUtils.decryptRecoveryKey(sessionEntry.getLskfHash(), locallyEncryptedKey); + } catch (InvalidKeyException e) { + Log.e(TAG, "Got InvalidKeyException during decrypting recovery key", e); throw new ServiceSpecificException(ERROR_DECRYPTION_FAILED, "Failed to decrypt recovery key " + e.getMessage()); } catch (AEADBadTagException e) { - // TODO: Remove the extraneous logging here Log.e(TAG, "Got AEADBadTagException during decrypting recovery key", e); - Log.e(TAG, constructLoggingMessage("sessionEntry.getLskfHash()", - sessionEntry.getLskfHash())); - Log.e(TAG, constructLoggingMessage("locallyEncryptedKey", locallyEncryptedKey)); throw new ServiceSpecificException(ERROR_DECRYPTION_FAILED, "Failed to decrypt recovery key " + e.getMessage()); } catch (NoSuchAlgorithmException e) { @@ -534,6 +524,9 @@ public class RecoverableKeyStoreManager { byte[] encryptedKeyMaterial = applicationKey.getEncryptedKeyMaterial(); try { + // TODO: Remove the extraneous logging here + Log.e(TAG, constructLoggingMessage("recoveryKey", recoveryKey)); + Log.e(TAG, constructLoggingMessage("encryptedKeyMaterial", encryptedKeyMaterial)); byte[] keyMaterial = KeySyncUtils.decryptApplicationKey(recoveryKey, encryptedKeyMaterial); keyMaterialByAlias.put(alias, keyMaterial); @@ -542,19 +535,14 @@ public class RecoverableKeyStoreManager { throw new ServiceSpecificException( ERROR_SERVICE_INTERNAL_ERROR, e.getMessage()); } catch (InvalidKeyException e) { - // TODO: Remove the extraneous logging here Log.e(TAG, "Got InvalidKeyException during decrypting application key with alias: " + alias, e); - Log.e(TAG, constructLoggingMessage("recoveryKey", recoveryKey)); - Log.e(TAG, constructLoggingMessage("encryptedKeyMaterial", encryptedKeyMaterial)); throw new ServiceSpecificException(ERROR_DECRYPTION_FAILED, "Failed to recover key with alias '" + alias + "': " + e.getMessage()); } catch (AEADBadTagException e) { // TODO: Remove the extraneous logging here Log.e(TAG, "Got AEADBadTagException during decrypting application key with alias: " + alias, e); - Log.e(TAG, constructLoggingMessage("recoveryKey", recoveryKey)); - Log.e(TAG, constructLoggingMessage("encryptedKeyMaterial", encryptedKeyMaterial)); throw new ServiceSpecificException(ERROR_DECRYPTION_FAILED, "Failed to recover key with alias '" + alias + "': " + e.getMessage()); } diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/PersistentKeyChainSnapshot.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/PersistentKeyChainSnapshot.java new file mode 100644 index 000000000000..52381b8f87d1 --- /dev/null +++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/PersistentKeyChainSnapshot.java @@ -0,0 +1,298 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.locksettings.recoverablekeystore.storage; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.security.keystore.recovery.KeyChainProtectionParams; +import android.security.keystore.recovery.KeyChainSnapshot; +import android.security.keystore.recovery.KeyDerivationParams; +import android.security.keystore.recovery.WrappedApplicationKey; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.util.ArrayUtils; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +/** + * This class provides helper methods serialize and deserialize {@link KeyChainSnapshot}. + * + * <p> It is necessary since {@link android.os.Parcelable} is not designed for persistent storage. + * + * <p> For every list, length is stored before the elements. + * + */ +public class PersistentKeyChainSnapshot { + private static final int VERSION = 1; + private static final int NULL_LIST_LENGTH = -1; + + private DataInputStream mInput; + private DataOutputStream mOut; + private ByteArrayOutputStream mOutStream; + + @VisibleForTesting + PersistentKeyChainSnapshot() { + } + + @VisibleForTesting + void initReader(byte[] input) { + mInput = new DataInputStream(new ByteArrayInputStream(input)); + } + + @VisibleForTesting + void initWriter() { + mOutStream = new ByteArrayOutputStream(); + mOut = new DataOutputStream(mOutStream); + } + + @VisibleForTesting + byte[] getOutput() { + return mOutStream.toByteArray(); + } + + /** + * Converts {@link KeyChainSnapshot} to its binary representation. + * + * @param snapshot The snapshot. + * + * @throws IOException if serialization failed. + */ + public static byte[] serialize(@NonNull KeyChainSnapshot snapshot) throws IOException { + PersistentKeyChainSnapshot writer = new PersistentKeyChainSnapshot(); + writer.initWriter(); + writer.writeInt(VERSION); + writer.writeKeyChainSnapshot(snapshot); + return writer.getOutput(); + } + + /** + * deserializes {@link KeyChainSnapshot}. + * + * @input input - byte array produced by {@link serialize} method. + * @throws IOException if parsing failed. + */ + public static @NonNull KeyChainSnapshot deserialize(@NonNull byte[] input) + throws IOException { + PersistentKeyChainSnapshot reader = new PersistentKeyChainSnapshot(); + reader.initReader(input); + try { + int version = reader.readInt(); + if (version != VERSION) { + throw new IOException("Unsupported version " + version); + } + return reader.readKeyChainSnapshot(); + } catch (IOException e) { + throw new IOException("Malformed KeyChainSnapshot", e); + } + } + + /** + * Must be in sync with {@link KeyChainSnapshot.writeToParcel} + */ + @VisibleForTesting + void writeKeyChainSnapshot(KeyChainSnapshot snapshot) throws IOException { + writeInt(snapshot.getSnapshotVersion()); + writeProtectionParamsList(snapshot.getKeyChainProtectionParams()); + writeBytes(snapshot.getEncryptedRecoveryKeyBlob()); + writeKeysList(snapshot.getWrappedApplicationKeys()); + + writeInt(snapshot.getMaxAttempts()); + writeLong(snapshot.getCounterId()); + writeBytes(snapshot.getServerParams()); + writeBytes(snapshot.getTrustedHardwarePublicKey()); + } + + @VisibleForTesting + KeyChainSnapshot readKeyChainSnapshot() throws IOException { + int snapshotVersion = readInt(); + List<KeyChainProtectionParams> protectionParams = readProtectionParamsList(); + byte[] encryptedRecoveryKey = readBytes(); + List<WrappedApplicationKey> keysList = readKeysList(); + + int maxAttempts = readInt(); + long conterId = readLong(); + byte[] serverParams = readBytes(); + byte[] trustedHardwarePublicKey = readBytes(); + + return new KeyChainSnapshot.Builder() + .setSnapshotVersion(snapshotVersion) + .setKeyChainProtectionParams(protectionParams) + .setEncryptedRecoveryKeyBlob(encryptedRecoveryKey) + .setWrappedApplicationKeys(keysList) + .setMaxAttempts(maxAttempts) + .setCounterId(conterId) + .setServerParams(serverParams) + .setTrustedHardwarePublicKey(trustedHardwarePublicKey) + .build(); + } + + @VisibleForTesting + void writeProtectionParamsList( + @NonNull List<KeyChainProtectionParams> ProtectionParamsList) throws IOException { + writeInt(ProtectionParamsList.size()); + for (KeyChainProtectionParams protectionParams : ProtectionParamsList) { + writeProtectionParams(protectionParams); + } + } + + @VisibleForTesting + List<KeyChainProtectionParams> readProtectionParamsList() throws IOException { + int length = readInt(); + List<KeyChainProtectionParams> result = new ArrayList<>(length); + for (int i = 0; i < length; i++) { + result.add(readProtectionParams()); + } + return result; + } + + /** + * Must be in sync with {@link KeyChainProtectionParams.writeToParcel} + */ + @VisibleForTesting + void writeProtectionParams(@NonNull KeyChainProtectionParams protectionParams) + throws IOException { + if (!ArrayUtils.isEmpty(protectionParams.getSecret())) { + // Extra security check. + throw new RuntimeException("User generated secret should not be stored"); + } + writeInt(protectionParams.getUserSecretType()); + writeInt(protectionParams.getLockScreenUiFormat()); + writeKeyDerivationParams(protectionParams.getKeyDerivationParams()); + writeBytes(protectionParams.getSecret()); + } + + @VisibleForTesting + KeyChainProtectionParams readProtectionParams() throws IOException { + int userSecretType = readInt(); + int lockScreenUiFormat = readInt(); + KeyDerivationParams derivationParams = readKeyDerivationParams(); + byte[] secret = readBytes(); + return new KeyChainProtectionParams.Builder() + .setUserSecretType(userSecretType) + .setLockScreenUiFormat(lockScreenUiFormat) + .setKeyDerivationParams(derivationParams) + .setSecret(secret) + .build(); + } + + /** + * Must be in sync with {@link KeyDerivationParams.writeToParcel} + */ + @VisibleForTesting + void writeKeyDerivationParams(@NonNull KeyDerivationParams Params) throws IOException { + writeInt(Params.getAlgorithm()); + writeBytes(Params.getSalt()); + } + + @VisibleForTesting + KeyDerivationParams readKeyDerivationParams() throws IOException { + int algorithm = readInt(); + byte[] salt = readBytes(); + return KeyDerivationParams.createSha256Params(salt); + } + + @VisibleForTesting + void writeKeysList(@NonNull List<WrappedApplicationKey> applicationKeys) throws IOException { + writeInt(applicationKeys.size()); + for (WrappedApplicationKey keyEntry : applicationKeys) { + writeKeyEntry(keyEntry); + } + } + + @VisibleForTesting + List<WrappedApplicationKey> readKeysList() throws IOException { + int length = readInt(); + List<WrappedApplicationKey> result = new ArrayList<>(length); + for (int i = 0; i < length; i++) { + result.add(readKeyEntry()); + } + return result; + } + + /** + * Must be in sync with {@link WrappedApplicationKey.writeToParcel} + */ + @VisibleForTesting + void writeKeyEntry(@NonNull WrappedApplicationKey keyEntry) throws IOException { + mOut.writeUTF(keyEntry.getAlias()); + writeBytes(keyEntry.getEncryptedKeyMaterial()); + writeBytes(keyEntry.getAccount()); + } + + @VisibleForTesting + WrappedApplicationKey readKeyEntry() throws IOException { + String alias = mInput.readUTF(); + byte[] keyMaterial = readBytes(); + byte[] account = readBytes(); + return new WrappedApplicationKey.Builder() + .setAlias(alias) + .setEncryptedKeyMaterial(keyMaterial) + .setAccount(account) + .build(); + } + + @VisibleForTesting + void writeInt(int value) throws IOException { + mOut.writeInt(value); + } + + @VisibleForTesting + int readInt() throws IOException { + return mInput.readInt(); + } + + @VisibleForTesting + void writeLong(long value) throws IOException { + mOut.writeLong(value); + } + + @VisibleForTesting + long readLong() throws IOException { + return mInput.readLong(); + } + + @VisibleForTesting + void writeBytes(@Nullable byte[] value) throws IOException { + if (value == null) { + writeInt(NULL_LIST_LENGTH); + return; + } + writeInt(value.length); + mOut.write(value, 0, value.length); + } + + /** + * Reads @code{byte[]} from current position. Converts {@code null} to an empty array. + */ + @VisibleForTesting + @NonNull byte[] readBytes() throws IOException { + int length = readInt(); + if (length == NULL_LIST_LENGTH) { + return new byte[]{}; + } + byte[] result = new byte[length]; + mInput.read(result, 0, result.length); + return result; + } +} + diff --git a/services/core/java/com/android/server/media/MediaSession2Record.java b/services/core/java/com/android/server/media/MediaSession2Record.java index ffddf60817ee..97c7bf6ced50 100644 --- a/services/core/java/com/android/server/media/MediaSession2Record.java +++ b/services/core/java/com/android/server/media/MediaSession2Record.java @@ -16,19 +16,13 @@ package com.android.server.media; -import android.annotation.CallSuper; import android.annotation.NonNull; -import android.annotation.Nullable; import android.content.Context; -import android.media.IMediaSession2; import android.media.MediaController2; import android.media.MediaSession2; import android.media.SessionToken2; -import android.os.Handler; -import android.os.Looper; import android.util.Log; import java.util.concurrent.Executor; -import java.util.concurrent.atomic.AtomicBoolean; /** * Records a {@link MediaSession2} and holds {@link MediaController2}. diff --git a/services/core/java/com/android/server/media/MediaSessionService.java b/services/core/java/com/android/server/media/MediaSessionService.java index 02df84e8040e..b8771849ac71 100644 --- a/services/core/java/com/android/server/media/MediaSessionService.java +++ b/services/core/java/com/android/server/media/MediaSessionService.java @@ -39,7 +39,6 @@ import android.media.AudioManager; import android.media.AudioPlaybackConfiguration; import android.media.AudioSystem; import android.media.IAudioService; -import android.media.IMediaSession2; import android.media.IRemoteVolumeController; import android.media.ISessionTokensListener; import android.media.MediaLibraryService2; @@ -470,31 +469,21 @@ public class MediaSessionService extends SystemService implements Monitor { ServiceInfo serviceInfo = services.get(i).serviceInfo; int uid; try { + // TODO(jaewan): Do this per user. uid = manager.getPackageUid(serviceInfo.packageName, PackageManager.GET_META_DATA); } catch (NameNotFoundException e) { continue; } - String id = (serviceInfo.metaData != null) ? serviceInfo.metaData.getString( - MediaSessionService2.SERVICE_META_DATA) : null; - // Do basic sanity check - // TODO(jaewan): also santity check if it's protected with the system|privileged - // permission - boolean conflict = (getSessionRecordLocked(uid, serviceInfo.name, id) != null); - if (conflict) { - Log.w(TAG, serviceInfo.packageName + " contains multiple" - + " MediaSessionService2s declared in the manifest with" - + " the same ID=" + id + ". Ignoring " - + serviceInfo.packageName + "/" + serviceInfo.name); - } else { - int type = (libraryServices.contains(services.get(i))) - ? SessionToken2.TYPE_LIBRARY_SERVICE - : SessionToken2.TYPE_SESSION_SERVICE; - SessionToken2 token = new SessionToken2(getContext(), uid, type, - serviceInfo.packageName, serviceInfo.name, id, null); + + try { + SessionToken2 token = new SessionToken2(getContext(), + serviceInfo.packageName, serviceInfo.name, uid); MediaSession2Record record = new MediaSession2Record(getContext(), token, mSessionDestroyedListener); mSessions.add(record); + } catch (IllegalArgumentException e) { + Log.d(TAG, "Invalid session service", e); } } } @@ -1425,6 +1414,16 @@ public class MediaSessionService extends SystemService implements Monitor { mUserRecords.valueAt(i).dumpLocked(pw, ""); } mAudioPlayerStateMonitor.dump(getContext(), pw, ""); + + // TODO(jaewan): Remove this debug command before ship. + if (args != null && args.length > 0 && "--purge".equals(args[0])) { + mSessions.clear(); + } + pw.println(); + pw.println("Session2: size=" + mSessions.size()); + for (int i = 0; i < mSessions.size(); i++) { + pw.println(" " + mSessions.get(i)); + } } } diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java index 0e54768bfa87..f09de5222715 100644 --- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java +++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java @@ -551,7 +551,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { mSuppressDefaultPolicy = suppressDefaultPolicy; - mPolicyFile = new AtomicFile(new File(systemDir, "netpolicy.xml")); + mPolicyFile = new AtomicFile(new File(systemDir, "netpolicy.xml"), "net-policy"); mAppOps = context.getSystemService(AppOpsManager.class); diff --git a/services/core/java/com/android/server/net/watchlist/NetworkWatchlistService.java b/services/core/java/com/android/server/net/watchlist/NetworkWatchlistService.java index 7165e600ca2f..5f4e47147109 100644 --- a/services/core/java/com/android/server/net/watchlist/NetworkWatchlistService.java +++ b/services/core/java/com/android/server/net/watchlist/NetworkWatchlistService.java @@ -23,8 +23,10 @@ import android.net.INetdEventCallback; import android.net.metrics.IpConnectivityLog; import android.os.Binder; import android.os.Process; +import android.os.ResultReceiver; import android.os.RemoteException; import android.os.ServiceManager; +import android.os.ShellCallback; import android.os.SystemProperties; import android.provider.Settings; import android.text.TextUtils; @@ -80,6 +82,7 @@ public class NetworkWatchlistService extends INetworkWatchlistManager.Stub { return; } try { + mService.init(); mService.initIpConnectivityMetrics(); mService.startWatchlistLogging(); } catch (RemoteException e) { @@ -127,6 +130,10 @@ public class NetworkWatchlistService extends INetworkWatchlistManager.Stub { mIpConnectivityMetrics = ipConnectivityMetrics; } + private void init() { + mConfig.removeTestModeConfig(); + } + private void initIpConnectivityMetrics() { mIpConnectivityMetrics = (IIpConnectivityMetrics) IIpConnectivityMetrics.Stub.asInterface( ServiceManager.getService(IpConnectivityLog.SERVICE_NAME)); @@ -151,6 +158,22 @@ public class NetworkWatchlistService extends INetworkWatchlistManager.Stub { } }; + private boolean isCallerShell() { + final int callingUid = Binder.getCallingUid(); + return callingUid == Process.SHELL_UID || callingUid == Process.ROOT_UID; + } + + @Override + public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err, + String[] args, ShellCallback callback, ResultReceiver resultReceiver) { + if (!isCallerShell()) { + Slog.w(TAG, "Only shell is allowed to call network watchlist shell commands"); + return; + } + (new NetworkWatchlistShellCommand(mContext)).exec(this, in, out, err, args, callback, + resultReceiver); + } + @VisibleForTesting protected boolean startWatchlistLoggingImpl() throws RemoteException { if (DEBUG) { diff --git a/services/core/java/com/android/server/net/watchlist/NetworkWatchlistShellCommand.java b/services/core/java/com/android/server/net/watchlist/NetworkWatchlistShellCommand.java new file mode 100644 index 000000000000..9533823df808 --- /dev/null +++ b/services/core/java/com/android/server/net/watchlist/NetworkWatchlistShellCommand.java @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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.net.watchlist; + +import android.content.Context; +import android.content.Intent; +import android.net.NetworkWatchlistManager; +import android.os.ParcelFileDescriptor; +import android.os.RemoteException; +import android.os.ShellCommand; + +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.PrintWriter; + +/** + * Network watchlist shell commands class, to provide a way to set temporary watchlist config for + * testing in shell, so CTS / GTS can use it to verify if watchlist feature is working properly. + */ +class NetworkWatchlistShellCommand extends ShellCommand { + + final NetworkWatchlistManager mNetworkWatchlistManager; + + NetworkWatchlistShellCommand(Context context) { + mNetworkWatchlistManager = new NetworkWatchlistManager(context); + } + + @Override + public int onCommand(String cmd) { + if (cmd == null) { + return handleDefaultCommands(cmd); + } + + final PrintWriter pw = getOutPrintWriter(); + try { + switch(cmd) { + case "set-test-config": + return runSetTestConfig(); + default: + return handleDefaultCommands(cmd); + } + } catch (RemoteException e) { + pw.println("Remote exception: " + e); + } + return -1; + } + + /** + * Method to get fd from input xml path, and set it as temporary watchlist config. + */ + private int runSetTestConfig() throws RemoteException { + final PrintWriter pw = getOutPrintWriter(); + try { + final String configXmlPath = getNextArgRequired(); + final ParcelFileDescriptor pfd = openFileForSystem(configXmlPath, "r"); + if (pfd != null) { + final InputStream fileStream = new FileInputStream(pfd.getFileDescriptor()); + WatchlistConfig.getInstance().setTestMode(fileStream); + } + pw.println("Success!"); + } catch (RuntimeException | IOException ex) { + pw.println("Error: " + ex.toString()); + return -1; + } + return 0; + } + + @Override + public void onHelp() { + final PrintWriter pw = getOutPrintWriter(); + pw.println("Network watchlist manager commands:"); + pw.println(" help"); + pw.println(" Print this help text."); + pw.println(""); + pw.println(" set-test-config your_watchlist_config.xml"); + pw.println(); + Intent.printIntentArgsHelp(pw , ""); + } +} diff --git a/services/core/java/com/android/server/net/watchlist/WatchlistConfig.java b/services/core/java/com/android/server/net/watchlist/WatchlistConfig.java index 7387ad4f90ff..2714d5e5f605 100644 --- a/services/core/java/com/android/server/net/watchlist/WatchlistConfig.java +++ b/services/core/java/com/android/server/net/watchlist/WatchlistConfig.java @@ -16,6 +16,7 @@ package com.android.server.net.watchlist; +import android.os.FileUtils; import android.util.AtomicFile; import android.util.Log; import android.util.Slog; @@ -32,6 +33,7 @@ import java.io.File; import java.io.FileDescriptor; import java.io.FileInputStream; import java.io.IOException; +import java.io.InputStream; import java.io.PrintWriter; import java.nio.charset.StandardCharsets; import java.security.MessageDigest; @@ -50,6 +52,8 @@ class WatchlistConfig { // Watchlist config that pushed by ConfigUpdater. private static final String NETWORK_WATCHLIST_DB_PATH = "/data/misc/network_watchlist/network_watchlist.xml"; + private static final String NETWORK_WATCHLIST_DB_FOR_TEST_PATH = + "/data/misc/network_watchlist/network_watchlist_for_test.xml"; // Hash for null / unknown config, a 32 byte array filled with content 0x00 private static final byte[] UNKNOWN_CONFIG_HASH = new byte[32]; @@ -80,7 +84,7 @@ class WatchlistConfig { private boolean mIsSecureConfig = true; private final static WatchlistConfig sInstance = new WatchlistConfig(); - private final File mXmlFile; + private File mXmlFile; private volatile CrcShaDigests mDomainDigests; private volatile CrcShaDigests mIpDigests; @@ -232,7 +236,38 @@ class WatchlistConfig { return UNKNOWN_CONFIG_HASH; } + /** + * This method will copy temporary test config and temporary override network watchlist config + * in memory. When device is rebooted, temporary test config will be removed, and system will + * use back the original watchlist config. + * Also, as temporary network watchlist config is not secure, we will mark it as insecure + * config and will be applied to testOnly applications only. + */ + public void setTestMode(InputStream testConfigInputStream) throws IOException { + Log.i(TAG, "Setting watchlist testing config"); + // Copy test config + FileUtils.copyToFileOrThrow(testConfigInputStream, + new File(NETWORK_WATCHLIST_DB_FOR_TEST_PATH)); + // Mark config as insecure, so it will be applied to testOnly applications only + mIsSecureConfig = false; + // Reload watchlist config using test config file + mXmlFile = new File(NETWORK_WATCHLIST_DB_FOR_TEST_PATH); + reloadConfig(); + } + + public void removeTestModeConfig() { + try { + final File f = new File(NETWORK_WATCHLIST_DB_FOR_TEST_PATH); + if (f.exists()) { + f.delete(); + } + } catch (Exception e) { + Log.e(TAG, "Unable to delete test config"); + } + } + public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + pw.println("Watchlist config hash: " + HexDump.toHexString(getWatchlistConfigHash())); pw.println("Domain CRC32 digest list:"); if (mDomainDigests != null) { mDomainDigests.crc32Digests.dump(fd, pw, args); diff --git a/services/core/java/com/android/server/net/watchlist/WatchlistLoggingHandler.java b/services/core/java/com/android/server/net/watchlist/WatchlistLoggingHandler.java index 3b6d59e178f2..c4de4ac1b7dc 100644 --- a/services/core/java/com/android/server/net/watchlist/WatchlistLoggingHandler.java +++ b/services/core/java/com/android/server/net/watchlist/WatchlistLoggingHandler.java @@ -118,6 +118,25 @@ class WatchlistLoggingHandler extends Handler { } /** + * Return if a given package has testOnly is true. + */ + private boolean isPackageTestOnly(int uid) { + final ApplicationInfo ai; + try { + final String[] packageNames = mPm.getPackagesForUid(uid); + if (packageNames == null || packageNames.length == 0) { + Slog.e(TAG, "Couldn't find package: " + packageNames); + return false; + } + ai = mPm.getApplicationInfo(packageNames[0],0); + } catch (NameNotFoundException e) { + // Should not happen. + return false; + } + return (ai.flags & ApplicationInfo.FLAG_TEST_ONLY) != 0; + } + + /** * Report network watchlist records if we collected enough data. */ public void reportWatchlistIfNecessary() { @@ -146,16 +165,21 @@ class WatchlistLoggingHandler extends Handler { } final String cncDomain = searchAllSubDomainsInWatchlist(hostname); if (cncDomain != null) { - insertRecord(getDigestFromUid(uid), cncDomain, timestamp); + insertRecord(uid, cncDomain, timestamp); } else { final String cncIp = searchIpInWatchlist(ipAddresses); if (cncIp != null) { - insertRecord(getDigestFromUid(uid), cncIp, timestamp); + insertRecord(uid, cncIp, timestamp); } } } - private boolean insertRecord(byte[] digest, String cncHost, long timestamp) { + private boolean insertRecord(int uid, String cncHost, long timestamp) { + if (!mConfig.isConfigSecure() && !isPackageTestOnly(uid)) { + // Skip package if config is not secure and package is not TestOnly app. + return true; + } + final byte[] digest = getDigestFromUid(uid); final boolean result = mDbHelper.insertNewRecord(digest, cncHost, timestamp); tryAggregateRecords(); return result; diff --git a/services/core/java/com/android/server/net/watchlist/WatchlistReportDbHelper.java b/services/core/java/com/android/server/net/watchlist/WatchlistReportDbHelper.java index c73b0cf1c8fc..4b577bb9c919 100644 --- a/services/core/java/com/android/server/net/watchlist/WatchlistReportDbHelper.java +++ b/services/core/java/com/android/server/net/watchlist/WatchlistReportDbHelper.java @@ -141,20 +141,19 @@ class WatchlistReportDbHelper extends SQLiteOpenHelper { } /** - * Aggregate the records in database, and return a rappor encoded result. + * Aggregate all records before most recent local midnight in database, and return a + * rappor encoded result. */ public AggregatedResult getAggregatedRecords() { - final long twoDaysBefore = getTwoDaysBeforeTimestamp(); - final long yesterday = getYesterdayTimestamp(); - final String selectStatement = WhiteListReportContract.TIMESTAMP + " >= ? AND " + - WhiteListReportContract.TIMESTAMP + " <= ?"; + final long lastMidnightTime = getLastMidnightTime(); + final String selectStatement = WhiteListReportContract.TIMESTAMP + " < ?"; final SQLiteDatabase db = getReadableDatabase(); Cursor c = null; try { c = db.query(true /* distinct */, WhiteListReportContract.TABLE, DIGEST_DOMAIN_PROJECTION, selectStatement, - new String[]{"" + twoDaysBefore, "" + yesterday}, null, null, + new String[]{"" + lastMidnightTime}, null, null, null, null); if (c == null) { return null; @@ -182,23 +181,19 @@ class WatchlistReportDbHelper extends SQLiteOpenHelper { } /** - * Remove all the records before yesterday. + * Remove all the records before most recent local midnight. * * @return True if success. */ public boolean cleanup() { final SQLiteDatabase db = getWritableDatabase(); - final long twoDaysBefore = getTwoDaysBeforeTimestamp(); - final String clause = WhiteListReportContract.TIMESTAMP + "< " + twoDaysBefore; + final long midnightTime = getLastMidnightTime(); + final String clause = WhiteListReportContract.TIMESTAMP + "< " + midnightTime; return db.delete(WhiteListReportContract.TABLE, clause, null) != 0; } - static long getTwoDaysBeforeTimestamp() { - return getMidnightTimestamp(2); - } - - static long getYesterdayTimestamp() { - return getMidnightTimestamp(1); + static long getLastMidnightTime() { + return getMidnightTimestamp(0); } static long getMidnightTimestamp(int daysBefore) { diff --git a/services/core/java/com/android/server/net/watchlist/WatchlistSettings.java b/services/core/java/com/android/server/net/watchlist/WatchlistSettings.java index b78fe4d2cca7..f5ba889e8cf4 100644 --- a/services/core/java/com/android/server/net/watchlist/WatchlistSettings.java +++ b/services/core/java/com/android/server/net/watchlist/WatchlistSettings.java @@ -77,7 +77,7 @@ class WatchlistSettings { @VisibleForTesting protected WatchlistSettings(File xmlFile) { - mXmlFile = new AtomicFile(xmlFile); + mXmlFile = new AtomicFile(xmlFile, "net-watchlist"); reloadSettings(); if (mPrivacySecretKey == null) { // Generate a new secret key and save settings diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index cbfa067aa6ec..727e7ee2ac18 100644 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -1413,7 +1413,7 @@ public class NotificationManagerService extends SystemService { AppGlobals.getPackageManager()), new ConditionProviders(getContext(), mUserProfiles, AppGlobals.getPackageManager()), null, snoozeHelper, new NotificationUsageStats(getContext()), - new AtomicFile(new File(systemDir, "notification_policy.xml")), + new AtomicFile(new File(systemDir, "notification_policy.xml"), "notification-policy"), (ActivityManager) getContext().getSystemService(Context.ACTIVITY_SERVICE), getGroupHelper()); diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java index 2859613c0047..7e3b5516b8cc 100644 --- a/services/core/java/com/android/server/notification/ZenModeHelper.java +++ b/services/core/java/com/android/server/notification/ZenModeHelper.java @@ -129,7 +129,6 @@ public class ZenModeHelper { mFiltering = new ZenModeFiltering(mContext); mConditions = new ZenModeConditions(this, conditionProviders); mServiceConfig = conditionProviders.getConfig(); - } public Looper getLooper() { @@ -822,7 +821,7 @@ public class ZenModeHelper { @VisibleForTesting protected void applyRestrictions() { final boolean zenPriorityOnly = mZenMode == Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS; - final boolean zenSilence = mZenMode == Global.ZEN_MODE_NO_INTERRUPTIONS; + final boolean zenSilence = mZenMode == Global.ZEN_MODE_NO_INTERRUPTIONS; final boolean zenAlarmsOnly = mZenMode == Global.ZEN_MODE_ALARMS; // notification restrictions @@ -838,7 +837,7 @@ public class ZenModeHelper { final boolean muteMediaAndSystemSounds = zenPriorityOnly && !mConfig.allowMediaSystemOther; // total silence restrictions final boolean muteEverything = zenSilence - || (zenPriorityOnly && areAllBehaviorSoundsMuted()); + || (zenPriorityOnly && ZenModeConfig.areAllZenBehaviorSoundsMuted(mConfig)); for (int usage : AudioAttributes.SDK_USAGES) { final int suppressionBehavior = AudioAttributes.SUPPRESSIBLE_USAGES.get(usage); @@ -871,12 +870,6 @@ public class ZenModeHelper { exceptionPackages); } - private boolean areAllBehaviorSoundsMuted() { - return !mConfig.allowAlarms && !mConfig.allowMediaSystemOther && !mConfig.allowReminders - && !mConfig.allowCalls && !mConfig.allowMessages && !mConfig.allowEvents - && !mConfig.allowRepeatCallers; - } - private void applyZenToRingerMode() { if (mAudioManager == null) return; // force the ringer mode into compliance @@ -891,6 +884,18 @@ public class ZenModeHelper { } break; case Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS: + if (ZenModeConfig.areAllPriorityOnlyNotificationZenSoundsMuted(mConfig)) { + if (ringerModeInternal != AudioManager.RINGER_MODE_SILENT) { + setPreviousRingerModeSetting(ringerModeInternal); + newRingerModeInternal = AudioManager.RINGER_MODE_SILENT; + } + } else { + if (ringerModeInternal == AudioManager.RINGER_MODE_SILENT) { + newRingerModeInternal = getPreviousRingerModeSetting(); + setPreviousRingerModeSetting(null); + } + } + break; case Global.ZEN_MODE_OFF: if (ringerModeInternal == AudioManager.RINGER_MODE_SILENT) { newRingerModeInternal = getPreviousRingerModeSetting(); @@ -999,8 +1004,7 @@ public class ZenModeHelper { switch (ringerModeNew) { case AudioManager.RINGER_MODE_SILENT: if (isChange && policy.doNotDisturbWhenSilent) { - if (mZenMode != Global.ZEN_MODE_NO_INTERRUPTIONS - && mZenMode != Global.ZEN_MODE_ALARMS) { + if (mZenMode == Global.ZEN_MODE_OFF) { newZen = Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS; } setPreviousRingerModeSetting(ringerModeOld); @@ -1010,7 +1014,10 @@ public class ZenModeHelper { case AudioManager.RINGER_MODE_NORMAL: if (isChange && ringerModeOld == AudioManager.RINGER_MODE_SILENT && (mZenMode == Global.ZEN_MODE_NO_INTERRUPTIONS - || mZenMode == Global.ZEN_MODE_ALARMS)) { + || mZenMode == Global.ZEN_MODE_ALARMS + || (mZenMode == Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS + && ZenModeConfig.areAllPriorityOnlyNotificationZenSoundsMuted( + mConfig)))) { newZen = Global.ZEN_MODE_OFF; } else if (mZenMode != Global.ZEN_MODE_OFF) { ringerModeExternalOut = AudioManager.RINGER_MODE_SILENT; @@ -1078,10 +1085,24 @@ public class ZenModeHelper { (1 << AudioSystem.STREAM_NOTIFICATION) | (1 << AudioSystem.STREAM_SYSTEM); - // alarm and music streams are only affected by ringer mode when in total silence if (mZenMode == Global.ZEN_MODE_NO_INTERRUPTIONS) { + // alarm and music streams affected by ringer mode when in total silence streams |= (1 << AudioSystem.STREAM_ALARM) | (1 << AudioSystem.STREAM_MUSIC); + } else if (mZenMode == Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS) { + // alarm and music streams affected by ringer mode when in priority only with + // media and alarms not allowed to bypass dnd + if (!mConfig.allowMediaSystemOther) { + streams |= (1 << AudioSystem.STREAM_MUSIC); + } else { + streams &= ~(1 << AudioSystem.STREAM_MUSIC); + } + + if (!mConfig.allowAlarms) { + streams |= (1 << AudioSystem.STREAM_ALARM); + } else { + streams &= ~(1 << AudioSystem.STREAM_ALARM); + } } else { streams &= ~((1 << AudioSystem.STREAM_ALARM) | (1 << AudioSystem.STREAM_MUSIC)); diff --git a/services/core/java/com/android/server/om/OverlayManagerService.java b/services/core/java/com/android/server/om/OverlayManagerService.java index 2041de64c8fb..4bc4a7ecc055 100644 --- a/services/core/java/com/android/server/om/OverlayManagerService.java +++ b/services/core/java/com/android/server/om/OverlayManagerService.java @@ -228,7 +228,7 @@ public final class OverlayManagerService extends SystemService { @NonNull final Installer installer) { super(context); mSettingsFile = - new AtomicFile(new File(Environment.getDataSystemDirectory(), "overlays.xml")); + new AtomicFile(new File(Environment.getDataSystemDirectory(), "overlays.xml"), "overlays"); mPackageManager = new PackageManagerHelper(); mUserManager = UserManagerService.getInstance(); IdmapManager im = new IdmapManager(installer); diff --git a/services/core/java/com/android/server/pm/InstantAppRegistry.java b/services/core/java/com/android/server/pm/InstantAppRegistry.java index af20cd77e626..30088dd9b12f 100644 --- a/services/core/java/com/android/server/pm/InstantAppRegistry.java +++ b/services/core/java/com/android/server/pm/InstantAppRegistry.java @@ -44,6 +44,7 @@ import android.util.Slog; import android.util.SparseArray; import android.util.SparseBooleanArray; import android.util.Xml; + import com.android.internal.annotations.GuardedBy; import com.android.internal.os.BackgroundThread; import com.android.internal.os.SomeArgs; @@ -51,6 +52,7 @@ import com.android.internal.util.ArrayUtils; import com.android.internal.util.XmlUtils; import libcore.io.IoUtils; + import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import org.xmlpull.v1.XmlSerializer; @@ -295,25 +297,26 @@ class InstantAppRegistry { continue; } + String cookieName = currentCookieFile.getName(); + String currentCookieSha256 = + cookieName.substring(INSTANT_APP_COOKIE_FILE_PREFIX.length(), + cookieName.length() - INSTANT_APP_COOKIE_FILE_SIFFIX.length()); + // Before we used only the first signature to compute the SHA 256 but some // apps could be singed by multiple certs and the cert order is undefined. // We prefer the modern computation procedure where all certs are taken // into account but also allow the value from the old computation to avoid // data loss. - final String[] signaturesSha256Digests = PackageUtils.computeSignaturesSha256Digests( - pkg.mSigningDetails.signatures); - final String signaturesSha256Digest = PackageUtils.computeSignaturesSha256Digest( - signaturesSha256Digests); - - // We prefer a match based on all signatures - if (currentCookieFile.equals(computeInstantCookieFile(pkg.packageName, - signaturesSha256Digest, userId))) { + if (pkg.mSigningDetails.checkCapability(currentCookieSha256, + PackageParser.SigningDetails.CertCapabilities.INSTALLED_DATA)) { return; } - // For backwards compatibility we accept match based on first signature - if (pkg.mSigningDetails.signatures.length > 1 && currentCookieFile.equals(computeInstantCookieFile( - pkg.packageName, signaturesSha256Digests[0], userId))) { + // For backwards compatibility we accept match based on first signature only in the case + // of multiply-signed packagse + final String[] signaturesSha256Digests = + PackageUtils.computeSignaturesSha256Digests(pkg.mSigningDetails.signatures); + if (signaturesSha256Digests[0].equals(currentCookieSha256)) { return; } diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java index 16fae99a71c5..59f9dae0619a 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerService.java +++ b/services/core/java/com/android/server/pm/PackageInstallerService.java @@ -58,6 +58,7 @@ import android.os.Process; import android.os.RemoteCallbackList; import android.os.RemoteException; import android.os.SELinux; +import android.os.SystemClock; import android.os.UserHandle; import android.os.UserManager; import android.os.storage.StorageManager; @@ -193,7 +194,8 @@ public class PackageInstallerService extends IPackageInstaller.Stub { mCallbacks = new Callbacks(mInstallThread.getLooper()); mSessionsFile = new AtomicFile( - new File(Environment.getDataSystemDirectory(), "install_sessions.xml")); + new File(Environment.getDataSystemDirectory(), "install_sessions.xml"), + "package-session"); mSessionsDir = new File(Environment.getDataSystemDirectory(), "install_sessions"); mSessionsDir.mkdirs(); } diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 3049e98099ca..01c44af586ed 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -108,8 +108,6 @@ import static com.android.server.pm.PackageManagerServiceUtils.dumpCriticalInfo; import static com.android.server.pm.PackageManagerServiceUtils.getCompressedFiles; import static com.android.server.pm.PackageManagerServiceUtils.getLastModifiedTime; import static com.android.server.pm.PackageManagerServiceUtils.logCriticalInfo; -import static com.android.server.pm.PackageManagerServiceUtils.signingDetailsHasCertificate; -import static com.android.server.pm.PackageManagerServiceUtils.signingDetailsHasSha256Certificate; import static com.android.server.pm.PackageManagerServiceUtils.verifySignatures; import static com.android.server.pm.permission.PermissionsState.PERMISSION_OPERATION_FAILURE; import static com.android.server.pm.permission.PermissionsState.PERMISSION_OPERATION_SUCCESS; @@ -246,6 +244,7 @@ import android.text.format.DateUtils; import android.util.ArrayMap; import android.util.ArraySet; import android.util.Base64; +import android.util.ByteStringUtils; import android.util.DisplayMetrics; import android.util.EventLog; import android.util.ExceptionUtils; @@ -5537,9 +5536,9 @@ Slog.e("TODD", } switch (type) { case CERT_INPUT_RAW_X509: - return signingDetailsHasCertificate(certificate, p.mSigningDetails); + return p.mSigningDetails.hasCertificate(certificate); case CERT_INPUT_SHA256: - return signingDetailsHasSha256Certificate(certificate, p.mSigningDetails); + return p.mSigningDetails.hasSha256Certificate(certificate); default: return false; } @@ -5578,9 +5577,9 @@ Slog.e("TODD", } switch (type) { case CERT_INPUT_RAW_X509: - return signingDetailsHasCertificate(certificate, signingDetails); + return signingDetails.hasCertificate(certificate); case CERT_INPUT_SHA256: - return signingDetailsHasSha256Certificate(certificate, signingDetails); + return signingDetails.hasSha256Certificate(certificate); default: return false; } @@ -8353,13 +8352,11 @@ Slog.e("TODD", } private void collectCertificatesLI(PackageSetting ps, PackageParser.Package pkg, - boolean forceCollect) throws PackageManagerException { + boolean forceCollect, boolean skipVerify) throws PackageManagerException { // When upgrading from pre-N MR1, verify the package time stamp using the package // directory and not the APK file. final long lastModifiedTime = mIsPreNMR1Upgrade ? new File(pkg.codePath).lastModified() : getLastModifiedTime(pkg); - // Note that currently skipVerify skips verification on both base and splits for simplicity. - final boolean skipVerify = forceCollect && canSkipFullPackageVerification(pkg); if (ps != null && !forceCollect && ps.codePathString.equals(pkg.codePath) && ps.timeStamp == lastModifiedTime @@ -8732,21 +8729,24 @@ Slog.e("TODD", } // Verify certificates against what was last scanned. If it is an updated priv app, we will - // force re-collecting certificate. Full apk verification will happen unless apk verity is - // set up for the file. In that case, only small part of the apk is verified upfront. + // force re-collecting certificate. final boolean forceCollect = PackageManagerServiceUtils.isApkVerificationForced( disabledPkgSetting); - collectCertificatesLI(pkgSetting, pkg, forceCollect); + // Full APK verification can be skipped during certificate collection, only if the file is + // in verified partition, or can be verified on access (when apk verity is enabled). In both + // cases, only data in Signing Block is verified instead of the whole file. + final boolean skipVerify = ((parseFlags & PackageParser.PARSE_IS_SYSTEM_DIR) != 0) || + (forceCollect && canSkipFullPackageVerification(pkg)); + collectCertificatesLI(pkgSetting, pkg, forceCollect, skipVerify); boolean shouldHideSystemApp = false; // A new application appeared on /system, but, we already have a copy of // the application installed on /data. if (scanSystemPartition && !isSystemPkgUpdated && pkgAlreadyExists && !pkgSetting.isSystem()) { - // if the signatures don't match, wipe the installed application and its data - if (compareSignatures(pkgSetting.signatures.mSigningDetails.signatures, - pkg.mSigningDetails.signatures) - != PackageManager.SIGNATURE_MATCH) { + + if (!pkg.mSigningDetails.checkCapability(pkgSetting.signatures.mSigningDetails, + PackageParser.SigningDetails.CertCapabilities.INSTALLED_DATA)) { logCriticalInfo(Log.WARN, "System package signature mismatch;" + " name: " + pkgSetting.name); @@ -9711,34 +9711,51 @@ Slog.e("TODD", } final String[] expectedCertDigests = requiredCertDigests[i]; - // For apps targeting O MR1 we require explicit enumeration of all certs. - final String[] libCertDigests = (targetSdk > Build.VERSION_CODES.O) - ? PackageUtils.computeSignaturesSha256Digests( - libPkg.mSigningDetails.signatures) - : PackageUtils.computeSignaturesSha256Digests( - new Signature[]{libPkg.mSigningDetails.signatures[0]}); - - // Take a shortcut if sizes don't match. Note that if an app doesn't - // target O we don't parse the "additional-certificate" tags similarly - // how we only consider all certs only for apps targeting O (see above). - // Therefore, the size check is safe to make. - if (expectedCertDigests.length != libCertDigests.length) { - throw new PackageManagerException(INSTALL_FAILED_MISSING_SHARED_LIBRARY, - "Package " + packageName + " requires differently signed" + - " static shared library; failing!"); - } - // Use a predictable order as signature order may vary - Arrays.sort(libCertDigests); - Arrays.sort(expectedCertDigests); - final int certCount = libCertDigests.length; - for (int j = 0; j < certCount; j++) { - if (!libCertDigests[j].equalsIgnoreCase(expectedCertDigests[j])) { + if (expectedCertDigests.length > 1) { + + // For apps targeting O MR1 we require explicit enumeration of all certs. + final String[] libCertDigests = (targetSdk > Build.VERSION_CODES.O) + ? PackageUtils.computeSignaturesSha256Digests( + libPkg.mSigningDetails.signatures) + : PackageUtils.computeSignaturesSha256Digests( + new Signature[]{libPkg.mSigningDetails.signatures[0]}); + + // Take a shortcut if sizes don't match. Note that if an app doesn't + // target O we don't parse the "additional-certificate" tags similarly + // how we only consider all certs only for apps targeting O (see above). + // Therefore, the size check is safe to make. + if (expectedCertDigests.length != libCertDigests.length) { throw new PackageManagerException(INSTALL_FAILED_MISSING_SHARED_LIBRARY, "Package " + packageName + " requires differently signed" + " static shared library; failing!"); } + + // Use a predictable order as signature order may vary + Arrays.sort(libCertDigests); + Arrays.sort(expectedCertDigests); + + final int certCount = libCertDigests.length; + for (int j = 0; j < certCount; j++) { + if (!libCertDigests[j].equalsIgnoreCase(expectedCertDigests[j])) { + throw new PackageManagerException( + INSTALL_FAILED_MISSING_SHARED_LIBRARY, + "Package " + packageName + " requires differently signed" + + " static shared library; failing!"); + } + } + } else { + + // lib signing cert could have rotated beyond the one expected, check to see + // if the new one has been blessed by the old + if (!libPkg.mSigningDetails.hasSha256Certificate( + ByteStringUtils.fromHexToByteArray(expectedCertDigests[0]))) { + throw new PackageManagerException( + INSTALL_FAILED_MISSING_SHARED_LIBRARY, + "Package " + packageName + " requires differently signed" + + " static shared library; failing!"); + } } } @@ -10155,6 +10172,15 @@ Slog.e("TODD", // We just determined the app is signed correctly, so bring // over the latest parsed certs. pkgSetting.signatures.mSigningDetails = pkg.mSigningDetails; + + + // if this is is a sharedUser, check to see if the new package is signed by a newer + // signing certificate than the existing one, and if so, copy over the new details + if (signatureCheckPs.sharedUser != null + && pkg.mSigningDetails.hasAncestor( + signatureCheckPs.sharedUser.signatures.mSigningDetails)) { + signatureCheckPs.sharedUser.signatures.mSigningDetails = pkg.mSigningDetails; + } } catch (PackageManagerException e) { if ((parseFlags & PackageParser.PARSE_IS_SYSTEM_DIR) == 0) { throw e; @@ -10181,6 +10207,13 @@ Slog.e("TODD", String msg = "System package " + pkg.packageName + " signature changed; retaining data."; reportSettingsProblem(Log.WARN, msg); + } catch (IllegalArgumentException e) { + + // should never happen: certs matched when checking, but not when comparing + // old to new for sharedUser + throw new PackageManagerException(INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES, + "Signing certificates comparison made on incomparable signing details" + + " but somehow passed verifySignatures!"); } } @@ -16045,10 +16078,10 @@ Slog.e("TODD", return; } } else { + // default to original signature matching - if (compareSignatures(oldPackage.mSigningDetails.signatures, - pkg.mSigningDetails.signatures) - != PackageManager.SIGNATURE_MATCH) { + if (!pkg.mSigningDetails.checkCapability(oldPackage.mSigningDetails, + PackageParser.SigningDetails.CertCapabilities.INSTALLED_DATA)) { res.setError(INSTALL_FAILED_UPDATE_INCOMPATIBLE, "New package has a different signature: " + pkgName); return; @@ -17042,9 +17075,25 @@ Slog.e("TODD", sourcePackageSetting, scanFlags))) { sigsOk = ksms.checkUpgradeKeySetLocked(sourcePackageSetting, pkg); } else { - sigsOk = compareSignatures( - sourcePackageSetting.signatures.mSigningDetails.signatures, - pkg.mSigningDetails.signatures) == PackageManager.SIGNATURE_MATCH; + + // in the event of signing certificate rotation, we need to see if the + // package's certificate has rotated from the current one, or if it is an + // older certificate with which the current is ok with sharing permissions + if (sourcePackageSetting.signatures.mSigningDetails.checkCapability( + pkg.mSigningDetails, + PackageParser.SigningDetails.CertCapabilities.PERMISSION)) { + sigsOk = true; + } else if (pkg.mSigningDetails.checkCapability( + sourcePackageSetting.signatures.mSigningDetails, + PackageParser.SigningDetails.CertCapabilities.PERMISSION)) { + + // the scanned package checks out, has signing certificate rotation + // history, and is newer; bring it over + sourcePackageSetting.signatures.mSigningDetails = pkg.mSigningDetails; + sigsOk = true; + } else { + sigsOk = false; + } } if (!sigsOk) { // If the owning package is the system itself, we log but allow diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java index 76c199b88a5a..c02331da4ed6 100644 --- a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java +++ b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java @@ -531,18 +531,29 @@ public class PackageManagerServiceUtils { // migrate the old signatures to the new scheme packageSignatures.mSigningDetails = parsedSignatures; return true; + } else if (parsedSignatures.hasPastSigningCertificates()) { + + // well this sucks: the parsed package has probably rotated signing certificates, but + // we don't have enough information to determine if the new signing certificate was + // blessed by the old one + logCriticalInfo(Log.INFO, "Existing package " + packageName + " has flattened signing " + + "certificate chain. Unable to install newer version with rotated signing " + + "certificate."); } return false; } - private static boolean matchSignaturesRecover(String packageName, - Signature[] existingSignatures, Signature[] parsedSignatures) { + private static boolean matchSignaturesRecover( + String packageName, + PackageParser.SigningDetails existingSignatures, + PackageParser.SigningDetails parsedSignatures, + @PackageParser.SigningDetails.CertCapabilities int flags) { String msg = null; try { - if (Signature.areEffectiveMatch(existingSignatures, parsedSignatures)) { - logCriticalInfo(Log.INFO, - "Recovered effectively matching certificates for " + packageName); - return true; + if (parsedSignatures.checkCapabilityRecover(existingSignatures, flags)) { + logCriticalInfo(Log.INFO, "Recovered effectively matching certificates for " + + packageName); + return true; } } catch (CertificateException e) { msg = e.getMessage(); @@ -563,9 +574,11 @@ public class PackageManagerServiceUtils { PackageSetting disabledPkgSetting) { try { PackageParser.collectCertificates(disabledPkgSetting.pkg, true /* skipVerify */); - if (compareSignatures(pkgSetting.signatures.mSigningDetails.signatures, - disabledPkgSetting.signatures.mSigningDetails.signatures) - != PackageManager.SIGNATURE_MATCH) { + if (pkgSetting.signatures.mSigningDetails.checkCapability( + disabledPkgSetting.signatures.mSigningDetails, + PackageParser.SigningDetails.CertCapabilities.INSTALLED_DATA)) { + return true; + } else { logCriticalInfo(Log.ERROR, "Updated system app mismatches cert on /system: " + pkgSetting.name); return false; @@ -575,69 +588,6 @@ public class PackageManagerServiceUtils { e.getMessage()); return false; } - return true; - } - - /** - * Checks the signing certificates to see if the provided certificate is a member. Invalid for - * {@code SigningDetails} with multiple signing certificates. - * @param certificate certificate to check for membership - * @param signingDetails signing certificates record whose members are to be searched - * @return true if {@code certificate} is in {@code signingDetails} - */ - public static boolean signingDetailsHasCertificate( - byte[] certificate, PackageParser.SigningDetails signingDetails) { - if (signingDetails == PackageParser.SigningDetails.UNKNOWN) { - return false; - } - Signature signature = new Signature(certificate); - if (signingDetails.hasPastSigningCertificates()) { - for (int i = 0; i < signingDetails.pastSigningCertificates.length; i++) { - if (signingDetails.pastSigningCertificates[i].equals(signature)) { - return true; - } - } - } else { - // no signing history, just check the current signer - if (signingDetails.signatures.length == 1 - && signingDetails.signatures[0].equals(signature)) { - return true; - } - } - return false; - } - - /** - * Checks the signing certificates to see if the provided certificate is a member. Invalid for - * {@code SigningDetails} with multiple signing certificaes. - * @param sha256Certificate certificate to check for membership - * @param signingDetails signing certificates record whose members are to be searched - * @return true if {@code certificate} is in {@code signingDetails} - */ - public static boolean signingDetailsHasSha256Certificate( - byte[] sha256Certificate, PackageParser.SigningDetails signingDetails ) { - if (signingDetails == PackageParser.SigningDetails.UNKNOWN) { - return false; - } - if (signingDetails.hasPastSigningCertificates()) { - for (int i = 0; i < signingDetails.pastSigningCertificates.length; i++) { - byte[] digest = PackageUtils.computeSha256DigestBytes( - signingDetails.pastSigningCertificates[i].toByteArray()); - if (Arrays.equals(sha256Certificate, digest)) { - return true; - } - } - } else { - // no signing history, just check the current signer - if (signingDetails.signatures.length == 1) { - byte[] digest = PackageUtils.computeSha256DigestBytes( - signingDetails.signatures[0].toByteArray()); - if (Arrays.equals(sha256Certificate, digest)) { - return true; - } - } - } - return false; } /** Returns true if APK Verity is enabled. */ @@ -662,10 +612,11 @@ public class PackageManagerServiceUtils { final String packageName = pkgSetting.name; boolean compatMatch = false; if (pkgSetting.signatures.mSigningDetails.signatures != null) { + // Already existing package. Make sure signatures match - boolean match = compareSignatures(pkgSetting.signatures.mSigningDetails.signatures, - parsedSignatures.signatures) - == PackageManager.SIGNATURE_MATCH; + boolean match = parsedSignatures.checkCapability( + pkgSetting.signatures.mSigningDetails, + PackageParser.SigningDetails.CertCapabilities.INSTALLED_DATA); if (!match && compareCompat) { match = matchSignaturesCompat(packageName, pkgSetting.signatures, parsedSignatures); @@ -673,8 +624,10 @@ public class PackageManagerServiceUtils { } if (!match && compareRecover) { match = matchSignaturesRecover( - packageName, pkgSetting.signatures.mSigningDetails.signatures, - parsedSignatures.signatures); + packageName, + pkgSetting.signatures.mSigningDetails, + parsedSignatures, + PackageParser.SigningDetails.CertCapabilities.INSTALLED_DATA); } if (!match && isApkVerificationForced(disabledPkgSetting)) { @@ -689,20 +642,35 @@ public class PackageManagerServiceUtils { } // Check for shared user signatures if (pkgSetting.sharedUser != null - && pkgSetting.sharedUser.signatures.mSigningDetails.signatures != null) { - // Already existing package. Make sure signatures match + && pkgSetting.sharedUser.signatures.mSigningDetails + != PackageParser.SigningDetails.UNKNOWN) { + + // Already existing package. Make sure signatures match. In case of signing certificate + // rotation, the packages with newer certs need to be ok with being sharedUserId with + // the older ones. We check to see if either the new package is signed by an older cert + // with which the current sharedUser is ok, or if it is signed by a newer one, and is ok + // with being sharedUser with the existing signing cert. boolean match = - compareSignatures( - pkgSetting.sharedUser.signatures.mSigningDetails.signatures, - parsedSignatures.signatures) == PackageManager.SIGNATURE_MATCH; + parsedSignatures.checkCapability( + pkgSetting.sharedUser.signatures.mSigningDetails, + PackageParser.SigningDetails.CertCapabilities.SHARED_USER_ID) + || pkgSetting.sharedUser.signatures.mSigningDetails.checkCapability( + parsedSignatures, + PackageParser.SigningDetails.CertCapabilities.SHARED_USER_ID); if (!match && compareCompat) { match = matchSignaturesCompat( packageName, pkgSetting.sharedUser.signatures, parsedSignatures); } if (!match && compareRecover) { - match = matchSignaturesRecover(packageName, - pkgSetting.sharedUser.signatures.mSigningDetails.signatures, - parsedSignatures.signatures); + match = + matchSignaturesRecover(packageName, + pkgSetting.sharedUser.signatures.mSigningDetails, + parsedSignatures, + PackageParser.SigningDetails.CertCapabilities.SHARED_USER_ID) + || matchSignaturesRecover(packageName, + parsedSignatures, + pkgSetting.sharedUser.signatures.mSigningDetails, + PackageParser.SigningDetails.CertCapabilities.SHARED_USER_ID); compatMatch |= match; } if (!match) { diff --git a/services/core/java/com/android/server/pm/PackageSettingBase.java b/services/core/java/com/android/server/pm/PackageSettingBase.java index 18356c570d01..f14a684b3865 100644 --- a/services/core/java/com/android/server/pm/PackageSettingBase.java +++ b/services/core/java/com/android/server/pm/PackageSettingBase.java @@ -23,6 +23,7 @@ import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_ENABLED; import android.content.pm.ApplicationInfo; import android.content.pm.IntentFilterVerificationInfo; import android.content.pm.PackageManager; +import android.content.pm.PackageParser; import android.content.pm.PackageUserState; import android.content.pm.Signature; import android.service.pm.PackageProto; @@ -236,6 +237,10 @@ public abstract class PackageSettingBase extends SettingBase { return signatures.mSigningDetails.signatures; } + public PackageParser.SigningDetails getSigningDetails() { + return signatures.mSigningDetails; + } + /** * Makes a shallow copy of the given package settings. * diff --git a/services/core/java/com/android/server/pm/PackageSignatures.java b/services/core/java/com/android/server/pm/PackageSignatures.java index d471fc837334..0229a371cf26 100644 --- a/services/core/java/com/android/server/pm/PackageSignatures.java +++ b/services/core/java/com/android/server/pm/PackageSignatures.java @@ -182,7 +182,6 @@ class PackageSignatures { Signature sig = readSignatures.get(idx); if (sig != null) { signatures[pos] = readSignatures.get(idx); - pos++; } else { PackageManagerService.reportSettingsProblem(Log.WARN, "Error in package manager settings: <cert> " @@ -202,7 +201,6 @@ class PackageSignatures { Signature sig = new Signature(key); readSignatures.set(idx, sig); signatures[pos] = sig; - pos++; } } catch (NumberFormatException e) { PackageManagerService.reportSettingsProblem(Log.WARN, @@ -245,6 +243,8 @@ class PackageSignatures { + "many <cert> tags, expected " + count + " at " + parser.getPositionDescription()); } + pos++; + XmlUtils.skipCurrentTag(parser); } else if (tagName.equals("pastSigs")) { if (flags == null) { // we haven't encountered pastSigs yet, go ahead @@ -296,8 +296,8 @@ class PackageSignatures { PackageManagerService.reportSettingsProblem(Log.WARN, "Unknown element under <sigs>: " + parser.getName()); + XmlUtils.skipCurrentTag(parser); } - XmlUtils.skipCurrentTag(parser); } return pos; } @@ -330,4 +330,4 @@ class PackageSignatures { } return buf.toString(); } -}
\ No newline at end of file +} diff --git a/services/core/java/com/android/server/pm/SELinuxMMAC.java b/services/core/java/com/android/server/pm/SELinuxMMAC.java index 2552643a6a22..fe5b3d456b87 100644 --- a/services/core/java/com/android/server/pm/SELinuxMMAC.java +++ b/services/core/java/com/android/server/pm/SELinuxMMAC.java @@ -61,10 +61,8 @@ public final class SELinuxMMAC { /** Whether or not the policy files have been read */ private static boolean sPolicyRead; - /** Path to MAC permissions on system image */ - private static final File[] MAC_PERMISSIONS = - { new File(Environment.getRootDirectory(), "/etc/selinux/plat_mac_permissions.xml"), - new File(Environment.getVendorDirectory(), "/etc/selinux/nonplat_mac_permissions.xml") }; + /** Required MAC permissions files */ + private static List<File> sMacPermissions = new ArrayList<>(); // Append privapp to existing seinfo label private static final String PRIVILEGED_APP_STR = ":privapp"; @@ -75,13 +73,40 @@ public final class SELinuxMMAC { // Append targetSdkVersion=n to existing seinfo label where n is the app's targetSdkVersion private static final String TARGETSDKVERSION_STR = ":targetSdkVersion="; + // Only initialize sMacPermissions once. + static { + // Platform mac permissions. + sMacPermissions.add(new File( + Environment.getRootDirectory(), "/etc/selinux/plat_mac_permissions.xml")); + + // Vendor mac permissions. + // The filename has been renamed from nonplat_mac_permissions to + // vendor_mac_permissions. Either of them should exist. + final File vendorMacPermission = new File( + Environment.getVendorDirectory(), "/etc/selinux/vendor_mac_permissions.xml"); + if (vendorMacPermission.exists()) { + sMacPermissions.add(vendorMacPermission); + } else { + // For backward compatibility. + sMacPermissions.add(new File(Environment.getVendorDirectory(), + "/etc/selinux/nonplat_mac_permissions.xml")); + } + + // ODM mac permissions (optional). + final File odmMacPermission = new File( + Environment.getOdmDirectory(), "/etc/selinux/odm_mac_permissions.xml"); + if (odmMacPermission.exists()) { + sMacPermissions.add(odmMacPermission); + } + } + /** * Load the mac_permissions.xml file containing all seinfo assignments used to - * label apps. The loaded mac_permissions.xml file is determined by the - * MAC_PERMISSIONS class variable which is set at class load time which itself - * is based on the USE_OVERRIDE_POLICY class variable. For further guidance on + * label apps. The loaded mac_permissions.xml files are plat_mac_permissions.xml and + * vendor_mac_permissions.xml, on /system and /vendor partitions, respectively. + * odm_mac_permissions.xml on /odm partition is optional. For further guidance on * the proper structure of a mac_permissions.xml file consult the source code - * located at system/sepolicy/mac_permissions.xml. + * located at system/sepolicy/private/mac_permissions.xml. * * @return boolean indicating if policy was correctly loaded. A value of false * typically indicates a structural problem with the xml or incorrectly @@ -100,10 +125,13 @@ public final class SELinuxMMAC { FileReader policyFile = null; XmlPullParser parser = Xml.newPullParser(); - for (int i = 0; i < MAC_PERMISSIONS.length; i++) { + + final int count = sMacPermissions.size(); + for (int i = 0; i < count; ++i) { + final File macPermission = sMacPermissions.get(i); try { - policyFile = new FileReader(MAC_PERMISSIONS[i]); - Slog.d(TAG, "Using policy file " + MAC_PERMISSIONS[i]); + policyFile = new FileReader(macPermission); + Slog.d(TAG, "Using policy file " + macPermission); parser.setInput(policyFile); parser.nextTag(); @@ -127,13 +155,13 @@ public final class SELinuxMMAC { StringBuilder sb = new StringBuilder("Exception @"); sb.append(parser.getPositionDescription()); sb.append(" while parsing "); - sb.append(MAC_PERMISSIONS[i]); + sb.append(macPermission); sb.append(":"); sb.append(ex); Slog.w(TAG, sb.toString()); return false; } catch (IOException ioe) { - Slog.w(TAG, "Exception parsing " + MAC_PERMISSIONS[i], ioe); + Slog.w(TAG, "Exception parsing " + macPermission, ioe); return false; } finally { IoUtils.closeQuietly(policyFile); @@ -454,7 +482,11 @@ final class Policy { Signature[] certs = mCerts.toArray(new Signature[0]); if (pkg.mSigningDetails != SigningDetails.UNKNOWN && !Signature.areExactMatch(certs, pkg.mSigningDetails.signatures)) { - return null; + + // certs aren't exact match, but the package may have rotated from the known system cert + if (certs.length > 1 || !pkg.mSigningDetails.hasCertificate(certs[0])) { + return null; + } } // Check for inner package name matches given that the diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java index 5e9019dfea04..b6e1534d6dcb 100644 --- a/services/core/java/com/android/server/pm/Settings.java +++ b/services/core/java/com/android/server/pm/Settings.java @@ -1996,6 +1996,8 @@ public final class Settings { if (DEBUG_MU) { Log.i(TAG, "Writing package restrictions for user=" + userId); } + final long startTime = SystemClock.uptimeMillis(); + // Keep the old stopped packages around until we know the new ones have // been successfully written. File userPackagesStateFile = getUserPackagesStateFile(userId); @@ -2129,6 +2131,9 @@ public final class Settings { |FileUtils.S_IRGRP|FileUtils.S_IWGRP, -1, -1); + com.android.internal.logging.EventLogTags.writeCommitSysConfigFile( + "package-user-" + userId, SystemClock.uptimeMillis() - startTime); + // Done, all is good! return; } catch(java.io.IOException e) { @@ -2390,6 +2395,8 @@ public final class Settings { void writeLPr() { //Debug.startMethodTracing("/data/system/packageprof", 8 * 1024 * 1024); + final long startTime = SystemClock.uptimeMillis(); + // Keep the old settings around until we know the new ones have // been successfully written. if (mSettingsFilename.exists()) { @@ -2535,6 +2542,8 @@ public final class Settings { writePackageListLPr(); writeAllUsersPackageRestrictionsLPr(); writeAllRuntimePermissionsLPr(); + com.android.internal.logging.EventLogTags.writeCommitSysConfigFile( + "package", SystemClock.uptimeMillis() - startTime); return; } catch(java.io.IOException e) { @@ -5134,7 +5143,8 @@ public final class Settings { } private void writePermissionsSync(int userId) { - AtomicFile destination = new AtomicFile(getUserRuntimePermissionsFile(userId)); + AtomicFile destination = new AtomicFile(getUserRuntimePermissionsFile(userId), + "package-perms-" + userId); ArrayMap<String, List<PermissionState>> permissionsForPackage = new ArrayMap<>(); ArrayMap<String, List<PermissionState>> permissionsForSharedUser = new ArrayMap<>(); diff --git a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java index e2123c25dd81..63087669b2f6 100644 --- a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java +++ b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java @@ -1166,9 +1166,9 @@ public final class DefaultPermissionGrantPolicy { final String systemPackageName = mServiceInternal.getKnownPackageName( PackageManagerInternal.PACKAGE_SYSTEM, UserHandle.USER_SYSTEM); final PackageParser.Package systemPackage = getPackage(systemPackageName); - return compareSignatures(systemPackage.mSigningDetails.signatures, - pkg.mSigningDetails.signatures) - == PackageManager.SIGNATURE_MATCH; + return pkg.mSigningDetails.hasAncestorOrSelf(systemPackage.mSigningDetails) + || systemPackage.mSigningDetails.checkCapability(pkg.mSigningDetails, + PackageParser.SigningDetails.CertCapabilities.PERMISSION); } private void grantDefaultPermissionExceptions(int userId) { diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java index cb3b1073a593..08f8bbd20af6 100644 --- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java +++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java @@ -1022,12 +1022,24 @@ Slog.e(TAG, "TODD: Packages: " + Arrays.toString(packages)); PackageManagerInternal.PACKAGE_SYSTEM, UserHandle.USER_SYSTEM); final PackageParser.Package systemPackage = mPackageManagerInt.getPackage(systemPackageName); - boolean allowed = (PackageManagerServiceUtils.compareSignatures( - bp.getSourceSignatures(), pkg.mSigningDetails.signatures) - == PackageManager.SIGNATURE_MATCH) - || (PackageManagerServiceUtils.compareSignatures( - systemPackage.mSigningDetails.signatures, pkg.mSigningDetails.signatures) - == PackageManager.SIGNATURE_MATCH); + + // check if the package is allow to use this signature permission. A package is allowed to + // use a signature permission if: + // - it has the same set of signing certificates as the source package + // - or its signing certificate was rotated from the source package's certificate + // - or its signing certificate is a previous signing certificate of the defining + // package, and the defining package still trusts the old certificate for permissions + // - or it shares the above relationships with the system package + boolean allowed = + pkg.mSigningDetails.hasAncestorOrSelf( + bp.getSourcePackageSetting().getSigningDetails()) + || bp.getSourcePackageSetting().getSigningDetails().checkCapability( + pkg.mSigningDetails, + PackageParser.SigningDetails.CertCapabilities.PERMISSION) + || pkg.mSigningDetails.hasAncestorOrSelf(systemPackage.mSigningDetails) + || systemPackage.mSigningDetails.checkCapability( + pkg.mSigningDetails, + PackageParser.SigningDetails.CertCapabilities.PERMISSION); if (!allowed && (privilegedPermission || oemPermission)) { if (pkg.isSystem()) { // For updated system applications, a privileged/oem permission diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java index 0f394a4eed65..0502848d698e 100644 --- a/services/core/java/com/android/server/policy/PhoneWindowManager.java +++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java @@ -1025,7 +1025,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { public void run() { // send interaction hint to improve redraw performance mPowerManagerInternal.powerHint(PowerHint.INTERACTION, 0); - if (isRotationChoiceEnabled()) { + if (isRotationChoicePossible(mCurrentAppOrientation)) { final boolean isValid = isValidRotationChoice(mCurrentAppOrientation, mRotation); sendProposedRotationChangeToStatusBarInternal(mRotation, isValid); @@ -7144,7 +7144,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { mOrientationListener.setCurrentRotation(rotation); } - public boolean isRotationChoiceEnabled() { + public boolean isRotationChoicePossible(int orientation) { // Rotation choice is only shown when the user is in locked mode. if (mUserRotationMode != WindowManagerPolicy.USER_ROTATION_LOCKED) return false; @@ -7184,50 +7184,45 @@ public class PhoneWindowManager implements WindowManagerPolicy { return false; } - // Rotation isn't forced, enable choice - return true; + // Ensure that some rotation choice is possible for the given orientation + switch (orientation) { + case ActivityInfo.SCREEN_ORIENTATION_FULL_USER: + case ActivityInfo.SCREEN_ORIENTATION_USER: + case ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED: + case ActivityInfo.SCREEN_ORIENTATION_USER_LANDSCAPE: + case ActivityInfo.SCREEN_ORIENTATION_USER_PORTRAIT: + // NOSENSOR description is ambiguous, in reality WM ignores user choice + return true; + } + + // Rotation is forced, should be controlled by system + return false; } public boolean isValidRotationChoice(int orientation, final int preferredRotation) { - // Determine if the given app orientation can be chosen and, if so, if it is compatible - // with the provided rotation choice - + // Determine if the given app orientation is compatible with the provided rotation choice switch (orientation) { - case ActivityInfo.SCREEN_ORIENTATION_PORTRAIT: - case ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE: - case ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT: - case ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE: - case ActivityInfo.SCREEN_ORIENTATION_LOCKED: - return false; // Forced into a particular rotation, no user choice - - case ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE: - case ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT: - case ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR: - case ActivityInfo.SCREEN_ORIENTATION_SENSOR: - return false; // Sensor overrides user choice + case ActivityInfo.SCREEN_ORIENTATION_FULL_USER: + // Works with any of the 4 rotations + return preferredRotation >= 0; - case ActivityInfo.SCREEN_ORIENTATION_NOSENSOR: - // TODO Can sensor be used to indirectly determine the orientation? - return false; + case ActivityInfo.SCREEN_ORIENTATION_USER_PORTRAIT: + // It's possible for the user pref to be set at 180 because of FULL_USER. This would + // make switching to USER_PORTRAIT appear at 180. Provide choice to back to portrait + // but never to go to 180. + return preferredRotation == mPortraitRotation; case ActivityInfo.SCREEN_ORIENTATION_USER_LANDSCAPE: - // If the user has locked sensor-based rotation, this behaves the same as landscape - return false; // User has locked the rotation, will behave as LANDSCAPE - case ActivityInfo.SCREEN_ORIENTATION_USER_PORTRAIT: - // If the user has locked sensor-based rotation, this behaves the same as portrait - return false; // User has locked the rotation, will behave as PORTRAIT + // Works landscape or seascape + return isLandscapeOrSeascape(preferredRotation); + case ActivityInfo.SCREEN_ORIENTATION_USER: + case ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED: // Works with any rotation except upside down return (preferredRotation >= 0) && (preferredRotation != mUpsideDownRotation); - case ActivityInfo.SCREEN_ORIENTATION_FULL_USER: - // Works with any of the 4 rotations - return preferredRotation >= 0; - - default: - // TODO: how to handle SCREEN_ORIENTATION_BEHIND, UNSET? - // For UNSPECIFIED use preferred orientation matching SCREEN_ORIENTATION_USER - return (preferredRotation >= 0) && (preferredRotation != mUpsideDownRotation); } + + return false; } private boolean isLandscapeOrSeascape(int rotation) { diff --git a/services/core/java/com/android/server/power/BatterySaverPolicy.java b/services/core/java/com/android/server/power/BatterySaverPolicy.java index a538967e501d..847c90a08c5c 100644 --- a/services/core/java/com/android/server/power/BatterySaverPolicy.java +++ b/services/core/java/com/android/server/power/BatterySaverPolicy.java @@ -33,6 +33,7 @@ import android.util.Slog; import com.android.internal.R; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; +import com.android.server.power.batterysaver.BatterySavingStats; import com.android.server.power.batterysaver.CpuFrequencies; import java.io.PrintWriter; @@ -498,6 +499,8 @@ public class BatterySaverPolicy extends ContentObserver { pw.print(" Noninteractive File values:\n"); dumpMap(pw, " ", mFilesForNoninteractive); pw.println(); + pw.println(); + BatterySavingStats.getInstance().dump(pw, " "); } } diff --git a/services/core/java/com/android/server/power/ShutdownThread.java b/services/core/java/com/android/server/power/ShutdownThread.java index b986e046347c..bd4aa1cce355 100644 --- a/services/core/java/com/android/server/power/ShutdownThread.java +++ b/services/core/java/com/android/server/power/ShutdownThread.java @@ -21,6 +21,7 @@ import android.app.AlertDialog; import android.app.Dialog; import android.app.IActivityManager; import android.app.ProgressDialog; +import android.app.admin.SecurityLog; import android.content.BroadcastReceiver; import android.content.Context; import android.content.DialogInterface; @@ -113,6 +114,7 @@ public final class ShutdownThread extends Thread { private static String METRIC_PM = "shutdown_package_manager"; private static String METRIC_RADIOS = "shutdown_radios"; private static String METRIC_RADIO = "shutdown_radio"; + private static String METRIC_SHUTDOWN_TIME_START = "begin_shutdown"; private final Object mActionDoneSync = new Object(); private boolean mActionDone; @@ -390,6 +392,10 @@ public final class ShutdownThread extends Thread { } } + if (SecurityLog.isLoggingEnabled()) { + SecurityLog.writeEvent(SecurityLog.TAG_OS_SHUTDOWN); + } + // start the thread that initiates shutdown sInstance.mHandler = new Handler() { }; @@ -410,6 +416,7 @@ public final class ShutdownThread extends Thread { public void run() { TimingsTraceLog shutdownTimingLog = newTimingsLog(); shutdownTimingLog.traceBegin("SystemServerShutdown"); + metricShutdownStart(); metricStarted(METRIC_SYSTEM_SERVER); BroadcastReceiver br = new BroadcastReceiver() { @@ -525,7 +532,7 @@ public final class ShutdownThread extends Thread { shutdownTimingLog.traceEnd(); // SystemServerShutdown metricEnded(METRIC_SYSTEM_SERVER); - saveMetrics(mReboot); + saveMetrics(mReboot, mReason); // Remaining work will be done by init, including vold shutdown rebootOrShutdown(mContext, mReboot, mReason); } @@ -547,6 +554,12 @@ public final class ShutdownThread extends Thread { } } + private static void metricShutdownStart() { + synchronized (TRON_METRICS) { + TRON_METRICS.put(METRIC_SHUTDOWN_TIME_START, System.currentTimeMillis()); + } + } + private void setRebootProgress(final int progress, final CharSequence message) { mHandler.post(new Runnable() { @Override @@ -668,10 +681,11 @@ public final class ShutdownThread extends Thread { PowerManagerService.lowLevelShutdown(reason); } - private static void saveMetrics(boolean reboot) { + private static void saveMetrics(boolean reboot, String reason) { StringBuilder metricValue = new StringBuilder(); metricValue.append("reboot:"); metricValue.append(reboot ? "y" : "n"); + metricValue.append(",").append("reason:").append(reason); final int metricsSize = TRON_METRICS.size(); for (int i = 0; i < metricsSize; i++) { final String name = TRON_METRICS.keyAt(i); diff --git a/services/core/java/com/android/server/power/batterysaver/BatterySaverController.java b/services/core/java/com/android/server/power/batterysaver/BatterySaverController.java index d4627c2da07e..32f38b7cf772 100644 --- a/services/core/java/com/android/server/power/batterysaver/BatterySaverController.java +++ b/services/core/java/com/android/server/power/batterysaver/BatterySaverController.java @@ -28,6 +28,7 @@ import android.content.Intent; import android.content.IntentFilter; import android.hardware.power.V1_0.PowerHint; import android.net.Uri; +import android.os.BatteryManager; import android.os.Handler; import android.os.Looper; import android.os.Message; @@ -50,6 +51,9 @@ import com.android.server.LocalServices; import com.android.server.power.BatterySaverPolicy; import com.android.server.power.BatterySaverPolicy.BatterySaverPolicyListener; import com.android.server.power.PowerManagerService; +import com.android.server.power.batterysaver.BatterySavingStats.BatterySaverState; +import com.android.server.power.batterysaver.BatterySavingStats.DozeState; +import com.android.server.power.batterysaver.BatterySavingStats.InteractiveState; import java.util.ArrayList; @@ -70,6 +74,8 @@ public class BatterySaverController implements BatterySaverPolicyListener { private final BatterySaverPolicy mBatterySaverPolicy; + private final BatterySavingStats mBatterySavingStats; + private static final String WARNING_LINK_URL = "http://goto.google.com/extreme-battery-saver"; @GuardedBy("mLock") @@ -78,6 +84,9 @@ public class BatterySaverController implements BatterySaverPolicyListener { @GuardedBy("mLock") private boolean mEnabled; + @GuardedBy("mLock") + private boolean mIsPluggedIn; + /** * Previously enabled or not; only for the event logging. Only use it from * {@link #handleBatterySaverStateChanged}. @@ -104,15 +113,28 @@ public class BatterySaverController implements BatterySaverPolicyListener { private final BroadcastReceiver mReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { + if (DEBUG) { + Slog.d(TAG, "onReceive: " + intent); + } switch (intent.getAction()) { case Intent.ACTION_SCREEN_ON: case Intent.ACTION_SCREEN_OFF: if (!isEnabled()) { + updateBatterySavingStats(); return; // No need to send it if not enabled. } // Don't send the broadcast, because we never did so in this case. mHandler.postStateChanged(/*sendBroadcast=*/ false); break; + case Intent.ACTION_BATTERY_CHANGED: + synchronized (mLock) { + mIsPluggedIn = (intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, 0) != 0); + } + // Fall-through. + case PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED: + case PowerManager.ACTION_LIGHT_DEVICE_IDLE_MODE_CHANGED: + updateBatterySavingStats(); + break; } } }; @@ -126,6 +148,7 @@ public class BatterySaverController implements BatterySaverPolicyListener { mBatterySaverPolicy = policy; mBatterySaverPolicy.addListener(this); mFileUpdater = new FileUpdater(context); + mBatterySavingStats = BatterySavingStats.getInstance(); // Initialize plugins. final ArrayList<Plugin> plugins = new ArrayList<>(); @@ -149,6 +172,9 @@ public class BatterySaverController implements BatterySaverPolicyListener { public void systemReady() { final IntentFilter filter = new IntentFilter(Intent.ACTION_SCREEN_ON); filter.addAction(Intent.ACTION_SCREEN_OFF); + filter.addAction(Intent.ACTION_BATTERY_CHANGED); + filter.addAction(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED); + filter.addAction(PowerManager.ACTION_LIGHT_DEVICE_IDLE_MODE_CHANGED); mContext.registerReceiver(mReceiver, filter); mFileUpdater.systemReady(LocalServices.getService(ActivityManagerInternal.class) @@ -280,7 +306,6 @@ public class BatterySaverController implements BatterySaverPolicyListener { enabled = mEnabled; mIsInteractive = isInteractive; - if (enabled) { fileValues = mBatterySaverPolicy.getFileValues(isInteractive); } else { @@ -293,6 +318,8 @@ public class BatterySaverController implements BatterySaverPolicyListener { pmi.powerHint(PowerHint.LOW_POWER, enabled ? 1 : 0); } + updateBatterySavingStats(); + if (ArrayUtils.isEmpty(fileValues)) { mFileUpdater.restoreDefault(); } else { @@ -332,7 +359,6 @@ public class BatterySaverController implements BatterySaverPolicyListener { mContext.sendBroadcastAsUser(intent, UserHandle.ALL, Manifest.permission.DEVICE_POWER); - for (LowPowerModeListener listener : listeners) { final PowerSaveState result = mBatterySaverPolicy.getBatterySaverPolicy( @@ -388,4 +414,28 @@ public class BatterySaverController implements BatterySaverPolicyListener { foregroundUser); } } + + private void updateBatterySavingStats() { + final PowerManager pm = getPowerManager(); + if (pm == null) { + Slog.wtf(TAG, "PowerManager not initialized"); + return; + } + final boolean isInteractive = pm.isInteractive(); + final int dozeMode = + pm.isDeviceIdleMode() ? DozeState.DEEP + : pm.isLightDeviceIdleMode() ? DozeState.LIGHT + : DozeState.NOT_DOZING; + + synchronized (mLock) { + if (mIsPluggedIn) { + mBatterySavingStats.startCharging(); + return; + } + mBatterySavingStats.transitionState( + mEnabled ? BatterySaverState.ON : BatterySaverState.OFF, + isInteractive ? InteractiveState.INTERACTIVE : InteractiveState.NON_INTERACTIVE, + dozeMode); + } + } } diff --git a/services/core/java/com/android/server/power/batterysaver/BatterySavingStats.java b/services/core/java/com/android/server/power/batterysaver/BatterySavingStats.java new file mode 100644 index 000000000000..946635043c12 --- /dev/null +++ b/services/core/java/com/android/server/power/batterysaver/BatterySavingStats.java @@ -0,0 +1,414 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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.power.batterysaver; + +import android.os.BatteryManagerInternal; +import android.os.SystemClock; +import android.util.ArrayMap; +import android.util.Slog; + +import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.logging.MetricsLogger; +import com.android.server.EventLogTags; +import com.android.server.LocalServices; +import com.android.server.power.BatterySaverPolicy; + +import java.io.PrintWriter; + +/** + * This class keeps track of battery drain rate. + * + * Test: + atest $ANDROID_BUILD_TOP/frameworks/base/services/tests/servicestests/src/com/android/server/power/batterysaver/BatterySavingStatsTest.java + */ +public class BatterySavingStats { + + private static final String TAG = "BatterySavingStats"; + + private static final boolean DEBUG = BatterySaverPolicy.DEBUG; + + private final Object mLock = new Object(); + + /** Whether battery saver is on or off. */ + interface BatterySaverState { + int OFF = 0; + int ON = 1; + + int SHIFT = 0; + int BITS = 1; + int MASK = (1 << BITS) - 1; + + static int fromIndex(int index) { + return (index >> SHIFT) & MASK; + } + } + + /** Whether the device is interactive (i.e. screen on) or not. */ + interface InteractiveState { + int NON_INTERACTIVE = 0; + int INTERACTIVE = 1; + + int SHIFT = BatterySaverState.SHIFT + BatterySaverState.BITS; + int BITS = 1; + int MASK = (1 << BITS) - 1; + + static int fromIndex(int index) { + return (index >> SHIFT) & MASK; + } + } + + /** Doze mode. */ + interface DozeState { + int NOT_DOZING = 0; + int LIGHT = 1; + int DEEP = 2; + + int SHIFT = InteractiveState.SHIFT + InteractiveState.BITS; + int BITS = 2; + int MASK = (1 << BITS) - 1; + + static int fromIndex(int index) { + return (index >> SHIFT) & MASK; + } + } + + /** + * Various stats in each state. + */ + static class Stat { + public long startTime; + public long endTime; + + public int startBatteryLevel; + public int endBatteryLevel; + + public long totalTimeMillis; + public int totalBatteryDrain; + + public long totalMinutes() { + return totalTimeMillis / 60_000; + } + + public double drainPerHour() { + if (totalTimeMillis == 0) { + return 0; + } + return (double) totalBatteryDrain / (totalTimeMillis / (60.0 * 60 * 1000)); + } + + @VisibleForTesting + String toStringForTest() { + return "{" + totalMinutes() + "m," + totalBatteryDrain + "," + + String.format("%.2f", drainPerHour()) + "}"; + } + } + + @VisibleForTesting + static final String COUNTER_POWER_MILLIAMPS_PREFIX = "battery_saver_stats_milliamps_"; + + @VisibleForTesting + static final String COUNTER_TIME_SECONDS_PREFIX = "battery_saver_stats_seconds_"; + + private static BatterySavingStats sInstance; + + private BatteryManagerInternal mBatteryManagerInternal; + private final MetricsLogger mMetricsLogger; + + private static final int STATE_NOT_INITIALIZED = -1; + private static final int STATE_CHARGING = -2; + + /** + * Current state, one of STATE_* or values returned by {@link #statesToIndex}. + */ + @GuardedBy("mLock") + private int mCurrentState = STATE_NOT_INITIALIZED; + + /** + * Stats in each state. + */ + @VisibleForTesting + @GuardedBy("mLock") + final ArrayMap<Integer, Stat> mStats = new ArrayMap<>(); + + private final MetricsLoggerHelper mMetricsLoggerHelper = new MetricsLoggerHelper(); + + /** + * Don't call it directly -- use {@link #getInstance()}. Not private for testing. + * @param metricsLogger + */ + @VisibleForTesting + BatterySavingStats(MetricsLogger metricsLogger) { + mBatteryManagerInternal = LocalServices.getService(BatteryManagerInternal.class); + mMetricsLogger = metricsLogger; + } + + public static synchronized BatterySavingStats getInstance() { + if (sInstance == null) { + sInstance = new BatterySavingStats(new MetricsLogger()); + } + return sInstance; + } + + private BatteryManagerInternal getBatteryManagerInternal() { + if (mBatteryManagerInternal == null) { + mBatteryManagerInternal = LocalServices.getService(BatteryManagerInternal.class); + } + return mBatteryManagerInternal; + } + + /** + * Takes a state triplet and generates a state index. + */ + @VisibleForTesting + static int statesToIndex( + int batterySaverState, int interactiveState, int dozeState) { + int ret = batterySaverState & BatterySaverState.MASK; + ret |= (interactiveState & InteractiveState.MASK) << InteractiveState.SHIFT; + ret |= (dozeState & DozeState.MASK) << DozeState.SHIFT; + return ret; + } + + /** + * Takes a state index and returns a string for logging. + */ + @VisibleForTesting + static String stateToString(int state) { + switch (state) { + case STATE_NOT_INITIALIZED: + return "NotInitialized"; + case STATE_CHARGING: + return "Charging"; + } + return "BS=" + BatterySaverState.fromIndex(state) + + ",I=" + InteractiveState.fromIndex(state) + + ",D=" + DozeState.fromIndex(state); + } + + /** + * @return {@link Stat} fo a given state. + */ + @VisibleForTesting + Stat getStat(int stateIndex) { + synchronized (mLock) { + Stat stat = mStats.get(stateIndex); + if (stat == null) { + stat = new Stat(); + mStats.put(stateIndex, stat); + } + return stat; + } + } + + /** + * @return {@link Stat} fo a given state triplet. + */ + private Stat getStat(int batterySaverState, int interactiveState, int dozeState) { + return getStat(statesToIndex(batterySaverState, interactiveState, dozeState)); + } + + @VisibleForTesting + long injectCurrentTime() { + return SystemClock.elapsedRealtime(); + } + + @VisibleForTesting + int injectBatteryLevel() { + final BatteryManagerInternal bmi = getBatteryManagerInternal(); + if (bmi == null) { + Slog.wtf(TAG, "BatteryManagerInternal not initialized"); + return 0; + } + return bmi.getBatteryChargeCounter(); + } + + /** + * Called from the outside whenever any of the states changes, when the device is not plugged + * in. + */ + public void transitionState(int batterySaverState, int interactiveState, int dozeState) { + synchronized (mLock) { + final int newState = statesToIndex( + batterySaverState, interactiveState, dozeState); + transitionStateLocked(newState); + } + } + + /** + * Called from the outside when the device is plugged in. + */ + public void startCharging() { + synchronized (mLock) { + transitionStateLocked(STATE_CHARGING); + } + } + + private void transitionStateLocked(int newState) { + if (mCurrentState == newState) { + return; + } + final long now = injectCurrentTime(); + final int batteryLevel = injectBatteryLevel(); + + endLastStateLocked(now, batteryLevel); + startNewStateLocked(newState, now, batteryLevel); + mMetricsLoggerHelper.transitionState(newState, now, batteryLevel); + } + + private void endLastStateLocked(long now, int batteryLevel) { + if (mCurrentState < 0) { + return; + } + final Stat stat = getStat(mCurrentState); + + stat.endBatteryLevel = batteryLevel; + stat.endTime = now; + + final long deltaTime = stat.endTime - stat.startTime; + final int deltaDrain = stat.startBatteryLevel - stat.endBatteryLevel; + + stat.totalTimeMillis += deltaTime; + stat.totalBatteryDrain += deltaDrain; + + if (DEBUG) { + Slog.d(TAG, "State summary: " + stateToString(mCurrentState) + + ": " + (deltaTime / 1_000) + "s " + + "Start level: " + stat.startBatteryLevel + "uA " + + "End level: " + stat.endBatteryLevel + "uA " + + deltaDrain + "uA"); + } + EventLogTags.writeBatterySavingStats( + BatterySaverState.fromIndex(mCurrentState), + InteractiveState.fromIndex(mCurrentState), + DozeState.fromIndex(mCurrentState), + deltaTime, + deltaDrain, + stat.totalTimeMillis, + stat.totalBatteryDrain); + + } + + private void startNewStateLocked(int newState, long now, int batteryLevel) { + if (DEBUG) { + Slog.d(TAG, "New state: " + stateToString(newState)); + } + mCurrentState = newState; + + if (mCurrentState < 0) { + return; + } + + final Stat stat = getStat(mCurrentState); + stat.startBatteryLevel = batteryLevel; + stat.startTime = now; + stat.endTime = 0; + } + + public void dump(PrintWriter pw, String indent) { + synchronized (mLock) { + pw.print(indent); + pw.println("Battery Saving Stats:"); + + indent = indent + " "; + + pw.print(indent); + pw.println("Battery Saver: Off On"); + dumpLineLocked(pw, indent, InteractiveState.NON_INTERACTIVE, "NonIntr", + DozeState.NOT_DOZING, "NonDoze"); + dumpLineLocked(pw, indent, InteractiveState.INTERACTIVE, " Intr", + DozeState.NOT_DOZING, " "); + + dumpLineLocked(pw, indent, InteractiveState.NON_INTERACTIVE, "NonIntr", + DozeState.DEEP, "Deep "); + dumpLineLocked(pw, indent, InteractiveState.INTERACTIVE, " Intr", + DozeState.DEEP, " "); + + dumpLineLocked(pw, indent, InteractiveState.NON_INTERACTIVE, "NonIntr", + DozeState.LIGHT, "Light "); + dumpLineLocked(pw, indent, InteractiveState.INTERACTIVE, " Intr", + DozeState.LIGHT, " "); + + pw.println(); + } + } + + private void dumpLineLocked(PrintWriter pw, String indent, + int interactiveState, String interactiveLabel, + int dozeState, String dozeLabel) { + pw.print(indent); + pw.print(dozeLabel); + pw.print(" "); + pw.print(interactiveLabel); + pw.print(": "); + + final Stat offStat = getStat(BatterySaverState.OFF, interactiveState, dozeState); + final Stat onStat = getStat(BatterySaverState.ON, interactiveState, dozeState); + + pw.println(String.format("%6dm %6dmA %8.1fmA/h %6dm %6dmA %8.1fmA/h", + offStat.totalMinutes(), + offStat.totalBatteryDrain / 1000, + offStat.drainPerHour() / 1000.0, + onStat.totalMinutes(), + onStat.totalBatteryDrain / 1000, + onStat.drainPerHour() / 1000.0)); + } + + @VisibleForTesting + class MetricsLoggerHelper { + private int mLastState = STATE_NOT_INITIALIZED; + private long mStartTime; + private int mStartBatteryLevel; + + private static final int STATE_CHANGE_DETECT_MASK = + (BatterySaverState.MASK << BatterySaverState.SHIFT) | + (InteractiveState.MASK << InteractiveState.SHIFT); + + public void transitionState(int newState, long now, int batteryLevel) { + final boolean stateChanging = + ((mLastState >= 0) ^ (newState >= 0)) || + (((mLastState ^ newState) & STATE_CHANGE_DETECT_MASK) != 0); + if (stateChanging) { + if (mLastState >= 0) { + final long deltaTime = now - mStartTime; + final int deltaBattery = mStartBatteryLevel - batteryLevel; + + report(mLastState, deltaTime, deltaBattery); + } + mStartTime = now; + mStartBatteryLevel = batteryLevel; + } + mLastState = newState; + } + + String getCounterSuffix(int state) { + final boolean batterySaver = + BatterySaverState.fromIndex(state) != BatterySaverState.OFF; + final boolean interactive = + InteractiveState.fromIndex(state) != InteractiveState.NON_INTERACTIVE; + if (batterySaver) { + return interactive ? "11" : "10"; + } else { + return interactive ? "01" : "00"; + } + } + + void report(int state, long deltaTimeMs, int deltaBatteryUa) { + final String suffix = getCounterSuffix(state); + mMetricsLogger.count(COUNTER_POWER_MILLIAMPS_PREFIX + suffix, deltaBatteryUa / 1000); + mMetricsLogger.count(COUNTER_TIME_SECONDS_PREFIX + suffix, (int) (deltaTimeMs / 1000)); + } + } +} diff --git a/services/core/java/com/android/server/slice/PinnedSliceState.java b/services/core/java/com/android/server/slice/PinnedSliceState.java index 5811714c73c4..8da16d7ea148 100644 --- a/services/core/java/com/android/server/slice/PinnedSliceState.java +++ b/services/core/java/com/android/server/slice/PinnedSliceState.java @@ -14,12 +14,15 @@ package com.android.server.slice; +import static android.app.slice.SliceManager.PERMISSION_GRANTED; + import android.app.slice.ISliceListener; import android.app.slice.Slice; import android.app.slice.SliceProvider; import android.app.slice.SliceSpec; import android.content.ContentProviderClient; import android.net.Uri; +import android.os.Binder; import android.os.Bundle; import android.os.IBinder; import android.os.IBinder.DeathRecipient; @@ -51,18 +54,16 @@ public class PinnedSliceState { @GuardedBy("mLock") private final ArraySet<String> mPinnedPkgs = new ArraySet<>(); @GuardedBy("mLock") - private final ArrayMap<IBinder, ISliceListener> mListeners = new ArrayMap<>(); + private final ArrayMap<IBinder, ListenerInfo> mListeners = new ArrayMap<>(); @GuardedBy("mLock") private SliceSpec[] mSupportedSpecs = null; - @GuardedBy("mLock") - private final ArrayMap<IBinder, String> mPkgMap = new ArrayMap<>(); private final DeathRecipient mDeathRecipient = this::handleRecheckListeners; + private boolean mSlicePinned; public PinnedSliceState(SliceManagerService service, Uri uri) { mService = service; mUri = uri; - mService.getHandler().post(this::handleSendPinned); mLock = mService.getLock(); } @@ -102,14 +103,27 @@ public class PinnedSliceState { } public void destroy() { - mService.getHandler().post(this::handleSendUnpinned); + setSlicePinned(false); } public void onChange() { mService.getHandler().post(this::handleBind); } - public void addSliceListener(ISliceListener listener, String pkg, SliceSpec[] specs) { + private void setSlicePinned(boolean pinned) { + synchronized (mLock) { + if (mSlicePinned == pinned) return; + mSlicePinned = pinned; + if (pinned) { + mService.getHandler().post(this::handleSendPinned); + } else { + mService.getHandler().post(this::handleSendUnpinned); + } + } + } + + public void addSliceListener(ISliceListener listener, String pkg, SliceSpec[] specs, + boolean hasPermission) { synchronized (mLock) { if (mListeners.size() == 0) { mService.listen(mUri); @@ -118,26 +132,27 @@ public class PinnedSliceState { listener.asBinder().linkToDeath(mDeathRecipient, 0); } catch (RemoteException e) { } - mListeners.put(listener.asBinder(), listener); - mPkgMap.put(listener.asBinder(), pkg); + mListeners.put(listener.asBinder(), new ListenerInfo(listener, pkg, hasPermission, + Binder.getCallingUid(), Binder.getCallingPid())); mergeSpecs(specs); + setSlicePinned(hasPermission); } } public boolean removeSliceListener(ISliceListener listener) { synchronized (mLock) { listener.asBinder().unlinkToDeath(mDeathRecipient, 0); - mPkgMap.remove(listener.asBinder()); if (mListeners.containsKey(listener.asBinder()) && mListeners.size() == 1) { mService.unlisten(mUri); } mListeners.remove(listener.asBinder()); } - return !isPinned(); + return !hasPinOrListener(); } public void pin(String pkg, SliceSpec[] specs) { synchronized (mLock) { + setSlicePinned(true); mPinnedPkgs.add(pkg); mergeSpecs(specs); } @@ -147,7 +162,7 @@ public class PinnedSliceState { synchronized (mLock) { mPinnedPkgs.remove(pkg); } - return !isPinned(); + return !hasPinOrListener(); } public boolean isListening() { @@ -156,8 +171,32 @@ public class PinnedSliceState { } } + public void recheckPackage(String pkg) { + synchronized (mLock) { + for (int i = 0; i < mListeners.size(); i++) { + ListenerInfo info = mListeners.valueAt(i); + if (!info.hasPermission && Objects.equals(info.pkg, pkg)) { + mService.getHandler().post(() -> { + // This bind lets the app itself participate in the permission grant. + Slice s = doBind(info); + if (mService.checkAccess(info.pkg, mUri, info.callingUid, info.callingPid) + == PERMISSION_GRANTED) { + info.hasPermission = true; + setSlicePinned(true); + try { + info.listener.onSliceUpdated(s); + } catch (RemoteException e) { + checkSelfRemove(); + } + } + }); + } + } + } + } + @VisibleForTesting - public boolean isPinned() { + public boolean hasPinOrListener() { synchronized (mLock) { return !mPinnedPkgs.isEmpty() || !mListeners.isEmpty(); } @@ -166,62 +205,71 @@ public class PinnedSliceState { ContentProviderClient getClient() { ContentProviderClient client = mService.getContext().getContentResolver().acquireContentProviderClient(mUri); + if (client == null) return null; client.setDetectNotResponding(SLICE_TIMEOUT); return client; } + private void checkSelfRemove() { + if (!hasPinOrListener()) { + // All the listeners died, remove from pinned state. + mService.unlisten(mUri); + mService.removePinnedSlice(mUri); + } + } + private void handleRecheckListeners() { - if (!isPinned()) return; + if (!hasPinOrListener()) return; synchronized (mLock) { for (int i = mListeners.size() - 1; i >= 0; i--) { - ISliceListener l = mListeners.valueAt(i); - if (!l.asBinder().isBinderAlive()) { + ListenerInfo l = mListeners.valueAt(i); + if (!l.listener.asBinder().isBinderAlive()) { mListeners.removeAt(i); } } - if (!isPinned()) { - // All the listeners died, remove from pinned state. - mService.removePinnedSlice(mUri); - } + checkSelfRemove(); } } private void handleBind() { Slice cachedSlice = doBind(null); synchronized (mLock) { - if (!isPinned()) return; + if (!hasPinOrListener()) return; for (int i = mListeners.size() - 1; i >= 0; i--) { - ISliceListener l = mListeners.valueAt(i); + ListenerInfo info = mListeners.valueAt(i); Slice s = cachedSlice; - if (s == null || s.hasHint(Slice.HINT_CALLER_NEEDED)) { - s = doBind(mPkgMap.get(l)); + if (s == null || s.hasHint(Slice.HINT_CALLER_NEEDED) + || !info.hasPermission) { + s = doBind(info); } if (s == null) { mListeners.removeAt(i); continue; } try { - l.onSliceUpdated(s); + info.listener.onSliceUpdated(s); } catch (RemoteException e) { Log.e(TAG, "Unable to notify slice " + mUri, e); mListeners.removeAt(i); continue; } } - if (!isPinned()) { - // All the listeners died, remove from pinned state. - mService.removePinnedSlice(mUri); - } + checkSelfRemove(); } } - private Slice doBind(String overridePkg) { + private Slice doBind(ListenerInfo info) { try (ContentProviderClient client = getClient()) { + if (client == null) return null; Bundle extras = new Bundle(); extras.putParcelable(SliceProvider.EXTRA_BIND_URI, mUri); extras.putParcelableArrayList(SliceProvider.EXTRA_SUPPORTED_SPECS, new ArrayList<>(Arrays.asList(mSupportedSpecs))); - extras.putString(SliceProvider.EXTRA_OVERRIDE_PKG, overridePkg); + if (info != null) { + extras.putString(SliceProvider.EXTRA_OVERRIDE_PKG, info.pkg); + extras.putInt(SliceProvider.EXTRA_OVERRIDE_UID, info.callingUid); + extras.putInt(SliceProvider.EXTRA_OVERRIDE_PID, info.callingPid); + } final Bundle res; try { res = client.call(SliceProvider.METHOD_SLICE, null, extras); @@ -232,11 +280,16 @@ public class PinnedSliceState { if (res == null) return null; Bundle.setDefusable(res, true); return res.getParcelable(SliceProvider.EXTRA_SLICE); + } catch (Throwable t) { + // Calling out of the system process, make sure they don't throw anything at us. + Log.e(TAG, "Caught throwable while binding " + mUri, t); + return null; } } private void handleSendPinned() { try (ContentProviderClient client = getClient()) { + if (client == null) return; Bundle b = new Bundle(); b.putParcelable(SliceProvider.EXTRA_BIND_URI, mUri); try { @@ -249,6 +302,7 @@ public class PinnedSliceState { private void handleSendUnpinned() { try (ContentProviderClient client = getClient()) { + if (client == null) return; Bundle b = new Bundle(); b.putParcelable(SliceProvider.EXTRA_BIND_URI, mUri); try { @@ -258,4 +312,22 @@ public class PinnedSliceState { } } } + + private class ListenerInfo { + + private ISliceListener listener; + private String pkg; + private boolean hasPermission; + private int callingUid; + private int callingPid; + + public ListenerInfo(ISliceListener listener, String pkg, boolean hasPermission, + int callingUid, int callingPid) { + this.listener = listener; + this.pkg = pkg; + this.hasPermission = hasPermission; + this.callingUid = callingUid; + this.callingPid = callingPid; + } + } } diff --git a/services/core/java/com/android/server/slice/SliceFullAccessList.java b/services/core/java/com/android/server/slice/SliceFullAccessList.java new file mode 100644 index 000000000000..591e809ad3d1 --- /dev/null +++ b/services/core/java/com/android/server/slice/SliceFullAccessList.java @@ -0,0 +1,131 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the + * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package com.android.server.slice; + +import android.content.Context; +import android.content.pm.UserInfo; +import android.os.UserManager; +import android.util.ArraySet; +import android.util.Log; +import android.util.SparseArray; + +import com.android.internal.util.XmlUtils; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; +import org.xmlpull.v1.XmlSerializer; + +import java.io.IOException; +import java.util.List; + +public class SliceFullAccessList { + + static final int DB_VERSION = 1; + private static final String TAG = "SliceFullAccessList"; + + private static final String TAG_LIST = "slice-access-list"; + private static final String TAG_PKG = "pkg"; + private static final String TAG_USER = "user"; + + private final String ATT_USER_ID = "user"; + private final String ATT_VERSION = "version"; + + private final SparseArray<ArraySet<String>> mFullAccessPkgs = new SparseArray<>(); + private final Context mContext; + + public SliceFullAccessList(Context context) { + mContext = context; + } + + public boolean hasFullAccess(String pkg, int userId) { + ArraySet<String> pkgs = mFullAccessPkgs.get(userId, null); + return pkgs != null && pkgs.contains(pkg); + } + + public void grantFullAccess(String pkg, int userId) { + ArraySet<String> pkgs = mFullAccessPkgs.get(userId, null); + if (pkgs == null) { + pkgs = new ArraySet<>(); + mFullAccessPkgs.put(userId, pkgs); + } + pkgs.add(pkg); + } + + public void writeXml(XmlSerializer out) throws IOException { + out.startTag(null, TAG_LIST); + out.attribute(null, ATT_VERSION, String.valueOf(DB_VERSION)); + + final int N = mFullAccessPkgs.size(); + for (int i = 0 ; i < N; i++) { + final int userId = mFullAccessPkgs.keyAt(i); + final ArraySet<String> pkgs = mFullAccessPkgs.valueAt(i); + out.startTag(null, TAG_USER); + out.attribute(null, ATT_USER_ID, Integer.toString(userId)); + if (pkgs != null) { + final int M = pkgs.size(); + for (int j = 0; j < M; j++) { + out.startTag(null, TAG_PKG); + out.text(pkgs.valueAt(j)); + out.endTag(null, TAG_PKG); + + } + } + out.endTag(null, TAG_USER); + } + out.endTag(null, TAG_LIST); + } + + public void readXml(XmlPullParser parser) throws XmlPullParserException, IOException { + // upgrade xml + int xmlVersion = XmlUtils.readIntAttribute(parser, ATT_VERSION, 0); + final List<UserInfo> activeUsers = UserManager.get(mContext).getUsers(true); + for (UserInfo userInfo : activeUsers) { + upgradeXml(xmlVersion, userInfo.getUserHandle().getIdentifier()); + } + + mFullAccessPkgs.clear(); + // read grants + int type; + while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) { + String tag = parser.getName(); + if (type == XmlPullParser.END_TAG + && TAG_LIST.equals(tag)) { + break; + } + if (type == XmlPullParser.START_TAG) { + if (TAG_USER.equals(tag)) { + final int userId = XmlUtils.readIntAttribute(parser, ATT_USER_ID, 0); + ArraySet<String> pkgs = new ArraySet<>(); + while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) { + String userTag = parser.getName(); + if (type == XmlPullParser.END_TAG + && TAG_USER.equals(userTag)) { + break; + } + if (type == XmlPullParser.START_TAG) { + if (TAG_PKG.equals(userTag)) { + final String pkg = parser.nextText(); + pkgs.add(pkg); + } + } + } + mFullAccessPkgs.put(userId, pkgs); + } + } + } + } + + protected void upgradeXml(final int xmlVersion, final int userId) {} +} diff --git a/services/core/java/com/android/server/slice/SliceManagerService.java b/services/core/java/com/android/server/slice/SliceManagerService.java index c1915801b61b..5db0fc0d9e00 100644 --- a/services/core/java/com/android/server/slice/SliceManagerService.java +++ b/services/core/java/com/android/server/slice/SliceManagerService.java @@ -19,6 +19,8 @@ package com.android.server.slice; import static android.content.ContentProvider.getUriWithoutUserId; import static android.content.ContentProvider.getUserIdFromUri; import static android.content.ContentProvider.maybeAddUserId; +import static android.content.pm.PackageManager.PERMISSION_DENIED; +import static android.content.pm.PackageManager.PERMISSION_GRANTED; import android.Manifest.permission; import android.app.ActivityManager; @@ -32,20 +34,24 @@ import android.app.slice.SliceSpec; import android.content.ComponentName; import android.content.Context; import android.content.Intent; -import android.content.pm.PackageManager; import android.content.pm.PackageManagerInternal; import android.content.pm.ResolveInfo; import android.database.ContentObserver; import android.net.Uri; import android.os.Binder; +import android.os.Environment; import android.os.Handler; import android.os.IBinder; import android.os.Looper; import android.os.Process; import android.os.RemoteException; +import android.os.UserHandle; import android.util.ArrayMap; import android.util.ArraySet; +import android.util.AtomicFile; import android.util.Log; +import android.util.Slog; +import android.util.Xml.Encoding; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; @@ -55,6 +61,16 @@ import com.android.server.LocalServices; import com.android.server.ServiceThread; import com.android.server.SystemService; +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; +import org.xmlpull.v1.XmlPullParserFactory; +import org.xmlpull.v1.XmlSerializer; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; import java.util.ArrayList; import java.util.List; import java.util.Objects; @@ -75,6 +91,8 @@ public class SliceManagerService extends ISliceManager.Stub { private final ArraySet<SliceGrant> mUserGrants = new ArraySet<>(); private final Handler mHandler; private final ContentObserver mObserver; + private final AtomicFile mSliceAccessFile; + private final SliceFullAccessList mAccessList; public SliceManagerService(Context context) { this(context, createHandler().getLooper()); @@ -99,6 +117,21 @@ public class SliceManagerService extends ISliceManager.Stub { } } }; + final File systemDir = new File(Environment.getDataDirectory(), "system"); + mSliceAccessFile = new AtomicFile(new File(systemDir, "slice_access.xml")); + mAccessList = new SliceFullAccessList(mContext); + + synchronized (mSliceAccessFile) { + if (!mSliceAccessFile.exists()) return; + try { + InputStream input = mSliceAccessFile.openRead(); + XmlPullParser parser = XmlPullParserFactory.newInstance().newPullParser(); + parser.setInput(input, Encoding.UTF_8.name()); + mAccessList.readXml(parser); + } catch (IOException | XmlPullParserException e) { + Slog.d(TAG, "Can't read slice access file", e); + } + } } /// ----- Lifecycle stuff ----- @@ -120,8 +153,10 @@ public class SliceManagerService extends ISliceManager.Stub { throws RemoteException { verifyCaller(pkg); uri = maybeAddUserId(uri, Binder.getCallingUserHandle().getIdentifier()); - enforceAccess(pkg, uri); - getOrCreatePinnedSlice(uri).addSliceListener(listener, pkg, specs); + enforceCrossUser(pkg, uri); + getOrCreatePinnedSlice(uri).addSliceListener(listener, pkg, specs, + checkAccess(pkg, uri, Binder.getCallingUid(), Binder.getCallingUid()) + == PERMISSION_GRANTED); } @Override @@ -129,7 +164,6 @@ public class SliceManagerService extends ISliceManager.Stub { throws RemoteException { verifyCaller(pkg); uri = maybeAddUserId(uri, Binder.getCallingUserHandle().getIdentifier()); - enforceAccess(pkg, uri); if (getPinnedSlice(uri).removeSliceListener(listener)) { removePinnedSlice(uri); } @@ -169,14 +203,14 @@ public class SliceManagerService extends ISliceManager.Stub { @Override public int checkSlicePermission(Uri uri, String pkg, int pid, int uid) throws RemoteException { if (mContext.checkUriPermission(uri, pid, uid, Intent.FLAG_GRANT_WRITE_URI_PERMISSION) - == PackageManager.PERMISSION_GRANTED) { + == PERMISSION_GRANTED) { return SliceManager.PERMISSION_GRANTED; } - if (hasFullSliceAccess(pkg, uid)) { + if (hasFullSliceAccess(pkg, UserHandle.getUserId(uid))) { return SliceManager.PERMISSION_GRANTED; } synchronized (mLock) { - if (mUserGrants.contains(new SliceGrant(uri, pkg))) { + if (mUserGrants.contains(new SliceGrant(uri, pkg, UserHandle.getUserId(uid)))) { return SliceManager.PERMISSION_USER_GRANTED; } } @@ -189,16 +223,23 @@ public class SliceManagerService extends ISliceManager.Stub { getContext().enforceCallingOrSelfPermission(permission.MANAGE_SLICE_PERMISSIONS, "Slice granting requires MANAGE_SLICE_PERMISSIONS"); if (allSlices) { - // TODO: Manage full access grants. + mAccessList.grantFullAccess(pkg, Binder.getCallingUserHandle().getIdentifier()); + mHandler.post(mSaveAccessList); } else { synchronized (mLock) { - mUserGrants.add(new SliceGrant(uri, pkg)); + mUserGrants.add(new SliceGrant(uri, pkg, + Binder.getCallingUserHandle().getIdentifier())); } - long ident = Binder.clearCallingIdentity(); - try { - mContext.getContentResolver().notifyChange(uri, null); - } finally { - Binder.restoreCallingIdentity(ident); + } + long ident = Binder.clearCallingIdentity(); + try { + mContext.getContentResolver().notifyChange(uri, null); + } finally { + Binder.restoreCallingIdentity(ident); + } + synchronized (mLock) { + for (PinnedSliceState p : mPinnedSlicesByUri.values()) { + p.recheckPackage(pkg); } } } @@ -249,17 +290,13 @@ public class SliceManagerService extends ISliceManager.Stub { return mHandler; } - private void enforceAccess(String pkg, Uri uri) throws RemoteException { - int user = Binder.getCallingUserHandle().getIdentifier(); + protected int checkAccess(String pkg, Uri uri, int uid, int pid) { + int user = UserHandle.getUserId(uid); // Check for default launcher/assistant. - if (!hasFullSliceAccess(pkg, Binder.getCallingUid())) { - try { - // Also allow things with uri access. - getContext().enforceUriPermission(uri, Binder.getCallingPid(), - Binder.getCallingUid(), - Intent.FLAG_GRANT_WRITE_URI_PERMISSION, - "Slice binding requires permission to the Uri"); - } catch (SecurityException e) { + if (!hasFullSliceAccess(pkg, user)) { + // Also allow things with uri access. + if (getContext().checkUriPermission(uri, pid, uid, + Intent.FLAG_GRANT_WRITE_URI_PERMISSION) != PERMISSION_GRANTED) { // Last fallback (if the calling app owns the authority, then it can have access). long ident = Binder.clearCallingIdentity(); try { @@ -268,17 +305,21 @@ public class SliceManagerService extends ISliceManager.Stub { ContentProviderHolder holder = null; String providerName = getUriWithoutUserId(uri).getAuthority(); try { - holder = activityManager.getContentProviderExternal( - providerName, getUserIdFromUri(uri, user), token); - if (holder == null || holder.info == null - || !Objects.equals(holder.info.packageName, pkg)) { - // No more fallbacks, no access. - throw e; - } - } finally { - if (holder != null && holder.provider != null) { - activityManager.removeContentProviderExternal(providerName, token); + try { + holder = activityManager.getContentProviderExternal( + providerName, getUserIdFromUri(uri, user), token); + if (holder == null || holder.info == null + || !Objects.equals(holder.info.packageName, pkg)) { + return PERMISSION_DENIED; + } + } finally { + if (holder != null && holder.provider != null) { + activityManager.removeContentProviderExternal(providerName, token); + } } + } catch (RemoteException e) { + // Can't happen. + e.rethrowAsRuntimeException(); } } finally { // I know, the double finally seems ugly, but seems safest for the identity. @@ -286,23 +327,31 @@ public class SliceManagerService extends ISliceManager.Stub { } } } - // Lastly check for any multi-userness. Any return statements above here will break this - // important check. + return PERMISSION_GRANTED; + } + + private void enforceCrossUser(String pkg, Uri uri) { + int user = Binder.getCallingUserHandle().getIdentifier(); if (getUserIdFromUri(uri, user) != user) { getContext().enforceCallingOrSelfPermission(permission.INTERACT_ACROSS_USERS_FULL, "Slice interaction across users requires INTERACT_ACROSS_USERS_FULL"); } } + private void enforceAccess(String pkg, Uri uri) throws RemoteException { + if (checkAccess(pkg, uri, Binder.getCallingUid(), Binder.getCallingPid()) + != PERMISSION_GRANTED) { + throw new SecurityException("Access to slice " + uri + " is required"); + } + enforceCrossUser(pkg, uri); + } + private void enforceFullAccess(String pkg, String name, Uri uri) { int user = Binder.getCallingUserHandle().getIdentifier(); if (!hasFullSliceAccess(pkg, user)) { throw new SecurityException(String.format("Call %s requires full slice access", name)); } - if (getUserIdFromUri(uri, user) != user) { - getContext().enforceCallingOrSelfPermission(permission.INTERACT_ACROSS_USERS_FULL, - "Slice interaction across users requires INTERACT_ACROSS_USERS_FULL"); - } + enforceCrossUser(pkg, uri); } private void verifyCaller(String pkg) { @@ -395,8 +444,7 @@ public class SliceManagerService extends ISliceManager.Stub { } private boolean isGrantedFullAccess(String pkg, int userId) { - // TODO: This will be user granted access, if we allow this through a prompt. - return false; + return mAccessList.hasFullAccess(pkg, userId); } private static ServiceThread createHandler() { @@ -406,6 +454,32 @@ public class SliceManagerService extends ISliceManager.Stub { return handlerThread; } + private final Runnable mSaveAccessList = new Runnable() { + @Override + public void run() { + synchronized (mSliceAccessFile) { + final FileOutputStream stream; + try { + stream = mSliceAccessFile.startWrite(); + } catch (IOException e) { + Slog.w(TAG, "Failed to save access file", e); + return; + } + + try { + XmlSerializer out = XmlPullParserFactory.newInstance().newSerializer(); + out.setOutput(stream, Encoding.UTF_8.name()); + mAccessList.writeXml(out); + out.flush(); + mSliceAccessFile.finishWrite(stream); + } catch (IOException | XmlPullParserException e) { + Slog.w(TAG, "Failed to save access file, restoring backup", e); + mSliceAccessFile.failWrite(stream); + } + } + } + }; + public static class Lifecycle extends SystemService { private SliceManagerService mService; @@ -440,10 +514,12 @@ public class SliceManagerService extends ISliceManager.Stub { private class SliceGrant { private final Uri mUri; private final String mPkg; + private final int mUserId; - public SliceGrant(Uri uri, String pkg) { + public SliceGrant(Uri uri, String pkg, int userId) { mUri = uri; mPkg = pkg; + mUserId = userId; } @Override @@ -455,7 +531,8 @@ public class SliceManagerService extends ISliceManager.Stub { public boolean equals(Object obj) { if (!(obj instanceof SliceGrant)) return false; SliceGrant other = (SliceGrant) obj; - return Objects.equals(other.mUri, mUri) && Objects.equals(other.mPkg, mPkg); + return Objects.equals(other.mUri, mUri) && Objects.equals(other.mPkg, mPkg) + && (other.mUserId == mUserId); } } } diff --git a/services/core/java/com/android/server/textclassifier/TextClassificationManagerService.java b/services/core/java/com/android/server/textclassifier/TextClassificationManagerService.java new file mode 100644 index 000000000000..853c7eb51b84 --- /dev/null +++ b/services/core/java/com/android/server/textclassifier/TextClassificationManagerService.java @@ -0,0 +1,395 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.textclassifier; + +import android.annotation.NonNull; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.ServiceConnection; +import android.os.Binder; +import android.os.Handler; +import android.os.IBinder; +import android.os.RemoteException; +import android.util.Slog; +import android.service.textclassifier.ITextClassifierService; +import android.service.textclassifier.ITextClassificationCallback; +import android.service.textclassifier.ITextLinksCallback; +import android.service.textclassifier.ITextSelectionCallback; +import android.service.textclassifier.TextClassifierService; +import android.view.textclassifier.TextClassification; +import android.view.textclassifier.TextClassifier; +import android.view.textclassifier.TextLinks; +import android.view.textclassifier.TextSelection; + +import com.android.internal.annotations.GuardedBy; +import com.android.internal.util.Preconditions; +import com.android.server.SystemService; + +import java.util.LinkedList; +import java.util.Queue; +import java.util.concurrent.Callable; +import java.util.concurrent.TimeUnit; + +/** + * A manager for TextClassifier services. + * Apps bind to the TextClassificationManagerService for text classification. This service + * reroutes calls to it to a {@link TextClassifierService} that it manages. + */ +public final class TextClassificationManagerService extends ITextClassifierService.Stub { + + private static final String LOG_TAG = "TextClassificationManagerService"; + + // How long after the last interaction with the service we would unbind + private static final long TIMEOUT_IDLE_BIND_MILLIS = TimeUnit.MINUTES.toMillis(1); + + public static final class Lifecycle extends SystemService { + + private final TextClassificationManagerService mManagerService; + + public Lifecycle(Context context) { + super(context); + mManagerService = new TextClassificationManagerService(context); + } + + @Override + public void onStart() { + try { + publishBinderService(Context.TEXT_CLASSIFICATION_SERVICE, mManagerService); + } catch (Throwable t) { + // Starting this service is not critical to the running of this device and should + // therefore not crash the device. If it fails, log the error and continue. + Slog.e(LOG_TAG, "Could not start the TextClassificationManagerService.", t); + } + } + } + + private final Context mContext; + private final Handler mHandler; + private final Intent mServiceIntent; + private final ServiceConnection mConnection; + private final Runnable mUnbind; + private final Object mLock; + @GuardedBy("mLock") + private final Queue<PendingRequest> mPendingRequests; + + @GuardedBy("mLock") + private ITextClassifierService mService; + @GuardedBy("mLock") + private boolean mBinding; + + private TextClassificationManagerService(Context context) { + mContext = Preconditions.checkNotNull(context); + mHandler = new Handler(); + mServiceIntent = new Intent(TextClassifierService.SERVICE_INTERFACE) + .setComponent(TextClassifierService.getServiceComponentName(mContext)); + mConnection = new ServiceConnection() { + @Override + public void onServiceConnected(ComponentName name, IBinder service) { + synchronized (mLock) { + mService = ITextClassifierService.Stub.asInterface(service); + setBindingLocked(false); + handlePendingRequestsLocked(); + } + } + + @Override + public void onServiceDisconnected(ComponentName name) { + cleanupService(); + } + + @Override + public void onBindingDied(ComponentName name) { + cleanupService(); + } + + @Override + public void onNullBinding(ComponentName name) { + cleanupService(); + } + + private void cleanupService() { + synchronized (mLock) { + mService = null; + setBindingLocked(false); + handlePendingRequestsLocked(); + } + } + }; + mPendingRequests = new LinkedList<>(); + mUnbind = this::unbind; + mLock = new Object(); + } + + @Override + public void onSuggestSelection( + CharSequence text, int selectionStartIndex, int selectionEndIndex, + TextSelection.Options options, ITextSelectionCallback callback) + throws RemoteException { + // TODO(b/72481438): All remote calls need to take userId. + validateInput(text, selectionStartIndex, selectionEndIndex, callback); + + if (!bind()) { + callback.onFailure(); + return; + } + + synchronized (mLock) { + if (isBoundLocked()) { + mService.onSuggestSelection( + text, selectionStartIndex, selectionEndIndex, options, callback); + scheduleUnbindLocked(); + } else { + final Callable<Void> request = () -> { + onSuggestSelection( + text, selectionStartIndex, selectionEndIndex, + options, callback); + return null; + }; + final Callable<Void> onServiceFailure = () -> { + callback.onFailure(); + return null; + }; + enqueueRequestLocked(request, onServiceFailure, callback.asBinder()); + } + } + } + + @Override + public void onClassifyText( + CharSequence text, int startIndex, int endIndex, + TextClassification.Options options, ITextClassificationCallback callback) + throws RemoteException { + validateInput(text, startIndex, endIndex, callback); + + if (!bind()) { + callback.onFailure(); + return; + } + + synchronized (mLock) { + if (isBoundLocked()) { + mService.onClassifyText(text, startIndex, endIndex, options, callback); + scheduleUnbindLocked(); + } else { + final Callable<Void> request = () -> { + onClassifyText(text, startIndex, endIndex, options, callback); + return null; + }; + final Callable<Void> onServiceFailure = () -> { + callback.onFailure(); + return null; + }; + enqueueRequestLocked(request, onServiceFailure, callback.asBinder()); + } + } + } + + @Override + public void onGenerateLinks( + CharSequence text, TextLinks.Options options, ITextLinksCallback callback) + throws RemoteException { + validateInput(text, callback); + + if (!bind()) { + callback.onFailure(); + return; + } + + synchronized (mLock) { + if (isBoundLocked()) { + mService.onGenerateLinks(text, options, callback); + scheduleUnbindLocked(); + } else { + final Callable<Void> request = () -> { + onGenerateLinks(text, options, callback); + return null; + }; + final Callable<Void> onServiceFailure = () -> { + callback.onFailure(); + return null; + }; + enqueueRequestLocked(request, onServiceFailure, callback.asBinder()); + } + } + } + + /** + * @return true if the service is bound or in the process of being bound. + * Returns false otherwise. + */ + private boolean bind() { + synchronized (mLock) { + if (isBoundLocked() || isBindingLocked()) { + return true; + } + + // TODO: Handle bind timeout. + final boolean willBind; + final long identity = Binder.clearCallingIdentity(); + try { + Slog.d(LOG_TAG, "Binding to " + mServiceIntent.getComponent()); + willBind = mContext.bindServiceAsUser( + mServiceIntent, mConnection, + Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE, + Binder.getCallingUserHandle()); + setBindingLocked(willBind); + } finally { + Binder.restoreCallingIdentity(identity); + } + return willBind; + } + } + + @GuardedBy("mLock") + private boolean isBoundLocked() { + return mService != null; + } + + @GuardedBy("mLock") + private boolean isBindingLocked() { + return mBinding; + } + + @GuardedBy("mLock") + private void setBindingLocked(boolean binding) { + mBinding = binding; + } + + private void unbind() { + synchronized (mLock) { + if (!isBoundLocked()) { + return; + } + + Slog.d(LOG_TAG, "Unbinding from " + mServiceIntent.getComponent()); + mContext.unbindService(mConnection); + + synchronized (mLock) { + mService = null; + } + } + } + + @GuardedBy("mLock") + private void scheduleUnbindLocked() { + mHandler.removeCallbacks(mUnbind); + mHandler.postDelayed(mUnbind, TIMEOUT_IDLE_BIND_MILLIS); + } + + @GuardedBy("mLock") + private void enqueueRequestLocked( + Callable<Void> request, Callable<Void> onServiceFailure, IBinder binder) { + mPendingRequests.add(new PendingRequest(request, onServiceFailure, binder)); + } + + @GuardedBy("mLock") + private void handlePendingRequestsLocked() { + // TODO(b/72481146): Implement PendingRequest similar to that in RemoteFillService. + final PendingRequest[] pendingRequests = + mPendingRequests.toArray(new PendingRequest[mPendingRequests.size()]); + for (PendingRequest pendingRequest : pendingRequests) { + if (isBoundLocked()) { + pendingRequest.executeLocked(); + } else { + pendingRequest.notifyServiceFailureLocked(); + } + } + } + + private final class PendingRequest implements IBinder.DeathRecipient { + + private final Callable<Void> mRequest; + private final Callable<Void> mOnServiceFailure; + private final IBinder mBinder; + + /** + * Initializes a new pending request. + * + * @param request action to perform when the service is bound + * @param onServiceFailure action to perform when the service dies or disconnects + * @param binder binder to the process that made this pending request + */ + PendingRequest( + @NonNull Callable<Void> request, @NonNull Callable<Void> onServiceFailure, + @NonNull IBinder binder) { + mRequest = Preconditions.checkNotNull(request); + mOnServiceFailure = Preconditions.checkNotNull(onServiceFailure); + mBinder = Preconditions.checkNotNull(binder); + try { + mBinder.linkToDeath(this, 0); + } catch (RemoteException e) { + e.printStackTrace(); + } + } + + @GuardedBy("mLock") + void executeLocked() { + removeLocked(); + try { + mRequest.call(); + } catch (Exception e) { + Slog.d(LOG_TAG, "Error handling pending request: " + e.getMessage()); + } + } + + @GuardedBy("mLock") + void notifyServiceFailureLocked() { + removeLocked(); + try { + mOnServiceFailure.call(); + } catch (Exception e) { + Slog.d(LOG_TAG, "Error notifying callback of service failure: " + + e.getMessage()); + } + } + + @Override + public void binderDied() { + synchronized (mLock) { + // No need to handle this pending request anymore. Remove. + removeLocked(); + } + } + + @GuardedBy("mLock") + private void removeLocked() { + mPendingRequests.remove(this); + mBinder.unlinkToDeath(this, 0); + } + } + + private static void validateInput( + CharSequence text, int startIndex, int endIndex, Object callback) + throws RemoteException { + try { + TextClassifier.Utils.validate(text, startIndex, endIndex, true /* allowInMainThread */); + Preconditions.checkNotNull(callback); + } catch (IllegalArgumentException | NullPointerException e) { + throw new RemoteException(e.getMessage()); + } + } + + private static void validateInput(CharSequence text, Object callback) throws RemoteException { + try { + TextClassifier.Utils.validate(text, true /* allowInMainThread */); + Preconditions.checkNotNull(callback); + } catch (IllegalArgumentException | NullPointerException e) { + throw new RemoteException(e.getMessage()); + } + } +} diff --git a/services/core/java/com/android/server/timezone/PackageStatusStorage.java b/services/core/java/com/android/server/timezone/PackageStatusStorage.java index 251a27763fd2..04f0871c9897 100644 --- a/services/core/java/com/android/server/timezone/PackageStatusStorage.java +++ b/services/core/java/com/android/server/timezone/PackageStatusStorage.java @@ -81,7 +81,7 @@ final class PackageStatusStorage { private final AtomicFile mPackageStatusFile; PackageStatusStorage(File storageDir) { - mPackageStatusFile = new AtomicFile(new File(storageDir, "package-status.xml")); + mPackageStatusFile = new AtomicFile(new File(storageDir, "package-status.xml"), "timezone-status"); } /** diff --git a/services/core/java/com/android/server/tv/PersistentDataStore.java b/services/core/java/com/android/server/tv/PersistentDataStore.java index 85a882960575..8f2194c3c8b6 100644 --- a/services/core/java/com/android/server/tv/PersistentDataStore.java +++ b/services/core/java/com/android/server/tv/PersistentDataStore.java @@ -91,7 +91,7 @@ final class PersistentDataStore { throw new IllegalStateException("User dir cannot be created: " + userDir); } } - mAtomicFile = new AtomicFile(new File(userDir, "tv-input-manager-state.xml")); + mAtomicFile = new AtomicFile(new File(userDir, "tv-input-manager-state.xml"), "tv-input-state"); } public boolean isParentalControlsEnabled() { diff --git a/services/core/java/com/android/server/updates/SmartSelectionInstallReceiver.java b/services/core/java/com/android/server/updates/SmartSelectionInstallReceiver.java index 1457366a17a3..eff9a9a5b503 100644 --- a/services/core/java/com/android/server/updates/SmartSelectionInstallReceiver.java +++ b/services/core/java/com/android/server/updates/SmartSelectionInstallReceiver.java @@ -21,8 +21,8 @@ public class SmartSelectionInstallReceiver extends ConfigUpdateInstallReceiver { public SmartSelectionInstallReceiver() { super( "/data/misc/textclassifier/", - "textclassifier.smartselection.model", - "metadata/smartselection", + "textclassifier.model", + "metadata/classification", "version"); } diff --git a/services/core/java/com/android/server/wm/AppTransition.java b/services/core/java/com/android/server/wm/AppTransition.java index fc7ad09d5182..2bdaa1a66b7e 100644 --- a/services/core/java/com/android/server/wm/AppTransition.java +++ b/services/core/java/com/android/server/wm/AppTransition.java @@ -38,6 +38,7 @@ import static android.view.WindowManager.TRANSIT_WALLPAPER_CLOSE; import static android.view.WindowManager.TRANSIT_WALLPAPER_INTRA_CLOSE; import static android.view.WindowManager.TRANSIT_WALLPAPER_INTRA_OPEN; import static android.view.WindowManager.TRANSIT_WALLPAPER_OPEN; + import static com.android.internal.R.styleable.WindowAnimation_activityCloseEnterAnimation; import static com.android.internal.R.styleable.WindowAnimation_activityCloseExitAnimation; import static com.android.internal.R.styleable.WindowAnimation_activityOpenEnterAnimation; @@ -1647,10 +1648,7 @@ public class AppTransition implements Dump { + " transit=" + appTransitionToString(transit) + " isEntrance=" + enter + " Callers=" + Debug.getCallers(3)); } - } else if (mNextAppTransitionType == NEXT_TRANSIT_TYPE_OPEN_CROSS_PROFILE_APPS - && (transit == TRANSIT_ACTIVITY_OPEN - || transit == TRANSIT_TASK_OPEN - || transit == TRANSIT_TASK_TO_FRONT)) { + } else if (mNextAppTransitionType == NEXT_TRANSIT_TYPE_OPEN_CROSS_PROFILE_APPS && enter) { a = loadAnimationRes("android", enter ? com.android.internal.R.anim.task_open_enter_cross_profile_apps diff --git a/services/core/java/com/android/server/wm/ConfigurationContainer.java b/services/core/java/com/android/server/wm/ConfigurationContainer.java index d340923b754e..2c2389b12577 100644 --- a/services/core/java/com/android/server/wm/ConfigurationContainer.java +++ b/services/core/java/com/android/server/wm/ConfigurationContainer.java @@ -219,6 +219,14 @@ public abstract class ConfigurationContainer<E extends ConfigurationContainer> { } /** + * Returns {@code true} if the {@link WindowConfiguration} in the override + * {@link Configuration} specifies bounds. + */ + public boolean hasOverrideBounds() { + return !getOverrideBounds().isEmpty(); + } + + /** * Sets the passed in {@link Rect} to the current bounds. * @see {@link #getOverrideBounds()}. */ diff --git a/services/core/java/com/android/server/wm/Dimmer.java b/services/core/java/com/android/server/wm/Dimmer.java index 9fe16ae85cfb..b435605b654f 100644 --- a/services/core/java/com/android/server/wm/Dimmer.java +++ b/services/core/java/com/android/server/wm/Dimmer.java @@ -17,34 +17,115 @@ package com.android.server.wm; import android.util.ArrayMap; -import android.util.Slog; import android.view.SurfaceControl; import android.graphics.Rect; +import com.android.internal.annotations.VisibleForTesting; + /** * Utility class for use by a WindowContainer implementation to add "DimLayer" support, that is * black layers of varying opacity at various Z-levels which create the effect of a Dim. */ class Dimmer { private static final String TAG = "WindowManager"; + private static final int DEFAULT_DIM_ANIM_DURATION = 200; + + private class DimAnimatable implements SurfaceAnimator.Animatable { + private final SurfaceControl mDimLayer; + + private DimAnimatable(SurfaceControl dimLayer) { + mDimLayer = dimLayer; + } + + @Override + public SurfaceControl.Transaction getPendingTransaction() { + return mHost.getPendingTransaction(); + } + + @Override + public void commitPendingTransaction() { + mHost.commitPendingTransaction(); + } + + @Override + public void onAnimationLeashCreated(SurfaceControl.Transaction t, SurfaceControl leash) { + } + + @Override + public void onAnimationLeashDestroyed(SurfaceControl.Transaction t) { + } + + @Override + public void destroyAfterPendingTransaction(SurfaceControl surface) { + mHost.destroyAfterPendingTransaction(surface); + } + + @Override + public SurfaceControl.Builder makeAnimationLeash() { + return mHost.makeAnimationLeash(); + } + + @Override + public SurfaceControl getAnimationLeashParent() { + return mHost.getSurfaceControl(); + } + + @Override + public SurfaceControl getSurfaceControl() { + return mDimLayer; + } + + @Override + public SurfaceControl getParentSurfaceControl() { + return mHost.getSurfaceControl(); + } + + @Override + public int getSurfaceWidth() { + // This will determine the size of the leash created. This should be the size of the + // host and not the dim layer since the dim layer may get bigger during animation. If + // that occurs, the leash size cannot change so we need to ensure the leash is big + // enough that the dim layer can grow. + // This works because the mHost will be a Task which has the display bounds. + return mHost.getSurfaceWidth(); + } + + @Override + public int getSurfaceHeight() { + // See getSurfaceWidth() above for explanation. + return mHost.getSurfaceHeight(); + } + } - private class DimState { - SurfaceControl mSurfaceControl; + @VisibleForTesting + class DimState { + /** + * The layer where property changes should be invoked on. + */ + SurfaceControl mDimLayer; boolean mDimming; + boolean isVisible; + SurfaceAnimator mSurfaceAnimator; /** - * Used for Dims not assosciated with a WindowContainer. See {@link Dimmer#dimAbove} for + * Used for Dims not associated with a WindowContainer. See {@link Dimmer#dimAbove} for * details on Dim lifecycle. */ boolean mDontReset; - DimState(SurfaceControl ctl) { - mSurfaceControl = ctl; + DimState(SurfaceControl dimLayer) { + mDimLayer = dimLayer; mDimming = true; + mSurfaceAnimator = new SurfaceAnimator(new DimAnimatable(dimLayer), () -> { + if (!mDimming) { + mDimLayer.destroy(); + } + }, mHost.mService.mAnimator::addAfterPrepareSurfacesRunnable, mHost.mService); } - }; + } - private ArrayMap<WindowContainer, DimState> mDimLayerUsers = new ArrayMap<>(); + @VisibleForTesting + ArrayMap<WindowContainer, DimState> mDimLayerUsers = new ArrayMap<>(); /** * The {@link WindowContainer} that our Dim's are bounded to. We may be dimming on behalf of the @@ -56,19 +137,18 @@ class Dimmer { mHost = host; } - SurfaceControl makeDimLayer() { - final SurfaceControl control = mHost.makeChildSurface(null) + private SurfaceControl makeDimLayer() { + return mHost.makeChildSurface(null) .setParent(mHost.getSurfaceControl()) .setColorLayer(true) .setName("Dim Layer for - " + mHost.getName()) .build(); - return control; } /** * Retreive the DimState for a given child of the host. */ - DimState getDimState(WindowContainer container) { + private DimState getDimState(WindowContainer container) { DimState state = mDimLayerUsers.get(container); if (state == null) { final SurfaceControl ctl = makeDimLayer(); @@ -88,14 +168,12 @@ class Dimmer { private void dim(SurfaceControl.Transaction t, WindowContainer container, int relativeLayer, float alpha) { final DimState d = getDimState(container); - t.show(d.mSurfaceControl); if (container != null) { - t.setRelativeLayer(d.mSurfaceControl, - container.getSurfaceControl(), relativeLayer); + t.setRelativeLayer(d.mDimLayer, container.getSurfaceControl(), relativeLayer); } else { - t.setLayer(d.mSurfaceControl, Integer.MAX_VALUE); + t.setLayer(d.mDimLayer, Integer.MAX_VALUE); } - t.setAlpha(d.mSurfaceControl, alpha); + t.setAlpha(d.mDimLayer, alpha); d.mDimming = true; } @@ -107,16 +185,18 @@ class Dimmer { */ void stopDim(SurfaceControl.Transaction t) { DimState d = getDimState(null); - t.hide(d.mSurfaceControl); + t.hide(d.mDimLayer); + d.isVisible = false; d.mDontReset = false; } + /** * Place a Dim above the entire host container. The caller is responsible for calling stopDim to * remove this effect. If the Dim can be assosciated with a particular child of the host * consider using the other variant of dimAbove which ties the Dim lifetime to the child * lifetime more explicitly. * - * @param t A transaction in which to apply the Dim. + * @param t A transaction in which to apply the Dim. * @param alpha The alpha at which to Dim. */ void dimAbove(SurfaceControl.Transaction t, float alpha) { @@ -128,9 +208,9 @@ class Dimmer { * for each call to {@link WindowContainer#prepareSurfaces} the Dim state will be reset * and the child should call dimAbove again to request the Dim to continue. * - * @param t A transaction in which to apply the Dim. + * @param t A transaction in which to apply the Dim. * @param container The container which to dim above. Should be a child of our host. - * @param alpha The alpha at which to Dim. + * @param alpha The alpha at which to Dim. */ void dimAbove(SurfaceControl.Transaction t, WindowContainer container, float alpha) { dim(t, container, 1, alpha); @@ -139,9 +219,9 @@ class Dimmer { /** * Like {@link #dimAbove} but places the dim below the given container. * - * @param t A transaction in which to apply the Dim. + * @param t A transaction in which to apply the Dim. * @param container The container which to dim below. Should be a child of our host. - * @param alpha The alpha at which to Dim. + * @param alpha The alpha at which to Dim. */ void dimBelow(SurfaceControl.Transaction t, WindowContainer container, float alpha) { @@ -159,7 +239,7 @@ class Dimmer { void resetDimStates() { for (int i = mDimLayerUsers.size() - 1; i >= 0; i--) { final DimState state = mDimLayerUsers.valueAt(i); - if (state.mDontReset == false) { + if (!state.mDontReset) { state.mDimming = false; } } @@ -169,7 +249,7 @@ class Dimmer { * Call after invoking {@link WindowContainer#prepareSurfaces} on children as * described in {@link #resetDimStates}. * - * @param t A transaction in which to update the dims. + * @param t A transaction in which to update the dims. * @param bounds The bounds at which to dim. * @return true if any Dims were updated. */ @@ -177,19 +257,80 @@ class Dimmer { boolean didSomething = false; for (int i = mDimLayerUsers.size() - 1; i >= 0; i--) { DimState state = mDimLayerUsers.valueAt(i); + WindowContainer container = mDimLayerUsers.keyAt(i); + // TODO: We want to animate the addition and removal of Dim's instead of immediately // acting. When we do this we need to take care to account for the "Replacing Windows" // case (and seamless dim transfer). - if (state.mDimming == false) { + if (!state.mDimming) { mDimLayerUsers.removeAt(i); - state.mSurfaceControl.destroy(); + startDimExit(container, state.mSurfaceAnimator, t); } else { didSomething = true; // TODO: Once we use geometry from hierarchy this falls away. - t.setSize(state.mSurfaceControl, bounds.width(), bounds.height()); - t.setPosition(state.mSurfaceControl, bounds.left, bounds.top); + t.setSize(state.mDimLayer, bounds.width(), bounds.height()); + t.setPosition(state.mDimLayer, bounds.left, bounds.top); + if (!state.isVisible) { + state.isVisible = true; + t.show(state.mDimLayer); + startDimEnter(container, state.mSurfaceAnimator, t); + } } } return didSomething; } + + private void startDimEnter(WindowContainer container, SurfaceAnimator animator, + SurfaceControl.Transaction t) { + startAnim(container, animator, t, 0 /* startAlpha */, 1 /* endAlpha */); + } + + private void startDimExit(WindowContainer container, SurfaceAnimator animator, + SurfaceControl.Transaction t) { + startAnim(container, animator, t, 1 /* startAlpha */, 0 /* endAlpha */); + } + + private void startAnim(WindowContainer container, SurfaceAnimator animator, + SurfaceControl.Transaction t, float startAlpha, float endAlpha) { + animator.startAnimation(t, new LocalAnimationAdapter( + new AlphaAnimationSpec(startAlpha, endAlpha, getDimDuration(container)), + mHost.mService.mSurfaceAnimationRunner), false /* hidden */); + } + + private long getDimDuration(WindowContainer container) { + // If there's no container, then there isn't an animation occurring while dimming. Set the + // duration to 0 so it immediately dims to the set alpha. + if (container == null) { + return 0; + } + + // Otherwise use the same duration as the animation on the WindowContainer + AnimationAdapter animationAdapter = container.mSurfaceAnimator.getAnimation(); + return animationAdapter == null ? DEFAULT_DIM_ANIM_DURATION + : animationAdapter.getDurationHint(); + } + + private static class AlphaAnimationSpec implements LocalAnimationAdapter.AnimationSpec { + private final long mDuration; + private final float mFromAlpha; + private final float mToAlpha; + + AlphaAnimationSpec(float fromAlpha, float toAlpha, long duration) { + mFromAlpha = fromAlpha; + mToAlpha = toAlpha; + mDuration = duration; + } + + @Override + public long getDuration() { + return mDuration; + } + + @Override + public void apply(SurfaceControl.Transaction t, SurfaceControl sc, long currentPlayTime) { + float alpha = ((float) currentPlayTime / getDuration()) * (mToAlpha - mFromAlpha) + + mFromAlpha; + t.setAlpha(sc, alpha); + } + } } diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index 3f49f0cd5c15..7674b5e9ed2c 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -3599,6 +3599,8 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo } private final class AboveAppWindowContainers extends NonAppWindowContainers { + private final Dimmer mDimmer = new Dimmer(this); + private final Rect mTmpDimBoundsRect = new Rect(); AboveAppWindowContainers(String name, WindowManagerService service) { super(name, service); } @@ -3630,6 +3632,22 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo imeContainer.assignRelativeLayer(t, getSurfaceControl(), Integer.MAX_VALUE); } } + + @Override + Dimmer getDimmer() { + return mDimmer; + } + + @Override + void prepareSurfaces() { + mDimmer.resetDimStates(); + super.prepareSurfaces(); + getBounds(mTmpDimBoundsRect); + + if (mDimmer.updateDims(getPendingTransaction(), mTmpDimBoundsRect)) { + scheduleAnimation(); + } + } } /** diff --git a/services/core/java/com/android/server/wm/DisplaySettings.java b/services/core/java/com/android/server/wm/DisplaySettings.java index 7f7968610151..97b64dc2b6b1 100644 --- a/services/core/java/com/android/server/wm/DisplaySettings.java +++ b/services/core/java/com/android/server/wm/DisplaySettings.java @@ -64,7 +64,7 @@ public class DisplaySettings { public DisplaySettings() { File dataDir = Environment.getDataDirectory(); File systemDir = new File(dataDir, "system"); - mFile = new AtomicFile(new File(systemDir, "display_settings.xml")); + mFile = new AtomicFile(new File(systemDir, "display_settings.xml"), "wm-displays"); } public void getOverscanLocked(String name, String uniqueId, Rect outRect) { diff --git a/services/core/java/com/android/server/wm/DockedStackDividerController.java b/services/core/java/com/android/server/wm/DockedStackDividerController.java index 7ae1f24b0cd6..80798bf41b84 100644 --- a/services/core/java/com/android/server/wm/DockedStackDividerController.java +++ b/services/core/java/com/android/server/wm/DockedStackDividerController.java @@ -568,6 +568,14 @@ public class DockedStackDividerController { : null; final TaskStack dockedStack = mDisplayContent.getSplitScreenPrimaryStack(); boolean visibleAndValid = visible && stack != null && dockedStack != null; + + // Ensure an old dim that was shown for the docked stack divider is removed so we don't end + // up with dim layers that can no longer be removed. + if (mDimmedStack != null && mDimmedStack != stack) { + mDimmedStack.stopDimming(); + mDimmedStack = null; + } + if (visibleAndValid) { mDimmedStack = stack; stack.dim(alpha); diff --git a/services/core/java/com/android/server/wm/RecentsAnimationController.java b/services/core/java/com/android/server/wm/RecentsAnimationController.java index c7d4b8ed0f16..fe5b65ccbf6d 100644 --- a/services/core/java/com/android/server/wm/RecentsAnimationController.java +++ b/services/core/java/com/android/server/wm/RecentsAnimationController.java @@ -265,7 +265,6 @@ public class RecentsAnimationController { mPendingAnimations.clear(); mService.mInputMonitor.updateInputWindowsLw(true /*force*/); - mService.scheduleAnimationLocked(); mService.destroyInputConsumer(INPUT_CONSUMER_RECENTS_ANIMATION); } diff --git a/services/core/java/com/android/server/wm/RemoteAnimationController.java b/services/core/java/com/android/server/wm/RemoteAnimationController.java index 7d4eafb07fe9..8269a3b783c4 100644 --- a/services/core/java/com/android/server/wm/RemoteAnimationController.java +++ b/services/core/java/com/android/server/wm/RemoteAnimationController.java @@ -88,6 +88,10 @@ class RemoteAnimationController { * Called when the transition is ready to be started, and all leashes have been set up. */ void goodToGo() { + if (mPendingAnimations.isEmpty()) { + onAnimationFinished(); + return; + } mHandler.postDelayed(mTimeoutRunnable, TIMEOUT_MS); try { mRemoteAnimationAdapter.getRunner().onAnimationStart(createAnimations(), diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index 0628436a4204..7d970d9a06b5 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -462,8 +462,8 @@ class Task extends WindowContainer<AppWindowToken> { } else { mStack.getBounds(mTmpRect); mTmpRect.intersect(getBounds()); + out.set(mTmpRect); } - out.set(mTmpRect); } else { out.set(getBounds()); } @@ -640,6 +640,7 @@ class Task extends WindowContainer<AppWindowToken> { mPreserveNonFloatingState = false; } + @Override Dimmer getDimmer() { return mDimmer; } diff --git a/services/core/java/com/android/server/wm/TaskStack.java b/services/core/java/com/android/server/wm/TaskStack.java index bc0f9ad71a61..ae5341bd8e45 100644 --- a/services/core/java/com/android/server/wm/TaskStack.java +++ b/services/core/java/com/android/server/wm/TaskStack.java @@ -736,15 +736,11 @@ public class TaskStack extends WindowContainer<Task> implements } private void updateSurfaceBounds() { - updateSurfaceBounds(getPendingTransaction()); + updateSurfaceSize(getPendingTransaction()); + updateSurfacePosition(); scheduleAnimation(); } - void updateSurfaceBounds(SurfaceControl.Transaction transaction) { - updateSurfaceSize(transaction); - updateSurfacePosition(transaction); - } - private void updateSurfaceSize(SurfaceControl.Transaction transaction) { if (mSurfaceControl == null) { return; @@ -1722,6 +1718,7 @@ public class TaskStack extends WindowContainer<Task> implements || activityType == ACTIVITY_TYPE_ASSISTANT; } + @Override Dimmer getDimmer() { return mDimmer; } diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java index 1f9255a2b20f..6bd7f22a4a4c 100644 --- a/services/core/java/com/android/server/wm/WindowContainer.java +++ b/services/core/java/com/android/server/wm/WindowContainer.java @@ -131,7 +131,7 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< @Override public void onConfigurationChanged(Configuration newParentConfig) { super.onConfigurationChanged(newParentConfig); - updateSurfacePosition(getPendingTransaction()); + updateSurfacePosition(); scheduleAnimation(); } @@ -467,10 +467,22 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< void onResize() { for (int i = mChildren.size() - 1; i >= 0; --i) { final WindowContainer wc = mChildren.get(i); - wc.onResize(); + wc.onParentResize(); } } + void onParentResize() { + // In the case this container has specified its own bounds, a parent resize will not + // affect its bounds. Any relevant changes will be propagated through changes to the + // Configuration override. + if (hasOverrideBounds()) { + return; + } + + // Default implementation is to treat as resize on self. + onResize(); + } + void onMovedByResize() { for (int i = mChildren.size() - 1; i >= 0; --i) { final WindowContainer wc = mChildren.get(i); @@ -1192,7 +1204,7 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< } } - void updateSurfacePosition(SurfaceControl.Transaction transaction) { + void updateSurfacePosition() { if (mSurfaceControl == null) { return; } @@ -1202,12 +1214,8 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< return; } - transaction.setPosition(mSurfaceControl, mTmpPos.x, mTmpPos.y); + getPendingTransaction().setPosition(mSurfaceControl, mTmpPos.x, mTmpPos.y); mLastSurfacePosition.set(mTmpPos.x, mTmpPos.y); - - for (int i = mChildren.size() - 1; i >= 0; i--) { - mChildren.get(i).updateSurfacePosition(transaction); - } } void getRelativePosition(Point outPos) { @@ -1219,4 +1227,11 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< outPos.offset(-parentBounds.left, -parentBounds.top); } } + + Dimmer getDimmer() { + if (mParent == null) { + return null; + } + return mParent.getDimmer(); + } } diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index 4fb239085e5c..066e4e6a8c67 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -907,9 +907,16 @@ public class WindowManagerService extends IWindowManager.Stub public static WindowManagerService main(final Context context, final InputManagerService im, final boolean haveInputMethods, final boolean showBootMsgs, final boolean onlyCore, WindowManagerPolicy policy) { + return main(context, im, haveInputMethods, showBootMsgs, onlyCore, policy, + new SurfaceAnimationRunner()); + } + + public static WindowManagerService main(final Context context, final InputManagerService im, + final boolean haveInputMethods, final boolean showBootMsgs, final boolean onlyCore, + WindowManagerPolicy policy, SurfaceAnimationRunner surfaceAnimationRunner) { DisplayThread.getHandler().runWithScissors(() -> sInstance = new WindowManagerService(context, im, haveInputMethods, showBootMsgs, - onlyCore, policy), 0); + onlyCore, policy, surfaceAnimationRunner), 0); return sInstance; } @@ -932,7 +939,7 @@ public class WindowManagerService extends IWindowManager.Stub private WindowManagerService(Context context, InputManagerService inputManager, boolean haveInputMethods, boolean showBootMsgs, boolean onlyCore, - WindowManagerPolicy policy) { + WindowManagerPolicy policy, SurfaceAnimationRunner surfaceAnimationRunner) { installLock(this, INDEX_WINDOW); mContext = context; mHaveInputMethods = haveInputMethods; @@ -1059,7 +1066,7 @@ public class WindowManagerService extends IWindowManager.Stub PowerManager.SCREEN_BRIGHT_WAKE_LOCK | PowerManager.ON_AFTER_RELEASE, TAG_WM); mHoldingScreenWakeLock.setReferenceCounted(false); - mSurfaceAnimationRunner = new SurfaceAnimationRunner(); + mSurfaceAnimationRunner = surfaceAnimationRunner; mAllowTheaterModeWakeFromLayout = context.getResources().getBoolean( com.android.internal.R.bool.config_allowTheaterModeWakeFromWindowLayout); diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index 477dd2bb9633..53a8d82f551e 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -2138,18 +2138,6 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP mInputWindowHandle.inputChannel = null; } - private Dimmer getDimmer() { - Task task = getTask(); - if (task != null) { - return task.getDimmer(); - } - TaskStack taskStack = getStack(); - if (taskStack != null) { - return taskStack.getDimmer(); - } - return null; - } - /** Returns true if the replacement window was removed. */ boolean removeReplacedWindowIfNeeded(WindowState replacement) { if (mWillReplaceWindow && mReplacementWindow == replacement && replacement.hasDrawnLw()) { @@ -4516,11 +4504,11 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP private void applyDims(Dimmer dimmer) { if (!mAnimatingExit && mAppDied) { mIsDimming = true; - getDimmer().dimAbove(getPendingTransaction(), this, DEFAULT_DIM_AMOUNT_DEAD_WINDOW); + dimmer.dimAbove(getPendingTransaction(), this, DEFAULT_DIM_AMOUNT_DEAD_WINDOW); } else if ((mAttrs.flags & FLAG_DIM_BEHIND) != 0 - && !mAnimatingExit && isVisible()) { + && !mAnimatingExit && isVisible() && !mWinAnimator.mLastHidden) { mIsDimming = true; - getDimmer().dimBelow(getPendingTransaction(), this, mAttrs.dimAmount); + dimmer.dimBelow(getPendingTransaction(), this, mAttrs.dimAmount); } } @@ -4531,8 +4519,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP if (dimmer != null) { applyDims(dimmer); } - - updateSurfacePosition(mPendingTransaction); + updateSurfacePosition(); mWinAnimator.prepareSurfaceLocked(true); super.prepareSurfaces(); @@ -4554,7 +4541,11 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP } @Override - void updateSurfacePosition(Transaction t) { + void updateSurfacePosition() { + updateSurfacePosition(getPendingTransaction()); + } + + private void updateSurfacePosition(Transaction t) { if (mSurfaceControl == null) { return; } diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java b/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java index 552942614db7..9fcf3eedd983 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java @@ -19,12 +19,10 @@ import android.annotation.UserIdInt; import android.app.admin.IDevicePolicyManager; import android.content.ComponentName; import android.os.PersistableBundle; -import android.os.UserHandle; import android.security.keymaster.KeymasterCertificateChain; import android.security.keystore.ParcelableKeyGenParameterSpec; import android.telephony.data.ApnSetting; -import com.android.internal.R; import com.android.server.SystemService; import java.util.ArrayList; @@ -107,11 +105,6 @@ abstract class BaseIDevicePolicyManager extends IDevicePolicyManager.Stub { } @Override - public boolean startUserInBackground(ComponentName who, UserHandle userHandle) { - return false; - } - - @Override public void setStartUserSessionMessage( ComponentName admin, CharSequence startUserSessionMessage) {} @@ -178,4 +171,10 @@ abstract class BaseIDevicePolicyManager extends IDevicePolicyManager.Stub { public void clearSystemUpdatePolicyFreezePeriodRecord() { } + + @Override + public boolean isMeteredDataDisabledForUser(ComponentName admin, + String packageName, int userId) { + return false; + } } diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index f238302e7e21..4c57f7f050d0 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -19,7 +19,6 @@ package com.android.server.devicepolicy; import static android.Manifest.permission.BIND_DEVICE_ADMIN; import static android.Manifest.permission.MANAGE_CA_CERTIFICATES; import static android.app.ActivityManager.LOCK_TASK_MODE_NONE; -import static android.app.ActivityManager.USER_OP_SUCCESS; import static android.app.admin.DeviceAdminReceiver.EXTRA_TRANSFER_OWNERSHIP_ADMIN_EXTRAS_BUNDLE; import static android.app.admin.DevicePolicyManager.ACTION_PROVISION_MANAGED_USER; import static android.app.admin.DevicePolicyManager.CODE_ACCOUNTS_NOT_EMPTY; @@ -68,9 +67,6 @@ import static com.android.internal.logging.nano.MetricsProto.MetricsEvent import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker .STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW; -import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.PROVISIONING_ENTRY_POINT_ADB; -import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW; - import static com.android.server.devicepolicy.TransferOwnershipMetadataManager.ADMIN_TYPE_DEVICE_OWNER; import static com.android.server.devicepolicy.TransferOwnershipMetadataManager.ADMIN_TYPE_PROFILE_OWNER; @@ -249,7 +245,6 @@ import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Objects; -import java.util.Map.Entry; import java.util.Set; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; @@ -744,7 +739,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { if (deviceOwner != null) { Bundle extras = new Bundle(); extras.putParcelable(Intent.EXTRA_USER, UserHandle.of(userHandle)); - sendAdminCommandLocked(deviceOwner, action, extras, null); + sendAdminCommandLocked(deviceOwner, action, extras, /* result */ null, + /* inForeground */ true); } } } @@ -3313,6 +3309,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { cleanUpOldUsers(); maybeSetDefaultProfileOwnerUserRestrictions(); handleStartUser(UserHandle.USER_SYSTEM); + maybeLogStart(); // Register an observer for watching for user setup complete and settings changes. mSetupContentObserver.register(); @@ -3368,6 +3365,16 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { updateSystemUpdateFreezePeriodsRecord(/* saveIfChanged */ true); } + private void maybeLogStart() { + if (!SecurityLog.isLoggingEnabled()) { + return; + } + final String verifiedBootState = + mInjector.systemPropertiesGet("ro.boot.verifiedbootstate"); + final String verityMode = mInjector.systemPropertiesGet("ro.boot.veritymode"); + SecurityLog.writeEvent(SecurityLog.TAG_OS_STARTUP, verifiedBootState, verityMode); + } + private void ensureDeviceOwnerUserStarted() { final int userId; synchronized (this) { @@ -3874,14 +3881,17 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { Preconditions.checkNotNull(who, "ComponentName is null"); validateQualityConstant(quality); + final int userId = mInjector.userHandleGetCallingUserId(); synchronized (this) { ActiveAdmin ap = getActiveAdminForCallerLocked( who, DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD, parent); - if (ap.minimumPasswordMetrics.quality != quality) { - ap.minimumPasswordMetrics.quality = quality; - updatePasswordValidityCheckpointLocked(mInjector.userHandleGetCallingUserId()); - saveSettingsLocked(mInjector.userHandleGetCallingUserId()); + final PasswordMetrics metrics = ap.minimumPasswordMetrics; + if (metrics.quality != quality) { + metrics.quality = quality; + updatePasswordValidityCheckpointLocked(userId); + saveSettingsLocked(userId); } + maybeLogPasswordComplexitySet(who, userId, parent, metrics); } } @@ -3974,14 +3984,17 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { return; } Preconditions.checkNotNull(who, "ComponentName is null"); + final int userId = mInjector.userHandleGetCallingUserId(); synchronized (this) { ActiveAdmin ap = getActiveAdminForCallerLocked( who, DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD, parent); - if (ap.minimumPasswordMetrics.length != length) { - ap.minimumPasswordMetrics.length = length; - updatePasswordValidityCheckpointLocked(mInjector.userHandleGetCallingUserId()); - saveSettingsLocked(mInjector.userHandleGetCallingUserId()); + final PasswordMetrics metrics = ap.minimumPasswordMetrics; + if (metrics.length != length) { + metrics.length = length; + updatePasswordValidityCheckpointLocked(userId); + saveSettingsLocked(userId); } + maybeLogPasswordComplexitySet(who, userId, parent, metrics); } } @@ -3997,15 +4010,21 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { return; } Preconditions.checkNotNull(who, "ComponentName is null"); + final int userId = mInjector.userHandleGetCallingUserId(); synchronized (this) { ActiveAdmin ap = getActiveAdminForCallerLocked( who, DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD, parent); if (ap.passwordHistoryLength != length) { ap.passwordHistoryLength = length; - updatePasswordValidityCheckpointLocked(mInjector.userHandleGetCallingUserId()); - saveSettingsLocked(mInjector.userHandleGetCallingUserId()); + updatePasswordValidityCheckpointLocked(userId); + saveSettingsLocked(userId); } } + if (SecurityLog.isLoggingEnabled()) { + final int affectedUserId = parent ? getProfileParentId(userId) : userId; + SecurityLog.writeEvent(SecurityLog.TAG_PASSWORD_HISTORY_LENGTH_SET, + who.getPackageName(), userId, affectedUserId, length); + } } @Override @@ -4039,6 +4058,11 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { // in case this is the first one, set the alarm on the appropriate user. setExpirationAlarmCheckLocked(mContext, userHandle, parent); } + if (SecurityLog.isLoggingEnabled()) { + final int affectedUserId = parent ? getProfileParentId(userHandle) : userHandle; + SecurityLog.writeEvent(SecurityLog.TAG_PASSWORD_EXPIRATION_SET, who.getPackageName(), + userHandle, affectedUserId, timeout); + } } /** @@ -4187,14 +4211,17 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { return; } Preconditions.checkNotNull(who, "ComponentName is null"); + final int userId = mInjector.userHandleGetCallingUserId(); synchronized (this) { - ActiveAdmin ap = getActiveAdminForCallerLocked( + final ActiveAdmin ap = getActiveAdminForCallerLocked( who, DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD, parent); - if (ap.minimumPasswordMetrics.upperCase != length) { - ap.minimumPasswordMetrics.upperCase = length; - updatePasswordValidityCheckpointLocked(mInjector.userHandleGetCallingUserId()); - saveSettingsLocked(mInjector.userHandleGetCallingUserId()); + final PasswordMetrics metrics = ap.minimumPasswordMetrics; + if (metrics.upperCase != length) { + metrics.upperCase = length; + updatePasswordValidityCheckpointLocked(userId); + saveSettingsLocked(userId); } + maybeLogPasswordComplexitySet(who, userId, parent, metrics); } } @@ -4207,14 +4234,17 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { @Override public void setPasswordMinimumLowerCase(ComponentName who, int length, boolean parent) { Preconditions.checkNotNull(who, "ComponentName is null"); + final int userId = mInjector.userHandleGetCallingUserId(); synchronized (this) { ActiveAdmin ap = getActiveAdminForCallerLocked( who, DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD, parent); - if (ap.minimumPasswordMetrics.lowerCase != length) { - ap.minimumPasswordMetrics.lowerCase = length; - updatePasswordValidityCheckpointLocked(mInjector.userHandleGetCallingUserId()); - saveSettingsLocked(mInjector.userHandleGetCallingUserId()); + final PasswordMetrics metrics = ap.minimumPasswordMetrics; + if (metrics.lowerCase != length) { + metrics.lowerCase = length; + updatePasswordValidityCheckpointLocked(userId); + saveSettingsLocked(userId); } + maybeLogPasswordComplexitySet(who, userId, parent, metrics); } } @@ -4230,14 +4260,17 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { return; } Preconditions.checkNotNull(who, "ComponentName is null"); + final int userId = mInjector.userHandleGetCallingUserId(); synchronized (this) { ActiveAdmin ap = getActiveAdminForCallerLocked( who, DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD, parent); - if (ap.minimumPasswordMetrics.letters != length) { - ap.minimumPasswordMetrics.letters = length; - updatePasswordValidityCheckpointLocked(mInjector.userHandleGetCallingUserId()); - saveSettingsLocked(mInjector.userHandleGetCallingUserId()); + final PasswordMetrics metrics = ap.minimumPasswordMetrics; + if (metrics.letters != length) { + metrics.letters = length; + updatePasswordValidityCheckpointLocked(userId); + saveSettingsLocked(userId); } + maybeLogPasswordComplexitySet(who, userId, parent, metrics); } } @@ -4253,14 +4286,17 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { return; } Preconditions.checkNotNull(who, "ComponentName is null"); + final int userId = mInjector.userHandleGetCallingUserId(); synchronized (this) { ActiveAdmin ap = getActiveAdminForCallerLocked( who, DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD, parent); - if (ap.minimumPasswordMetrics.numeric != length) { - ap.minimumPasswordMetrics.numeric = length; - updatePasswordValidityCheckpointLocked(mInjector.userHandleGetCallingUserId()); - saveSettingsLocked(mInjector.userHandleGetCallingUserId()); + final PasswordMetrics metrics = ap.minimumPasswordMetrics; + if (metrics.numeric != length) { + metrics.numeric = length; + updatePasswordValidityCheckpointLocked(userId); + saveSettingsLocked(userId); } + maybeLogPasswordComplexitySet(who, userId, parent, metrics); } } @@ -4268,7 +4304,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { public int getPasswordMinimumNumeric(ComponentName who, int userHandle, boolean parent) { return getStrictestPasswordRequirement(who, userHandle, parent, admin -> admin.minimumPasswordMetrics.numeric, PASSWORD_QUALITY_COMPLEX); - } + } @Override public void setPasswordMinimumSymbols(ComponentName who, int length, boolean parent) { @@ -4276,14 +4312,17 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { return; } Preconditions.checkNotNull(who, "ComponentName is null"); + final int userId = mInjector.userHandleGetCallingUserId(); synchronized (this) { ActiveAdmin ap = getActiveAdminForCallerLocked( who, DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD, parent); - if (ap.minimumPasswordMetrics.symbols != length) { + final PasswordMetrics metrics = ap.minimumPasswordMetrics; + if (metrics.symbols != length) { ap.minimumPasswordMetrics.symbols = length; - updatePasswordValidityCheckpointLocked(mInjector.userHandleGetCallingUserId()); - saveSettingsLocked(mInjector.userHandleGetCallingUserId()); + updatePasswordValidityCheckpointLocked(userId); + saveSettingsLocked(userId); } + maybeLogPasswordComplexitySet(who, userId, parent, metrics); } } @@ -4299,14 +4338,17 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { return; } Preconditions.checkNotNull(who, "ComponentName is null"); + final int userId = mInjector.userHandleGetCallingUserId(); synchronized (this) { ActiveAdmin ap = getActiveAdminForCallerLocked( who, DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD, parent); - if (ap.minimumPasswordMetrics.nonLetter != length) { + final PasswordMetrics metrics = ap.minimumPasswordMetrics; + if (metrics.nonLetter != length) { ap.minimumPasswordMetrics.nonLetter = length; - updatePasswordValidityCheckpointLocked(mInjector.userHandleGetCallingUserId()); - saveSettingsLocked(mInjector.userHandleGetCallingUserId()); + updatePasswordValidityCheckpointLocked(userId); + saveSettingsLocked(userId); } + maybeLogPasswordComplexitySet(who, userId, parent, metrics); } } @@ -4593,6 +4635,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { return; } Preconditions.checkNotNull(who, "ComponentName is null"); + final int userId = mInjector.userHandleGetCallingUserId(); synchronized (this) { // This API can only be called by an active device admin, // so try to retrieve it to check that the caller is one. @@ -4602,9 +4645,14 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { who, DeviceAdminInfo.USES_POLICY_WATCH_LOGIN, parent); if (ap.maximumFailedPasswordsForWipe != num) { ap.maximumFailedPasswordsForWipe = num; - saveSettingsLocked(mInjector.userHandleGetCallingUserId()); + saveSettingsLocked(userId); } } + if (SecurityLog.isLoggingEnabled()) { + final int affectedUserId = parent ? getProfileParentId(userId) : userId; + SecurityLog.writeEvent(SecurityLog.TAG_MAX_PASSWORD_ATTEMPTS_SET, who.getPackageName(), + userId, affectedUserId, num); + } } @Override @@ -4702,7 +4750,6 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { return false; } } - @Override public boolean resetPassword(String passwordOrNull, int flags) throws RemoteException { final int callingUid = mInjector.binderGetCallingUid(); @@ -4958,6 +5005,11 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { updateMaximumTimeToLockLocked(userHandle); } } + if (SecurityLog.isLoggingEnabled()) { + final int affectedUserId = parent ? getProfileParentId(userHandle) : userHandle; + SecurityLog.writeEvent(SecurityLog.TAG_MAX_SCREEN_LOCK_TIMEOUT_SET, + who.getPackageName(), userHandle, affectedUserId, timeMs); + } } private void updateMaximumTimeToLockLocked(@UserIdInt int userId) { @@ -5127,11 +5179,12 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { final long ident = mInjector.binderClearCallingIdentity(); try { + final ComponentName adminComponent = admin.info.getComponent(); // Evict key if ((flags & DevicePolicyManager.FLAG_EVICT_CREDENTIAL_ENCRYPTION_KEY) != 0) { enforceManagedProfile( callingUserId, "set FLAG_EVICT_CREDENTIAL_ENCRYPTION_KEY"); - if (!isProfileOwner(admin.info.getComponent(), callingUserId)) { + if (!isProfileOwner(adminComponent, callingUserId)) { throw new SecurityException("Only profile owner admins can set " + "FLAG_EVICT_CREDENTIAL_ENCRYPTION_KEY"); } @@ -5161,6 +5214,13 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } else { mInjector.getTrustManager().setDeviceLockedForUser(userToLock, true); } + + if (SecurityLog.isLoggingEnabled()) { + final int affectedUserId = + parent ? getProfileParentId(callingUserId) : callingUserId; + SecurityLog.writeEvent(SecurityLog.TAG_REMOTE_LOCK, + adminComponent.getPackageName(), callingUserId, affectedUserId); + } } catch (RemoteException e) { } finally { mInjector.binderRestoreCallingIdentity(ident); @@ -6191,8 +6251,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { if (mInjector.securityLogIsLoggingEnabled()) { SecurityLog.writeEvent(SecurityLog.TAG_KEYGUARD_DISMISS_AUTH_ATTEMPT, - /*result*/ 0, - /*method strength*/ 1); + /*result*/ 0, /*method strength*/ 1); } } @@ -7039,6 +7098,11 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { saveSettingsLocked(userHandle); } } + if (SecurityLog.isLoggingEnabled()) { + final int affectedUserId = parent ? getProfileParentId(userHandle) : userHandle; + SecurityLog.writeEvent(SecurityLog.TAG_KEYGUARD_DISABLED_FEATURES_SET, + who.getPackageName(), userHandle, affectedUserId, which); + } } /** @@ -8961,7 +9025,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } @Override - public boolean startUserInBackground(ComponentName who, UserHandle userHandle) { + public int startUserInBackground(ComponentName who, UserHandle userHandle) { Preconditions.checkNotNull(who, "ComponentName is null"); Preconditions.checkNotNull(userHandle, "UserHandle is null"); @@ -8972,27 +9036,31 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { final int userId = userHandle.getIdentifier(); if (isManagedProfile(userId)) { Log.w(LOG_TAG, "Managed profile cannot be started in background"); - return false; + return DevicePolicyManager.USER_OPERATION_ERROR_MANAGED_PROFILE; } final long id = mInjector.binderClearCallingIdentity(); try { if (!mInjector.getActivityManagerInternal().canStartMoreUsers()) { Log.w(LOG_TAG, "Cannot start more users in background"); - return false; + return DevicePolicyManager.USER_OPERATION_ERROR_MAX_RUNNING_USERS; } - return mInjector.getIActivityManager().startUserInBackground(userId); + if (mInjector.getIActivityManager().startUserInBackground(userId)) { + return DevicePolicyManager.USER_OPERATION_SUCCESS; + } else { + return DevicePolicyManager.USER_OPERATION_ERROR_UNKNOWN; + } } catch (RemoteException e) { // Same process, should not happen. - return false; + return DevicePolicyManager.USER_OPERATION_ERROR_UNKNOWN; } finally { mInjector.binderRestoreCallingIdentity(id); } } @Override - public boolean stopUser(ComponentName who, UserHandle userHandle) { + public int stopUser(ComponentName who, UserHandle userHandle) { Preconditions.checkNotNull(who, "ComponentName is null"); Preconditions.checkNotNull(userHandle, "UserHandle is null"); @@ -9003,23 +9071,14 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { final int userId = userHandle.getIdentifier(); if (isManagedProfile(userId)) { Log.w(LOG_TAG, "Managed profile cannot be stopped"); - return false; + return DevicePolicyManager.USER_OPERATION_ERROR_MANAGED_PROFILE; } - final long id = mInjector.binderClearCallingIdentity(); - try { - return mInjector.getIActivityManager().stopUser(userId, true /*force*/, null) - == USER_OP_SUCCESS; - } catch (RemoteException e) { - // Same process, should not happen. - return false; - } finally { - mInjector.binderRestoreCallingIdentity(id); - } + return stopUserUnchecked(userId); } @Override - public boolean logoutUser(ComponentName who) { + public int logoutUser(ComponentName who) { Preconditions.checkNotNull(who, "ComponentName is null"); final int callingUserId = mInjector.userHandleGetCallingUserId(); @@ -9033,20 +9092,40 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { if (isManagedProfile(callingUserId)) { Log.w(LOG_TAG, "Managed profile cannot be logout"); - return false; + return DevicePolicyManager.USER_OPERATION_ERROR_MANAGED_PROFILE; } final long id = mInjector.binderClearCallingIdentity(); try { if (!mInjector.getIActivityManager().switchUser(UserHandle.USER_SYSTEM)) { Log.w(LOG_TAG, "Failed to switch to primary user"); - return false; + // This should never happen as target user is UserHandle.USER_SYSTEM + return DevicePolicyManager.USER_OPERATION_ERROR_UNKNOWN; } - return mInjector.getIActivityManager().stopUser(callingUserId, true /*force*/, null) - == USER_OP_SUCCESS; } catch (RemoteException e) { // Same process, should not happen. - return false; + return DevicePolicyManager.USER_OPERATION_ERROR_UNKNOWN; + } finally { + mInjector.binderRestoreCallingIdentity(id); + } + + return stopUserUnchecked(callingUserId); + } + + private int stopUserUnchecked(int userId) { + final long id = mInjector.binderClearCallingIdentity(); + try { + switch (mInjector.getIActivityManager().stopUser(userId, true /*force*/, null)) { + case ActivityManager.USER_OP_SUCCESS: + return DevicePolicyManager.USER_OPERATION_SUCCESS; + case ActivityManager.USER_OP_IS_CURRENT: + return DevicePolicyManager.USER_OPERATION_ERROR_CURRENT_USER; + default: + return DevicePolicyManager.USER_OPERATION_ERROR_UNKNOWN; + } + } catch (RemoteException e) { + // Same process, should not happen. + return DevicePolicyManager.USER_OPERATION_ERROR_UNKNOWN; } finally { mInjector.binderRestoreCallingIdentity(id); } @@ -9186,6 +9265,12 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } saveUserRestrictionsLocked(userHandle); } + if (SecurityLog.isLoggingEnabled()) { + final int eventTag = enabledFromThisOwner + ? SecurityLog.TAG_USER_RESTRICTION_ADDED + : SecurityLog.TAG_USER_RESTRICTION_REMOVED; + SecurityLog.writeEvent(eventTag, who.getPackageName(), userHandle, key); + } } private void saveUserRestrictionsLocked(int userId) { @@ -11335,22 +11420,38 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } } + @Override + public boolean isMeteredDataDisabledForUser(ComponentName who, + String packageName, int userId) { + Preconditions.checkNotNull(who); + + if (!mHasFeature) { + return false; + } + if (!isCallerWithSystemUid()) { + throw new SecurityException( + "Only the system can query restricted pkgs for a specific user"); + } + synchronized (this) { + final ActiveAdmin admin = getActiveAdminUncheckedLocked(who, userId); + if (admin != null && admin.meteredDisabledPackages != null) { + return admin.meteredDisabledPackages.contains(packageName); + } + } + return false; + } + private void pushMeteredDisabledPackagesLocked(int userId) { mInjector.getNetworkPolicyManagerInternal().setMeteredRestrictedPackages( getMeteredDisabledPackagesLocked(userId), userId); } private Set<String> getMeteredDisabledPackagesLocked(int userId) { - final DevicePolicyData policy = getUserData(userId); + final ComponentName who = getOwnerComponent(userId); final Set<String> restrictedPkgs = new ArraySet<>(); - for (int i = policy.mAdminList.size() - 1; i >= 0; --i) { - final ActiveAdmin admin = policy.mAdminList.get(i); - if (!isActiveAdminWithPolicyForUserLocked(admin, - DeviceAdminInfo.USES_POLICY_PROFILE_OWNER, userId)) { - // Not a profile or device owner, ignore - continue; - } - if (admin.meteredDisabledPackages != null) { + if (who != null) { + final ActiveAdmin admin = getActiveAdminUncheckedLocked(who, userId); + if (admin != null && admin.meteredDisabledPackages != null) { restrictedPkgs.addAll(admin.meteredDisabledPackages); } } @@ -12399,9 +12500,11 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } @Override - public boolean clearApplicationUserData(ComponentName admin, String packageName, + public void clearApplicationUserData(ComponentName admin, String packageName, IPackageDataObserver callback) { Preconditions.checkNotNull(admin, "ComponentName is null"); + Preconditions.checkNotNull(packageName, "packageName is null"); + Preconditions.checkNotNull(callback, "callback is null"); synchronized (this) { getActiveAdminForCallerLocked(admin, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER); } @@ -12409,29 +12512,24 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { long ident = mInjector.binderClearCallingIdentity(); try { - return ActivityManager.getService().clearApplicationUserData(packageName, false, - callback, userId); + ActivityManager.getService().clearApplicationUserData(packageName, false, callback, + userId); } catch(RemoteException re) { // Same process, should not happen. } catch (SecurityException se) { // This can happen e.g. for device admin packages, do not throw out the exception, // because callers have no means to know beforehand for which packages this might - // happen. + // happen. If so, we send back that removal failed. Slog.w(LOG_TAG, "Not allowed to clear application user data for package " + packageName, se); - } finally { - mInjector.binderRestoreCallingIdentity(ident); - } - - if (callback != null) { try { - // If there was a throw above, we send back that removal failed callback.onRemoveCompleted(packageName, false); } catch (RemoteException re) { // Caller is no longer available, ignore } + } finally { + mInjector.binderRestoreCallingIdentity(ident); } - return false; } @Override @@ -12962,4 +13060,15 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { TRANSFER_OWNERSHIP_PARAMETERS_XML); parametersFile.delete(); } + + private void maybeLogPasswordComplexitySet(ComponentName who, int userId, boolean parent, + PasswordMetrics metrics) { + if (SecurityLog.isLoggingEnabled()) { + final int affectedUserId = parent ? getProfileParentId(userId) : userId; + SecurityLog.writeEvent(SecurityLog.TAG_PASSWORD_COMPLEXITY_SET, who.getPackageName(), + userId, affectedUserId, metrics.length, metrics.quality, metrics.letters, + metrics.nonLetter, metrics.numeric, metrics.upperCase, metrics.lowerCase, + metrics.symbols); + } + } } diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PasswordBlacklist.java b/services/devicepolicy/java/com/android/server/devicepolicy/PasswordBlacklist.java index 6a9b53a0a2df..a17a1075f100 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/PasswordBlacklist.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/PasswordBlacklist.java @@ -47,7 +47,7 @@ public class PasswordBlacklist { * This is a lightweight operation to prepare variables but not perform any IO. */ public PasswordBlacklist(File file) { - mFile = new AtomicFile(file); + mFile = new AtomicFile(file, "device-policy"); } /** diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/SecurityLogMonitor.java b/services/devicepolicy/java/com/android/server/devicepolicy/SecurityLogMonitor.java index a9fd8e53e941..3277adf479ed 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/SecurityLogMonitor.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/SecurityLogMonitor.java @@ -71,6 +71,10 @@ class SecurityLogMonitor implements Runnable { */ private static final int BUFFER_ENTRIES_MAXIMUM_LEVEL = BUFFER_ENTRIES_NOTIFICATION_LEVEL * 10; /** + * Critical log buffer level, 90% of capacity. + */ + private static final int BUFFER_ENTRIES_CRITICAL_LEVEL = BUFFER_ENTRIES_MAXIMUM_LEVEL * 9 / 10; + /** * How often should Device Owner be notified under normal circumstances. */ private static final long RATE_LIMIT_INTERVAL_MILLISECONDS = TimeUnit.HOURS.toMillis(2); @@ -97,6 +101,10 @@ class SecurityLogMonitor implements Runnable { @GuardedBy("mLock") private boolean mAllowedToRetrieve = false; + // Whether we have already logged the fact that log buffer reached 90%, to avoid dupes. + @GuardedBy("mLock") + private boolean mCriticalLevelLogged = false; + /** * Last events fetched from log to check for overlap between batches. We can leave it empty if * we are sure there will be no overlap anymore, e.g. when we get empty batch. @@ -116,10 +124,12 @@ class SecurityLogMonitor implements Runnable { void start() { Slog.i(TAG, "Starting security logging."); + SecurityLog.writeEvent(SecurityLog.TAG_LOGGING_STARTED); mLock.lock(); try { if (mMonitorThread == null) { mPendingLogs = new ArrayList<>(); + mCriticalLevelLogged = false; mId = 0; mAllowedToRetrieve = false; mNextAllowedRetrievalTimeMillis = -1; @@ -135,6 +145,7 @@ class SecurityLogMonitor implements Runnable { void stop() { Slog.i(TAG, "Stopping security logging."); + SecurityLog.writeEvent(SecurityLog.TAG_LOGGING_STOPPED); mLock.lock(); try { if (mMonitorThread != null) { @@ -205,6 +216,7 @@ class SecurityLogMonitor implements Runnable { mLock.lock(); mAllowedToRetrieve = false; mPendingLogs = new ArrayList<>(); + mCriticalLevelLogged = false; mLock.unlock(); Slog.i(TAG, "Discarded all logs."); } @@ -222,6 +234,7 @@ class SecurityLogMonitor implements Runnable { + RATE_LIMIT_INTERVAL_MILLISECONDS; List<SecurityEvent> result = mPendingLogs; mPendingLogs = new ArrayList<>(); + mCriticalLevelLogged = false; return result; } else { return null; @@ -344,11 +357,14 @@ class SecurityLogMonitor implements Runnable { // Save the rest of the new batch. mPendingLogs.addAll(idLogs); + checkCriticalLevel(); + if (mPendingLogs.size() > BUFFER_ENTRIES_MAXIMUM_LEVEL) { // Truncate buffer down to half of BUFFER_ENTRIES_MAXIMUM_LEVEL. mPendingLogs = new ArrayList<>(mPendingLogs.subList( mPendingLogs.size() - (BUFFER_ENTRIES_MAXIMUM_LEVEL / 2), mPendingLogs.size())); + mCriticalLevelLogged = false; Slog.i(TAG, "Pending logs buffer full. Discarding old logs."); } if (DEBUG) Slog.d(TAG, mPendingLogs.size() + " pending events in the buffer after merging," @@ -357,6 +373,20 @@ class SecurityLogMonitor implements Runnable { } @GuardedBy("mLock") + private void checkCriticalLevel() { + if (!SecurityLog.isLoggingEnabled()) { + return; + } + + if (mPendingLogs.size() >= BUFFER_ENTRIES_CRITICAL_LEVEL) { + if (!mCriticalLevelLogged) { + mCriticalLevelLogged = true; + SecurityLog.writeEvent(SecurityLog.TAG_LOG_BUFFER_SIZE_CRITICAL); + } + } + } + + @GuardedBy("mLock") private void assignLogId(SecurityEvent event) { event.setId(mId); if (mId == Long.MAX_VALUE) { diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index f95c6f042fed..210fd473ccd4 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -111,6 +111,7 @@ import com.android.server.stats.StatsCompanionService; import com.android.server.statusbar.StatusBarManagerService; import com.android.server.storage.DeviceStorageMonitorService; import com.android.server.telecom.TelecomLoaderService; +import com.android.server.textclassifier.TextClassificationManagerService; import com.android.server.trust.TrustManagerService; import com.android.server.tv.TvInputManagerService; import com.android.server.tv.TvRemoteService; @@ -733,6 +734,8 @@ public final class SystemServer { false); boolean disableTextServices = SystemProperties.getBoolean("config.disable_textservices", false); + boolean disableSystemTextClassifier = SystemProperties.getBoolean( + "config.disable_systemtextclassifier", false); boolean disableConsumerIr = SystemProperties.getBoolean("config.disable_consumerir", false); boolean disableVrManager = SystemProperties.getBoolean("config.disable_vrmanager", false); boolean disableCameraService = SystemProperties.getBoolean("config.disable_cameraservice", @@ -1066,6 +1069,12 @@ public final class SystemServer { traceEnd(); } + if (!disableSystemTextClassifier) { + traceBeginAndSlog("StartTextClassificationManagerService"); + mSystemServiceManager.startService(TextClassificationManagerService.Lifecycle.class); + traceEnd(); + } + traceBeginAndSlog("StartNetworkScoreService"); try { networkScore = new NetworkScoreService(context); diff --git a/services/robotests/src/com/android/server/backup/BackupManagerConstantsTest.java b/services/robotests/src/com/android/server/backup/BackupManagerConstantsTest.java index c397f23b2b5a..0752537abfcc 100644 --- a/services/robotests/src/com/android/server/backup/BackupManagerConstantsTest.java +++ b/services/robotests/src/com/android/server/backup/BackupManagerConstantsTest.java @@ -26,6 +26,7 @@ import android.provider.Settings; import com.android.server.testing.FrameworkRobolectricTestRunner; import com.android.server.testing.SystemLoaderClasses; +import com.android.server.testing.SystemLoaderPackages; import org.junit.Before; import org.junit.Test; @@ -36,7 +37,7 @@ import org.robolectric.annotation.Config; @RunWith(FrameworkRobolectricTestRunner.class) @Config(manifest = Config.NONE, sdk = 26) -@SystemLoaderClasses({BackupManagerConstants.class}) +@SystemLoaderPackages({"com.android.server.backup"}) @Presubmit public class BackupManagerConstantsTest { private static final String PACKAGE_NAME = "some.package.name"; diff --git a/services/robotests/src/com/android/server/backup/BackupManagerServiceTest.java b/services/robotests/src/com/android/server/backup/BackupManagerServiceTest.java index b60ad4b3f815..df09780ecdd0 100644 --- a/services/robotests/src/com/android/server/backup/BackupManagerServiceTest.java +++ b/services/robotests/src/com/android/server/backup/BackupManagerServiceTest.java @@ -48,7 +48,8 @@ import com.android.server.backup.testing.TransportData; import com.android.server.backup.testing.TransportTestUtils.TransportMock; import com.android.server.backup.transport.TransportNotRegisteredException; import com.android.server.testing.FrameworkRobolectricTestRunner; -import com.android.server.testing.SystemLoaderClasses; +import com.android.server.testing.SystemLoaderPackages; + import java.io.File; import java.util.HashMap; import java.util.List; @@ -74,11 +75,7 @@ import org.robolectric.shadows.ShadowSystemClock; sdk = 26, shadows = {ShadowAppBackupUtils.class, ShadowBackupPolicyEnforcer.class} ) -@SystemLoaderClasses({ - BackupManagerService.class, - TransportManager.class, - PackageManagerBackupAgent.class -}) +@SystemLoaderPackages({"com.android.server.backup"}) @Presubmit public class BackupManagerServiceTest { private static final String TAG = "BMSTest"; diff --git a/services/robotests/src/com/android/server/backup/PerformBackupTaskTest.java b/services/robotests/src/com/android/server/backup/PerformBackupTaskTest.java index 1360828bc81c..e103464207d2 100644 --- a/services/robotests/src/com/android/server/backup/PerformBackupTaskTest.java +++ b/services/robotests/src/com/android/server/backup/PerformBackupTaskTest.java @@ -69,6 +69,7 @@ import com.android.server.backup.testing.TransportTestUtils.TransportMock; import com.android.server.backup.transport.TransportClient; import com.android.server.testing.FrameworkRobolectricTestRunner; import com.android.server.testing.SystemLoaderClasses; +import com.android.server.testing.SystemLoaderPackages; import com.android.server.testing.shadows.FrameworkShadowPackageManager; import com.android.server.testing.shadows.ShadowBackupDataInput; import com.android.server.testing.shadows.ShadowBackupDataOutput; @@ -102,12 +103,10 @@ import java.util.stream.Stream; ShadowQueuedWork.class } ) +@SystemLoaderPackages({"com.android.server.backup"}) @SystemLoaderClasses({ - BackupManagerService.class, - PerformBackupTask.class, BackupDataOutput.class, FullBackupDataOutput.class, - TransportManager.class, BackupAgent.class, IBackupTransport.class, IBackupAgent.class, diff --git a/services/robotests/src/com/android/server/backup/TransportManagerTest.java b/services/robotests/src/com/android/server/backup/TransportManagerTest.java index 068fe8137467..44ac8039bbed 100644 --- a/services/robotests/src/com/android/server/backup/TransportManagerTest.java +++ b/services/robotests/src/com/android/server/backup/TransportManagerTest.java @@ -51,7 +51,7 @@ import com.android.server.backup.transport.TransportClient; import com.android.server.backup.transport.TransportClientManager; import com.android.server.backup.transport.TransportNotRegisteredException; import com.android.server.testing.FrameworkRobolectricTestRunner; -import com.android.server.testing.SystemLoaderClasses; +import com.android.server.testing.SystemLoaderPackages; import com.android.server.testing.shadows.FrameworkShadowContextImpl; import com.android.server.testing.shadows.FrameworkShadowPackageManager; import java.util.ArrayList; @@ -75,7 +75,7 @@ import org.robolectric.shadows.ShadowPackageManager; sdk = 26, shadows = {FrameworkShadowPackageManager.class, FrameworkShadowContextImpl.class} ) -@SystemLoaderClasses({TransportManager.class}) +@SystemLoaderPackages({"com.android.server.backup"}) @Presubmit public class TransportManagerTest { private static final String PACKAGE_A = "some.package.a"; diff --git a/services/robotests/src/com/android/server/backup/internal/PerformInitializeTaskTest.java b/services/robotests/src/com/android/server/backup/internal/PerformInitializeTaskTest.java index 55fb46068eeb..5810c30acbf5 100644 --- a/services/robotests/src/com/android/server/backup/internal/PerformInitializeTaskTest.java +++ b/services/robotests/src/com/android/server/backup/internal/PerformInitializeTaskTest.java @@ -49,7 +49,7 @@ import com.android.server.backup.testing.TransportData; import com.android.server.backup.testing.TransportTestUtils.TransportMock; import com.android.server.backup.transport.TransportClient; import com.android.server.testing.FrameworkRobolectricTestRunner; -import com.android.server.testing.SystemLoaderClasses; +import com.android.server.testing.SystemLoaderPackages; import org.junit.Before; import org.junit.Test; @@ -67,11 +67,7 @@ import java.util.stream.Stream; @RunWith(FrameworkRobolectricTestRunner.class) @Config(manifest = Config.NONE, sdk = 26) -@SystemLoaderClasses({ - BackupManagerService.class, - PerformInitializeTaskTest.class, - TransportManager.class -}) +@SystemLoaderPackages({"com.android.server.backup"}) @Presubmit public class PerformInitializeTaskTest { @Mock private BackupManagerService mBackupManagerService; diff --git a/services/robotests/src/com/android/server/backup/transport/TransportClientTest.java b/services/robotests/src/com/android/server/backup/transport/TransportClientTest.java index db6e62f87a34..ff1644cb75ad 100644 --- a/services/robotests/src/com/android/server/backup/transport/TransportClientTest.java +++ b/services/robotests/src/com/android/server/backup/transport/TransportClientTest.java @@ -46,6 +46,7 @@ import com.android.server.EventLogTags; import com.android.server.backup.TransportManager; import com.android.server.testing.FrameworkRobolectricTestRunner; import com.android.server.testing.SystemLoaderClasses; +import com.android.server.testing.SystemLoaderPackages; import com.android.server.testing.shadows.ShadowCloseGuard; import com.android.server.testing.shadows.ShadowEventLog; import com.android.server.testing.shadows.ShadowSlog; @@ -66,7 +67,7 @@ import org.robolectric.shadows.ShadowLooper; sdk = 26, shadows = {ShadowEventLog.class, ShadowCloseGuard.class, ShadowSlog.class} ) -@SystemLoaderClasses({TransportManager.class, TransportClient.class}) +@SystemLoaderPackages({"com.android.server.backup"}) @Presubmit public class TransportClientTest { private static final String PACKAGE_NAME = "some.package.name"; diff --git a/services/robotests/src/com/android/server/testing/FrameworkRobolectricTestRunner.java b/services/robotests/src/com/android/server/testing/FrameworkRobolectricTestRunner.java index c94d5983d2f5..d2a4d06dfbd0 100644 --- a/services/robotests/src/com/android/server/testing/FrameworkRobolectricTestRunner.java +++ b/services/robotests/src/com/android/server/testing/FrameworkRobolectricTestRunner.java @@ -16,8 +16,7 @@ package com.android.server.testing; -import com.android.server.backup.PerformBackupTaskTest; -import com.android.server.backup.internal.PerformBackupTask; +import static java.util.Arrays.asList; import com.google.common.collect.ImmutableSet; @@ -33,10 +32,11 @@ import org.robolectric.util.Util; import java.io.IOException; import java.io.InputStream; import java.net.URL; -import java.nio.file.Files; -import java.nio.file.Paths; +import java.util.Arrays; import java.util.Enumeration; +import java.util.HashSet; import java.util.Set; +import java.util.stream.Stream; import javax.annotation.Nonnull; @@ -51,9 +51,9 @@ import javax.annotation.Nonnull; * against the actual classes that are in the tree, not a past version of them. Ideally we would * have a locally built jar referenced by Robolectric, but until that happens one can use this * class. - * This class reads the {@link SystemLoaderClasses} annotation on test classes and for each class - * in that annotation value it will bypass the android jar and load it from the system class loader. - * Allowing the test to test the actual class in the tree. + * This class reads the {@link SystemLoaderClasses} or {@link SystemLoaderPackages} annotations on + * test classes, for classes that match the annotations it will bypass the android jar and load it + * from the system class loader. Allowing the test to test the actual class in the tree. * * Implementation note: One could think about overriding * {@link RobolectricTestRunner#createClassLoaderConfig(FrameworkMethod)} method and putting the @@ -72,11 +72,21 @@ public class FrameworkRobolectricTestRunner extends RobolectricTestRunner { public FrameworkRobolectricTestRunner(Class<?> testClass) throws InitializationError { super(testClass); - SystemLoaderClasses annotation = testClass.getAnnotation(SystemLoaderClasses.class); - Class<?>[] systemLoaderClasses = - (annotation != null) ? annotation.value() : new Class<?>[0]; - Set<String> systemLoaderClassNames = classesToClassNames(systemLoaderClasses); - mSandboxFactory = new FrameworkSandboxFactory(systemLoaderClassNames); + Set<String> classPrefixes = getSystemLoaderClassPrefixes(testClass); + mSandboxFactory = new FrameworkSandboxFactory(classPrefixes); + } + + private Set<String> getSystemLoaderClassPrefixes(Class<?> testClass) { + Set<String> classPrefixes = new HashSet<>(); + SystemLoaderClasses byClass = testClass.getAnnotation(SystemLoaderClasses.class); + if (byClass != null) { + Stream.of(byClass.value()).map(Class::getName).forEach(classPrefixes::add); + } + SystemLoaderPackages byPackage = testClass.getAnnotation(SystemLoaderPackages.class); + if (byPackage != null) { + classPrefixes.addAll(asList(byPackage.value())); + } + return classPrefixes; } @Nonnull @@ -92,15 +102,15 @@ public class FrameworkRobolectricTestRunner extends RobolectricTestRunner { } private static class FrameworkClassLoader extends SandboxClassLoader { - private final Set<String> mSystemLoaderClasses; + private final Set<String> mSystemLoaderClassPrefixes; private FrameworkClassLoader( - Set<String> systemLoaderClasses, + Set<String> systemLoaderClassPrefixes, ClassLoader systemClassLoader, InstrumentationConfiguration instrumentationConfig, URL... urls) { super(systemClassLoader, instrumentationConfig, urls); - mSystemLoaderClasses = systemLoaderClasses; + mSystemLoaderClassPrefixes = systemLoaderClassPrefixes; } @Override @@ -146,8 +156,8 @@ public class FrameworkRobolectricTestRunner extends RobolectricTestRunner { * loader, so we test if the classes in the annotation are prefixes of the class to load. */ private boolean shouldLoadFromSystemLoader(String className) { - for (String classNamePrefix : mSystemLoaderClasses) { - if (className.startsWith(classNamePrefix)) { + for (String classPrefix : mSystemLoaderClassPrefixes) { + if (className.startsWith(classPrefix)) { return true; } } @@ -156,10 +166,10 @@ public class FrameworkRobolectricTestRunner extends RobolectricTestRunner { } private static class FrameworkSandboxFactory extends SandboxFactory { - private final Set<String> mSystemLoaderClasses; + private final Set<String> mSystemLoaderClassPrefixes; - private FrameworkSandboxFactory(Set<String> systemLoaderClasses) { - mSystemLoaderClasses = systemLoaderClasses; + private FrameworkSandboxFactory(Set<String> systemLoaderClassPrefixes) { + mSystemLoaderClassPrefixes = systemLoaderClassPrefixes; } @Nonnull @@ -167,18 +177,10 @@ public class FrameworkRobolectricTestRunner extends RobolectricTestRunner { public ClassLoader createClassLoader( InstrumentationConfiguration instrumentationConfig, URL... urls) { return new FrameworkClassLoader( - mSystemLoaderClasses, + mSystemLoaderClassPrefixes, ClassLoader.getSystemClassLoader(), instrumentationConfig, urls); } } - - private static Set<String> classesToClassNames(Class<?>[] classes) { - ImmutableSet.Builder<String> builder = ImmutableSet.builder(); - for (Class<?> classObject : classes) { - builder.add(classObject.getName()); - } - return builder.build(); - } } diff --git a/services/robotests/src/com/android/server/testing/SystemLoaderPackages.java b/services/robotests/src/com/android/server/testing/SystemLoaderPackages.java new file mode 100644 index 000000000000..e01c0a4cffa5 --- /dev/null +++ b/services/robotests/src/com/android/server/testing/SystemLoaderPackages.java @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.server.testing; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Annotation to be used in test classes that run with {@link FrameworkRobolectricTestRunner}. + * This will make the classes under the specified packages be loaded from the system class loader, + * NOT from the Robolectric android jar. + * + * @see FrameworkRobolectricTestRunner + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +public @interface SystemLoaderPackages { + String[] value() default {}; +} diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java index 6b87ea965686..00a85a539538 100644 --- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java @@ -2160,6 +2160,51 @@ public class DevicePolicyManagerTest extends DpmTestBase { () -> dpm.getMeteredDataDisabled(admin1)); } + public void testGetMeteredDataDisabledForUser() throws Exception { + setAsProfileOwner(admin1); + + // Setup + final ArrayList<String> emptyList = new ArrayList<>(); + final ArrayList<String> pkgsToRestrict = new ArrayList<>(); + final String package1 = "com.example.one"; + final String package2 = "com.example.two"; + final String package3 = "com.example.three"; + pkgsToRestrict.add(package1); + pkgsToRestrict.add(package2); + setupPackageInPackageManager(package1, DpmMockContext.CALLER_USER_HANDLE, 123, 0); + setupPackageInPackageManager(package2, DpmMockContext.CALLER_USER_HANDLE, 456, 0); + List<String> excludedPkgs = dpm.setMeteredDataDisabled(admin1, pkgsToRestrict); + + // Verify + assertEquals(emptyList, excludedPkgs); + mContext.binder.callingUid = DpmMockContext.SYSTEM_UID; + assertTrue(package1 + "should be restricted", + dpm.isMeteredDataDisabledForUser(admin1, package1, + DpmMockContext.CALLER_USER_HANDLE)); + assertTrue(package2 + "should be restricted", + dpm.isMeteredDataDisabledForUser(admin1, package2, + DpmMockContext.CALLER_USER_HANDLE)); + assertFalse(package3 + "should not be restricted", + dpm.isMeteredDataDisabledForUser(admin1, package3, + DpmMockContext.CALLER_USER_HANDLE)); + } + + public void testGetMeteredDataDisabledForUser_nonSystemUidCaller() throws Exception { + setAsProfileOwner(admin1); + assertExpectException(SecurityException.class, + /* messageRegex= */ "Only the system can query restricted pkgs", + () -> dpm.isMeteredDataDisabledForUser( + admin1, "com.example.one", DpmMockContext.CALLER_USER_HANDLE)); + dpm.clearProfileOwner(admin1); + + setDeviceOwner(); + assertExpectException(SecurityException.class, + /* messageRegex= */ "Only the system can query restricted pkgs", + () -> dpm.isMeteredDataDisabledForUser( + admin1, "com.example.one", DpmMockContext.CALLER_USER_HANDLE)); + clearDeviceOwner(); + } + public void testCreateAdminSupportIntent() throws Exception { // Setup device owner. mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID; diff --git a/services/tests/servicestests/src/com/android/server/job/JobSetTest.java b/services/tests/servicestests/src/com/android/server/job/JobSetTest.java new file mode 100644 index 000000000000..83bd9fc2f648 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/job/JobSetTest.java @@ -0,0 +1,146 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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.job; + +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +import static org.junit.Assume.assumeFalse; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import android.app.job.JobInfo; +import android.content.ComponentName; +import android.content.Context; +import android.content.pm.PackageManagerInternal; +import android.os.Build; +import android.os.UserHandle; +import android.support.test.InstrumentationRegistry; +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; +import android.util.ArraySet; +import android.util.Log; +import android.util.SparseArray; + +import com.android.server.LocalServices; +import com.android.server.job.controllers.JobStatus; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public class JobSetTest { + private static final String TAG = JobSetTest.class.getSimpleName(); + private static final int SECONDARY_USER_ID_1 = 12; + private static final int SECONDARY_USER_ID_2 = 13; + + private Context mContext; + private ComponentName mComponent; + private JobStore.JobSet mJobSet; + + @Before + public void setUp() throws Exception { + mContext = InstrumentationRegistry.getTargetContext(); + mComponent = new ComponentName(mContext, JobStoreTest.class); + mJobSet = new JobStore.JobSet(); + final PackageManagerInternal pm = mock(PackageManagerInternal.class); + when(pm.getPackageTargetSdkVersion(anyString())) + .thenReturn(Build.VERSION_CODES.CUR_DEVELOPMENT); + LocalServices.addService(PackageManagerInternal.class, pm); + assumeFalse("Test cannot run in user " + mContext.getUserId(), + mContext.getUserId() == SECONDARY_USER_ID_1 + || mContext.getUserId() == SECONDARY_USER_ID_2); + } + + private JobStatus getJobStatusWithCallinUid(int jobId, int callingUid) { + final JobInfo jobInfo = new JobInfo.Builder(jobId, mComponent) + .setPeriodic(10) + .setRequiresCharging(true) + .build(); + return JobStatus.createFromJobInfo(jobInfo, callingUid, mContext.getPackageName(), + mContext.getUserId(), "Test"); + } + + @Test + public void testBothMapsHaveSameJobs() { + final int callingUid1 = UserHandle.getUid(SECONDARY_USER_ID_1, 1); + final int callingUid2 = UserHandle.getUid(SECONDARY_USER_ID_2, 1); + final JobStatus testJob1 = getJobStatusWithCallinUid(1, callingUid1); + final JobStatus testJob2 = getJobStatusWithCallinUid(2, callingUid2); + mJobSet.add(testJob1); + mJobSet.add(testJob2); + for (int i = 11; i <= 20; i++) { + mJobSet.add(getJobStatusWithCallinUid(i, (i%2 == 0) ? callingUid2 : callingUid1)); + } + assertHaveSameJobs(mJobSet.mJobsPerSourceUid, mJobSet.mJobs); + mJobSet.remove(testJob1); + mJobSet.remove(testJob2); + assertHaveSameJobs(mJobSet.mJobsPerSourceUid, mJobSet.mJobs); + mJobSet.removeJobsOfNonUsers(new int[] {mContext.getUserId(), SECONDARY_USER_ID_1}); + assertHaveSameJobs(mJobSet.mJobsPerSourceUid, mJobSet.mJobs); + mJobSet.removeJobsOfNonUsers(new int[] {mContext.getUserId()}); + assertTrue("mJobs should be empty", mJobSet.mJobs.size() == 0); + assertTrue("mJobsPerSourceUid should be empty", mJobSet.mJobsPerSourceUid.size() == 0); + } + + private static void assertHaveSameJobs(SparseArray<ArraySet<JobStatus>> map1, + SparseArray<ArraySet<JobStatus>> map2) { + final ArraySet<JobStatus> set1 = new ArraySet<>(); + final ArraySet<JobStatus> set2 = new ArraySet<>(); + int size1 = 0; + for (int i = 0; i < map1.size(); i++) { + final ArraySet<JobStatus> jobs = map1.valueAt(i); + if (jobs == null) return; + size1 += jobs.size(); + set1.addAll(jobs); + } + for (int i = 0; i < map2.size(); i++) { + final ArraySet<JobStatus> jobs = map2.valueAt(i); + if (jobs == null) return; + size1 -= jobs.size(); + set2.addAll(jobs); + } + if (size1 != 0 || !set1.equals(set2)) { + dump("map1", map1); + dump("map2", map2); + fail("Both maps have different sets of jobs"); + } + } + + private static void dump(String prefix, SparseArray<ArraySet<JobStatus>> jobMap) { + final StringBuilder str = new StringBuilder(); + for (int i = 0; i < jobMap.size(); i++) { + final ArraySet<JobStatus> jobs = jobMap.valueAt(i); + if (jobs == null) return; + str.append("[Key: " + jobMap.keyAt(i) + ", Value: {"); + for (int j = 0; j < jobs.size(); j++) { + final JobStatus job = jobs.valueAt(j); + str.append("(s=" + job.getSourceUid() + ", c=" + job.getUid() + "), "); + } + str.append("}], "); + } + Log.d(TAG, prefix + ": " + str.toString()); + } + + @After + public void tearDown() throws Exception { + LocalServices.removeServiceForTest(PackageManagerInternal.class); + } +} diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/PersistentKeyChainSnapshotTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/PersistentKeyChainSnapshotTest.java new file mode 100644 index 000000000000..aad5295abd75 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/PersistentKeyChainSnapshotTest.java @@ -0,0 +1,335 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.locksettings.recoverablekeystore.storage; + +import static com.google.common.truth.Truth.assertThat; +import static org.testng.Assert.assertThrows; + +import android.security.keystore.recovery.KeyDerivationParams; +import android.security.keystore.recovery.WrappedApplicationKey; +import android.security.keystore.recovery.KeyChainSnapshot; +import android.security.keystore.recovery.KeyChainProtectionParams; + +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; + +@SmallTest +@RunWith(AndroidJUnit4.class) +public class PersistentKeyChainSnapshotTest { + + private static final String ALIAS = "some_key"; + private static final String ALIAS2 = "another_key"; + private static final byte[] RECOVERY_KEY_MATERIAL = "recovery_key_data" + .getBytes(StandardCharsets.UTF_8); + private static final byte[] KEY_MATERIAL = "app_key_data".getBytes(StandardCharsets.UTF_8); + private static final byte[] PUBLIC_KEY = "public_key_data".getBytes(StandardCharsets.UTF_8); + private static final byte[] ACCOUNT = "test_account".getBytes(StandardCharsets.UTF_8); + private static final byte[] SALT = "salt".getBytes(StandardCharsets.UTF_8); + private static final int SNAPSHOT_VERSION = 2; + private static final int MAX_ATTEMPTS = 10; + private static final long COUNTER_ID = 123456789L; + private static final byte[] SERVER_PARAMS = "server_params".getBytes(StandardCharsets.UTF_8); + private static final byte[] ZERO_BYTES = new byte[0]; + private static final byte[] ONE_BYTE = new byte[]{(byte) 11}; + private static final byte[] TWO_BYTES = new byte[]{(byte) 222,(byte) 222}; + + @Test + public void testWriteInt() throws Exception { + PersistentKeyChainSnapshot writer = new PersistentKeyChainSnapshot(); + writer.initWriter(); + writer.writeInt(Integer.MIN_VALUE); + writer.writeInt(Integer.MAX_VALUE); + byte[] result = writer.getOutput(); + + PersistentKeyChainSnapshot reader = new PersistentKeyChainSnapshot(); + reader.initReader(result); + assertThat(reader.readInt()).isEqualTo(Integer.MIN_VALUE); + assertThat(reader.readInt()).isEqualTo(Integer.MAX_VALUE); + + assertThrows( + IOException.class, + () -> reader.readInt()); + } + + @Test + public void testWriteLong() throws Exception { + PersistentKeyChainSnapshot writer = new PersistentKeyChainSnapshot(); + writer.initWriter(); + writer.writeLong(Long.MIN_VALUE); + writer.writeLong(Long.MAX_VALUE); + byte[] result = writer.getOutput(); + + PersistentKeyChainSnapshot reader = new PersistentKeyChainSnapshot(); + reader.initReader(result); + assertThat(reader.readLong()).isEqualTo(Long.MIN_VALUE); + assertThat(reader.readLong()).isEqualTo(Long.MAX_VALUE); + + assertThrows( + IOException.class, + () -> reader.readLong()); + } + + @Test + public void testWriteBytes() throws Exception { + PersistentKeyChainSnapshot writer = new PersistentKeyChainSnapshot(); + writer.initWriter(); + writer.writeBytes(ZERO_BYTES); + writer.writeBytes(ONE_BYTE); + writer.writeBytes(TWO_BYTES); + byte[] result = writer.getOutput(); + + PersistentKeyChainSnapshot reader = new PersistentKeyChainSnapshot(); + reader.initReader(result); + assertThat(reader.readBytes()).isEqualTo(ZERO_BYTES); + assertThat(reader.readBytes()).isEqualTo(ONE_BYTE); + assertThat(reader.readBytes()).isEqualTo(TWO_BYTES); + + assertThrows( + IOException.class, + () -> reader.readBytes()); + } + + @Test + public void testReadBytes_returnsNullArrayAsEmpty() throws Exception { + PersistentKeyChainSnapshot writer = new PersistentKeyChainSnapshot(); + writer.initWriter(); + writer.writeBytes(null); + byte[] result = writer.getOutput(); + + PersistentKeyChainSnapshot reader = new PersistentKeyChainSnapshot(); + reader.initReader(result); + assertThat(reader.readBytes()).isEqualTo(new byte[]{}); // null -> empty array + } + + @Test + public void testWriteKeyEntry() throws Exception { + PersistentKeyChainSnapshot writer = new PersistentKeyChainSnapshot(); + writer.initWriter(); + WrappedApplicationKey entry = new WrappedApplicationKey.Builder() + .setAlias(ALIAS) + .setEncryptedKeyMaterial(KEY_MATERIAL) + .setAccount(ACCOUNT) + .build(); + writer.writeKeyEntry(entry); + + byte[] result = writer.getOutput(); + + PersistentKeyChainSnapshot reader = new PersistentKeyChainSnapshot(); + reader.initReader(result); + + WrappedApplicationKey copy = reader.readKeyEntry(); + assertThat(copy.getAlias()).isEqualTo(ALIAS); + assertThat(copy.getEncryptedKeyMaterial()).isEqualTo(KEY_MATERIAL); + assertThat(copy.getAccount()).isEqualTo(ACCOUNT); + + assertThrows( + IOException.class, + () -> reader.readKeyEntry()); + } + + public void testWriteProtectionParams() throws Exception { + PersistentKeyChainSnapshot writer = new PersistentKeyChainSnapshot(); + writer.initWriter(); + KeyDerivationParams derivationParams = KeyDerivationParams.createSha256Params(SALT); + KeyChainProtectionParams protectionParams = new KeyChainProtectionParams.Builder() + .setUserSecretType(1) + .setLockScreenUiFormat(2) + .setKeyDerivationParams(derivationParams) + .build(); + writer.writeProtectionParams(protectionParams); + + byte[] result = writer.getOutput(); + + PersistentKeyChainSnapshot reader = new PersistentKeyChainSnapshot(); + reader.initReader(result); + + KeyChainProtectionParams copy = reader.readProtectionParams(); + assertThat(copy.getUserSecretType()).isEqualTo(1); + assertThat(copy.getLockScreenUiFormat()).isEqualTo(2); + assertThat(copy.getKeyDerivationParams().getSalt()).isEqualTo(SALT); + + assertThrows( + IOException.class, + () -> reader.readProtectionParams()); + } + + public void testKeyChainSnapshot() throws Exception { + PersistentKeyChainSnapshot writer = new PersistentKeyChainSnapshot(); + writer.initWriter(); + + KeyDerivationParams derivationParams = KeyDerivationParams.createSha256Params(SALT); + + ArrayList<KeyChainProtectionParams> protectionParamsList = new ArrayList<>(); + protectionParamsList.add(new KeyChainProtectionParams.Builder() + .setUserSecretType(1) + .setLockScreenUiFormat(2) + .setKeyDerivationParams(derivationParams) + .build()); + + ArrayList<WrappedApplicationKey> appKeysList = new ArrayList<>(); + appKeysList.add(new WrappedApplicationKey.Builder() + .setAlias(ALIAS) + .setEncryptedKeyMaterial(KEY_MATERIAL) + .setAccount(ACCOUNT) + .build()); + + KeyChainSnapshot snapshot = new KeyChainSnapshot.Builder() + .setSnapshotVersion(SNAPSHOT_VERSION) + .setKeyChainProtectionParams(protectionParamsList) + .setEncryptedRecoveryKeyBlob(KEY_MATERIAL) + .setWrappedApplicationKeys(appKeysList) + .setMaxAttempts(MAX_ATTEMPTS) + .setCounterId(COUNTER_ID) + .setServerParams(SERVER_PARAMS) + .setTrustedHardwarePublicKey(PUBLIC_KEY) + .build(); + + writer.writeKeyChainSnapshot(snapshot); + + byte[] result = writer.getOutput(); + + PersistentKeyChainSnapshot reader = new PersistentKeyChainSnapshot(); + reader.initReader(result); + + KeyChainSnapshot copy = reader.readKeyChainSnapshot(); + assertThat(copy.getSnapshotVersion()).isEqualTo(SNAPSHOT_VERSION); + assertThat(copy.getKeyChainProtectionParams()).hasSize(2); + assertThat(copy.getKeyChainProtectionParams().get(0).getUserSecretType()).isEqualTo(1); + assertThat(copy.getKeyChainProtectionParams().get(1).getUserSecretType()).isEqualTo(2); + assertThat(copy.getEncryptedRecoveryKeyBlob()).isEqualTo(RECOVERY_KEY_MATERIAL); + assertThat(copy.getWrappedApplicationKeys()).hasSize(2); + assertThat(copy.getWrappedApplicationKeys().get(0).getAlias()).isEqualTo(ALIAS); + assertThat(copy.getWrappedApplicationKeys().get(1).getAlias()).isEqualTo(ALIAS2); + assertThat(copy.getMaxAttempts()).isEqualTo(MAX_ATTEMPTS); + assertThat(copy.getCounterId()).isEqualTo(COUNTER_ID); + assertThat(copy.getServerParams()).isEqualTo(SERVER_PARAMS); + assertThat(copy.getTrustedHardwarePublicKey()).isEqualTo(PUBLIC_KEY); + + assertThrows( + IOException.class, + () -> reader.readKeyChainSnapshot()); + + verifyDeserialize(snapshot); + } + + public void testKeyChainSnapshot_withManyKeysAndProtectionParams() throws Exception { + PersistentKeyChainSnapshot writer = new PersistentKeyChainSnapshot(); + writer.initWriter(); + + KeyDerivationParams derivationParams = KeyDerivationParams.createSha256Params(SALT); + + ArrayList<KeyChainProtectionParams> protectionParamsList = new ArrayList<>(); + protectionParamsList.add(new KeyChainProtectionParams.Builder() + .setUserSecretType(1) + .setLockScreenUiFormat(2) + .setKeyDerivationParams(derivationParams) + .build()); + protectionParamsList.add(new KeyChainProtectionParams.Builder() + .setUserSecretType(2) + .setLockScreenUiFormat(3) + .setKeyDerivationParams(derivationParams) + .build()); + ArrayList<WrappedApplicationKey> appKeysList = new ArrayList<>(); + appKeysList.add(new WrappedApplicationKey.Builder() + .setAlias(ALIAS) + .setEncryptedKeyMaterial(KEY_MATERIAL) + .setAccount(ACCOUNT) + .build()); + appKeysList.add(new WrappedApplicationKey.Builder() + .setAlias(ALIAS2) + .setEncryptedKeyMaterial(KEY_MATERIAL) + .setAccount(ACCOUNT) + .build()); + + + KeyChainSnapshot snapshot = new KeyChainSnapshot.Builder() + .setSnapshotVersion(SNAPSHOT_VERSION) + .setKeyChainProtectionParams(protectionParamsList) + .setEncryptedRecoveryKeyBlob(KEY_MATERIAL) + .setWrappedApplicationKeys(appKeysList) + .setMaxAttempts(MAX_ATTEMPTS) + .setCounterId(COUNTER_ID) + .setServerParams(SERVER_PARAMS) + .setTrustedHardwarePublicKey(PUBLIC_KEY) + .build(); + + writer.writeKeyChainSnapshot(snapshot); + + byte[] result = writer.getOutput(); + + PersistentKeyChainSnapshot reader = new PersistentKeyChainSnapshot(); + reader.initReader(result); + + KeyChainSnapshot copy = reader.readKeyChainSnapshot(); + assertThat(copy.getSnapshotVersion()).isEqualTo(SNAPSHOT_VERSION); + assertThat(copy.getKeyChainProtectionParams().get(0).getUserSecretType()).isEqualTo(1); + assertThat(copy.getEncryptedRecoveryKeyBlob()).isEqualTo(RECOVERY_KEY_MATERIAL); + assertThat(copy.getWrappedApplicationKeys().get(0).getAlias()).isEqualTo(ALIAS); + assertThat(copy.getMaxAttempts()).isEqualTo(MAX_ATTEMPTS); + assertThat(copy.getCounterId()).isEqualTo(COUNTER_ID); + assertThat(copy.getServerParams()).isEqualTo(SERVER_PARAMS); + assertThat(copy.getTrustedHardwarePublicKey()).isEqualTo(PUBLIC_KEY); + + assertThrows( + IOException.class, + () -> reader.readKeyChainSnapshot()); + + verifyDeserialize(snapshot); + } + + private void verifyDeserialize(KeyChainSnapshot snapshot) throws Exception { + byte[] serialized = PersistentKeyChainSnapshot.serialize(snapshot); + KeyChainSnapshot copy = PersistentKeyChainSnapshot.deserialize(serialized); + assertThat(copy.getSnapshotVersion()) + .isEqualTo(snapshot.getSnapshotVersion()); + assertThat(copy.getKeyChainProtectionParams().size()) + .isEqualTo(copy.getKeyChainProtectionParams().size()); + assertThat(copy.getEncryptedRecoveryKeyBlob()) + .isEqualTo(snapshot.getEncryptedRecoveryKeyBlob()); + assertThat(copy.getWrappedApplicationKeys().size()) + .isEqualTo(snapshot.getWrappedApplicationKeys().size()); + assertThat(copy.getMaxAttempts()).isEqualTo(snapshot.getMaxAttempts()); + assertThat(copy.getCounterId()).isEqualTo(snapshot.getCounterId()); + assertThat(copy.getServerParams()).isEqualTo(snapshot.getServerParams()); + assertThat(copy.getTrustedHardwarePublicKey()) + .isEqualTo(snapshot.getTrustedHardwarePublicKey()); + } + + + public void testDeserialize_failsForNewerVersion() throws Exception { + byte[] newVersion = new byte[]{(byte) 2, (byte) 0, (byte) 0, (byte) 0}; + assertThrows( + IOException.class, + () -> PersistentKeyChainSnapshot.deserialize(newVersion)); + } + + public void testDeserialize_failsForEmptyData() throws Exception { + byte[] empty = new byte[]{}; + assertThrows( + IOException.class, + () -> PersistentKeyChainSnapshot.deserialize(empty)); + } + +} + diff --git a/services/tests/servicestests/src/com/android/server/power/batterysaver/BatterySavingStatsTest.java b/services/tests/servicestests/src/com/android/server/power/batterysaver/BatterySavingStatsTest.java new file mode 100644 index 000000000000..c3714c85e895 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/power/batterysaver/BatterySavingStatsTest.java @@ -0,0 +1,310 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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.power.batterysaver; + +import static org.junit.Assert.assertEquals; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; + +import com.android.internal.logging.MetricsLogger; +import com.android.server.power.batterysaver.BatterySavingStats.BatterySaverState; +import com.android.server.power.batterysaver.BatterySavingStats.DozeState; +import com.android.server.power.batterysaver.BatterySavingStats.InteractiveState; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.io.ByteArrayOutputStream; +import java.io.PrintWriter; + +/** + atest $ANDROID_BUILD_TOP/frameworks/base/services/tests/servicestests/src/com/android/server/power/batterysaver/BatterySavingStatsTest.java + */ +@SmallTest +@RunWith(AndroidJUnit4.class) +public class BatterySavingStatsTest { + private class BatterySavingStatsTestable extends BatterySavingStats { + private long mTime = 1_000_000; // Some random starting time. + + private int mBatteryLevel = 1_000_000_000; + + private BatterySavingStatsTestable() { + super(mMetricsLogger); + } + + @Override + long injectCurrentTime() { + return mTime; + } + + @Override + int injectBatteryLevel() { + return mBatteryLevel; + } + + void assertDumpable() { + final ByteArrayOutputStream out = new ByteArrayOutputStream(); + dump(new PrintWriter(out), ""); // Just make sure it won't crash. + } + + void advanceClock(int minutes) { + mTime += 60_000 * minutes; + } + + void drainBattery(int value) { + mBatteryLevel -= value; + if (mBatteryLevel < 0) { + mBatteryLevel = 0; + } + } + + String toDebugString() { + final StringBuilder sb = new StringBuilder(); + String sep = ""; + for (int i = 0; i < mStats.size(); i++) { + sb.append(sep); + sb.append(stateToString(mStats.keyAt(i))); + sb.append(":"); + sb.append(mStats.valueAt(i).toStringForTest()); + sep = "\n"; + } + return sb.toString(); + } + } + + public MetricsLogger mMetricsLogger = mock(MetricsLogger.class); + + @Test + public void testAll() { + final BatterySavingStatsTestable target = new BatterySavingStatsTestable(); + + target.assertDumpable(); + + target.advanceClock(1); + target.drainBattery(2); + + target.transitionState( + BatterySaverState.OFF, + InteractiveState.INTERACTIVE, + DozeState.NOT_DOZING); + + target.advanceClock(4); + target.drainBattery(1); + + target.transitionState( + BatterySaverState.OFF, + InteractiveState.NON_INTERACTIVE, + DozeState.NOT_DOZING); + + target.advanceClock(2); + target.drainBattery(5); + + target.transitionState( + BatterySaverState.OFF, + InteractiveState.INTERACTIVE, + DozeState.NOT_DOZING); + + target.advanceClock(4); + target.drainBattery(1); + + target.transitionState( + BatterySaverState.OFF, + InteractiveState.NON_INTERACTIVE, + DozeState.NOT_DOZING); + + target.advanceClock(2); + target.drainBattery(5); + + target.transitionState( + BatterySaverState.OFF, + InteractiveState.INTERACTIVE, + DozeState.NOT_DOZING); + + target.advanceClock(3); + target.drainBattery(1); + + target.transitionState( + BatterySaverState.OFF, + InteractiveState.NON_INTERACTIVE, + DozeState.LIGHT); + + target.advanceClock(5); + target.drainBattery(1); + + target.transitionState( + BatterySaverState.OFF, + InteractiveState.NON_INTERACTIVE, + DozeState.DEEP); + + target.advanceClock(1); + target.drainBattery(2); + + target.transitionState( + BatterySaverState.ON, + InteractiveState.INTERACTIVE, + DozeState.NOT_DOZING); + + target.advanceClock(1); + target.drainBattery(3); + + target.transitionState( + BatterySaverState.OFF, + InteractiveState.INTERACTIVE, + DozeState.NOT_DOZING); + + target.advanceClock(3); + target.drainBattery(5); + + target.transitionState( + BatterySaverState.ON, + InteractiveState.INTERACTIVE, + DozeState.NOT_DOZING); + + target.advanceClock(3); + target.drainBattery(5); + + target.startCharging(); + + target.advanceClock(5); + target.drainBattery(10); + + target.transitionState( + BatterySaverState.ON, + InteractiveState.INTERACTIVE, + DozeState.NOT_DOZING); + + target.advanceClock(5); + target.drainBattery(1); + + target.startCharging(); + + target.assertDumpable(); + + assertEquals( + "BS=0,I=0,D=0:{4m,10,150.00}\n" + + "BS=1,I=0,D=0:{0m,0,0.00}\n" + + "BS=0,I=1,D=0:{14m,8,34.29}\n" + + "BS=1,I=1,D=0:{9m,9,60.00}\n" + + "BS=0,I=0,D=1:{5m,1,12.00}\n" + + "BS=1,I=0,D=1:{0m,0,0.00}\n" + + "BS=0,I=1,D=1:{0m,0,0.00}\n" + + "BS=1,I=1,D=1:{0m,0,0.00}\n" + + "BS=0,I=0,D=2:{1m,2,120.00}\n" + + "BS=1,I=0,D=2:{0m,0,0.00}\n" + + "BS=0,I=1,D=2:{0m,0,0.00}\n" + + "BS=1,I=1,D=2:{0m,0,0.00}", + target.toDebugString()); + } + + private void assertMetricsLog(String counter, int value) { + verify(mMetricsLogger, times(1)).count(eq(counter), eq(value)); + } + + @Test + public void testMetricsLogger() { + final BatterySavingStatsTestable target = new BatterySavingStatsTestable(); + + target.advanceClock(1); + target.drainBattery(1000); + + target.transitionState( + BatterySaverState.OFF, + InteractiveState.INTERACTIVE, + DozeState.NOT_DOZING); + + verify(mMetricsLogger, times(0)).count(anyString(), anyInt()); + + target.advanceClock(1); + target.drainBattery(2000); + + reset(mMetricsLogger); + target.transitionState( + BatterySaverState.OFF, + InteractiveState.NON_INTERACTIVE, + DozeState.NOT_DOZING); + + assertMetricsLog(BatterySavingStats.COUNTER_POWER_MILLIAMPS_PREFIX + "01", 2); + assertMetricsLog(BatterySavingStats.COUNTER_TIME_SECONDS_PREFIX + "01", 60); + + target.advanceClock(1); + target.drainBattery(2000); + + reset(mMetricsLogger); + target.transitionState( + BatterySaverState.OFF, + InteractiveState.NON_INTERACTIVE, + DozeState.DEEP); + + target.advanceClock(1); + target.drainBattery(2000); + + verify(mMetricsLogger, times(0)).count(anyString(), anyInt()); + + target.transitionState( + BatterySaverState.OFF, + InteractiveState.NON_INTERACTIVE, + DozeState.LIGHT); + + target.advanceClock(1); + target.drainBattery(2000); + + verify(mMetricsLogger, times(0)).count(anyString(), anyInt()); + + target.transitionState( + BatterySaverState.ON, + InteractiveState.INTERACTIVE, + DozeState.NOT_DOZING); + + assertMetricsLog(BatterySavingStats.COUNTER_POWER_MILLIAMPS_PREFIX + "00", 2 * 3); + assertMetricsLog(BatterySavingStats.COUNTER_TIME_SECONDS_PREFIX + "00", 60 * 3); + + target.advanceClock(10); + target.drainBattery(10_000); + + reset(mMetricsLogger); + target.startCharging(); + + assertMetricsLog(BatterySavingStats.COUNTER_POWER_MILLIAMPS_PREFIX + "11", 10); + assertMetricsLog(BatterySavingStats.COUNTER_TIME_SECONDS_PREFIX + "11", 60 * 10); + + target.advanceClock(1); + target.drainBattery(2000); + + reset(mMetricsLogger); + target.transitionState( + BatterySaverState.ON, + InteractiveState.NON_INTERACTIVE, + DozeState.NOT_DOZING); + + verify(mMetricsLogger, times(0)).count(anyString(), anyInt()); + + target.advanceClock(1); + target.drainBattery(2000); + + target.startCharging(); + + assertMetricsLog(BatterySavingStats.COUNTER_POWER_MILLIAMPS_PREFIX + "10", 2); + assertMetricsLog(BatterySavingStats.COUNTER_TIME_SECONDS_PREFIX + "10", 60); + } +} diff --git a/services/tests/servicestests/src/com/android/server/wm/DimmerTests.java b/services/tests/servicestests/src/com/android/server/wm/DimmerTests.java index 70906dfbdb14..396fef40466f 100644 --- a/services/tests/servicestests/src/com/android/server/wm/DimmerTests.java +++ b/services/tests/servicestests/src/com/android/server/wm/DimmerTests.java @@ -16,11 +16,14 @@ package com.android.server.wm; -import java.util.HashMap; - -import org.junit.Test; -import org.junit.Before; -import org.junit.runner.RunWith; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; import android.graphics.Rect; import android.platform.test.annotations.Presubmit; @@ -28,22 +31,25 @@ import android.support.test.runner.AndroidJUnit4; import android.view.SurfaceControl; import android.view.SurfaceSession; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertEquals; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.any; -import static org.mockito.Mockito.eq; +import org.junit.Before; +import org.junit.Ignore; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.stubbing.Answer; /** * Build/Install/Run: - * bit FrameworksServicesTests:com.android.server.wm.DimmerTests; + * atest FrameworksServicesTests:com.android.server.wm.DimmerTests; */ @Presubmit +@Ignore("b/72450130") @RunWith(AndroidJUnit4.class) public class DimmerTests extends WindowTestsBase { + + public DimmerTests() { + super(spy(new SurfaceAnimationRunner())); + } + private class TestWindowContainer extends WindowContainer<TestWindowContainer> { final SurfaceControl mControl = mock(SurfaceControl.class); final SurfaceControl.Transaction mTransaction = mock(SurfaceControl.Transaction.class); @@ -65,12 +71,14 @@ public class DimmerTests extends WindowTestsBase { private class MockSurfaceBuildingContainer extends WindowContainer<TestWindowContainer> { final SurfaceSession mSession = new SurfaceSession(); - SurfaceControl mBuiltSurface = null; - final SurfaceControl mHostControl = mock(SurfaceControl.class); final SurfaceControl.Transaction mHostTransaction = mock(SurfaceControl.Transaction.class); MockSurfaceBuildingContainer() { super(sWm); + mSurfaceControl = sWm.makeSurfaceBuilder(mSession) + .setName("test surface") + .setSize(1, 1) + .build(); } class MockSurfaceBuilder extends SurfaceControl.Builder { @@ -80,21 +88,23 @@ public class DimmerTests extends WindowTestsBase { @Override public SurfaceControl build() { - SurfaceControl sc = mock(SurfaceControl.class); - mBuiltSurface = sc; - return sc; + return spy(sWm.makeSurfaceBuilder(mSession) + .setName("test surface") + .setSize(1, 1) + .build()); } } - @Override - SurfaceControl.Builder makeChildSurface(WindowContainer child) { - return new MockSurfaceBuilder(mSession); + SurfaceControl.Builder makeSurface() { + return sWm.makeSurfaceBuilder(mSession) + .setName("test surface") + .setSize(1, 1); } @Override - public SurfaceControl getSurfaceControl() { - return mHostControl; + SurfaceControl.Builder makeChildSurface(WindowContainer child) { + return new MockSurfaceBuilder(mSession); } @Override @@ -114,29 +124,37 @@ public class DimmerTests extends WindowTestsBase { mTransaction = mock(SurfaceControl.Transaction.class); mDimmer = new Dimmer(mHost); + + doAnswer((Answer<Void>) invocation -> { + Runnable runnable = invocation.getArgument(3); + runnable.run(); + return null; + }).when(sWm.mSurfaceAnimationRunner).startAnimation(any(), any(), any(), any()); } @Test public void testDimAboveNoChildCreatesSurface() throws Exception { final float alpha = 0.8f; mDimmer.dimAbove(mTransaction, alpha); - assertNotNull("Dimmer should have created a surface", mHost.mBuiltSurface); - verify(mTransaction).setAlpha(mHost.mBuiltSurface, alpha); - verify(mTransaction).show(mHost.mBuiltSurface); - verify(mTransaction).setLayer(mHost.mBuiltSurface, Integer.MAX_VALUE); + SurfaceControl dimLayer = getDimLayer(null); + + assertNotNull("Dimmer should have created a surface", dimLayer); + + verify(mTransaction).setAlpha(dimLayer, alpha); + verify(mTransaction).setLayer(dimLayer, Integer.MAX_VALUE); } @Test public void testDimAboveNoChildRedundantlyUpdatesAlphaOnExistingSurface() throws Exception { float alpha = 0.8f; mDimmer.dimAbove(mTransaction, alpha); - final SurfaceControl firstSurface = mHost.mBuiltSurface; + final SurfaceControl firstSurface = getDimLayer(null); alpha = 0.9f; mDimmer.dimAbove(mTransaction, alpha); - assertEquals(firstSurface, mHost.mBuiltSurface); + assertEquals(firstSurface, getDimLayer(null)); verify(mTransaction).setAlpha(firstSurface, 0.9f); } @@ -148,16 +166,20 @@ public class DimmerTests extends WindowTestsBase { int height = 300; Rect bounds = new Rect(0, 0, width, height); mDimmer.updateDims(mTransaction, bounds); - verify(mTransaction).setSize(mHost.mBuiltSurface, width, height); + + verify(mTransaction).setSize(getDimLayer(null), width, height); + verify(mTransaction).show(getDimLayer(null)); } @Test public void testDimAboveNoChildNotReset() throws Exception { mDimmer.dimAbove(mTransaction, 0.8f); + SurfaceControl dimLayer = getDimLayer(null); mDimmer.resetDimStates(); mDimmer.updateDims(mTransaction, new Rect()); - verify(mHost.mBuiltSurface, never()).destroy(); + verify(mTransaction).show(getDimLayer(null)); + verify(dimLayer, never()).destroy(); } @Test @@ -167,11 +189,12 @@ public class DimmerTests extends WindowTestsBase { final float alpha = 0.8f; mDimmer.dimAbove(mTransaction, child, alpha); - assertNotNull("Dimmer should have created a surface", mHost.mBuiltSurface); + SurfaceControl mDimLayer = getDimLayer(child); + + assertNotNull("Dimmer should have created a surface", mDimLayer); - verify(mTransaction).setAlpha(mHost.mBuiltSurface, alpha); - verify(mTransaction).show(mHost.mBuiltSurface); - verify(mTransaction).setRelativeLayer(mHost.mBuiltSurface, child.mControl, 1); + verify(mTransaction).setAlpha(mDimLayer, alpha); + verify(mTransaction).setRelativeLayer(mDimLayer, child.mControl, 1); } @Test @@ -181,11 +204,12 @@ public class DimmerTests extends WindowTestsBase { final float alpha = 0.8f; mDimmer.dimBelow(mTransaction, child, alpha); - assertNotNull("Dimmer should have created a surface", mHost.mBuiltSurface); + SurfaceControl mDimLayer = getDimLayer(child); - verify(mTransaction).setAlpha(mHost.mBuiltSurface, alpha); - verify(mTransaction).show(mHost.mBuiltSurface); - verify(mTransaction).setRelativeLayer(mHost.mBuiltSurface, child.mControl, -1); + assertNotNull("Dimmer should have created a surface", mDimLayer); + + verify(mTransaction).setAlpha(mDimLayer, alpha); + verify(mTransaction).setRelativeLayer(mDimLayer, child.mControl, -1); } @Test @@ -195,9 +219,11 @@ public class DimmerTests extends WindowTestsBase { final float alpha = 0.8f; mDimmer.dimAbove(mTransaction, child, alpha); + SurfaceControl dimLayer = getDimLayer(child); mDimmer.resetDimStates(); + mDimmer.updateDims(mTransaction, new Rect()); - verify(mHost.mBuiltSurface).destroy(); + verify(dimLayer).destroy(); } @Test @@ -207,10 +233,16 @@ public class DimmerTests extends WindowTestsBase { final float alpha = 0.8f; mDimmer.dimAbove(mTransaction, child, alpha); + SurfaceControl dimLayer = getDimLayer(child); mDimmer.resetDimStates(); mDimmer.dimAbove(mTransaction, child, alpha); mDimmer.updateDims(mTransaction, new Rect()); - verify(mHost.mBuiltSurface, never()).destroy(); + verify(mTransaction).show(dimLayer); + verify(dimLayer, never()).destroy(); + } + + private SurfaceControl getDimLayer(WindowContainer windowContainer) { + return mDimmer.mDimLayerUsers.get(windowContainer).mDimLayer; } } diff --git a/services/tests/servicestests/src/com/android/server/wm/RemoteAnimationControllerTest.java b/services/tests/servicestests/src/com/android/server/wm/RemoteAnimationControllerTest.java index 897be34e36fe..f860195bd6ea 100644 --- a/services/tests/servicestests/src/com/android/server/wm/RemoteAnimationControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/wm/RemoteAnimationControllerTest.java @@ -20,6 +20,7 @@ import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION; import static org.junit.Assert.assertEquals; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyZeroInteractions; import android.graphics.Point; import android.graphics.Rect; @@ -134,4 +135,10 @@ public class RemoteAnimationControllerTest extends WindowTestsBase { verify(mMockRunner).onAnimationCancelled(); verify(mFinishedCallback).onAnimationFinished(eq(adapter)); } + + @Test + public void testZeroAnimations() throws Exception { + mController.goodToGo(); + verifyZeroInteractions(mMockRunner); + } } diff --git a/services/tests/servicestests/src/com/android/server/wm/TestWindowManagerPolicy.java b/services/tests/servicestests/src/com/android/server/wm/TestWindowManagerPolicy.java index 35ca493e909f..81fd889c82c2 100644 --- a/services/tests/servicestests/src/com/android/server/wm/TestWindowManagerPolicy.java +++ b/services/tests/servicestests/src/com/android/server/wm/TestWindowManagerPolicy.java @@ -27,6 +27,7 @@ import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.any; import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.spy; import android.os.PowerSaveState; import android.util.proto.ProtoOutputStream; @@ -68,6 +69,11 @@ class TestWindowManagerPolicy implements WindowManagerPolicy { private Runnable mRunnableWhenAddingSplashScreen; static synchronized WindowManagerService getWindowManagerService(Context context) { + return getWindowManagerService(context, new SurfaceAnimationRunner()); + } + + static synchronized WindowManagerService getWindowManagerService(Context context, + SurfaceAnimationRunner surfaceAnimationRunner) { if (sWm == null) { // We only want to do this once for the test process as we don't want WM to try to // register a bunch of local services again. @@ -105,7 +111,7 @@ class TestWindowManagerPolicy implements WindowManagerPolicy { } sWm = WindowManagerService.main(context, ims, true, false, - false, new TestWindowManagerPolicy()); + false, new TestWindowManagerPolicy(), surfaceAnimationRunner); } return sWm; } diff --git a/services/tests/servicestests/src/com/android/server/wm/WindowContainerTests.java b/services/tests/servicestests/src/com/android/server/wm/WindowContainerTests.java index 196b4a99d02d..1bd9a933885b 100644 --- a/services/tests/servicestests/src/com/android/server/wm/WindowContainerTests.java +++ b/services/tests/servicestests/src/com/android/server/wm/WindowContainerTests.java @@ -20,6 +20,7 @@ import org.junit.Test; import org.junit.runner.RunWith; import android.content.res.Configuration; +import android.graphics.Rect; import android.platform.test.annotations.Presubmit; import android.support.test.filters.SmallTest; import android.support.test.runner.AndroidJUnit4; @@ -41,11 +42,16 @@ import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + /** * Test class for {@link WindowContainer}. * * Build/Install/Run: - * bit FrameworksServicesTests:com.android.server.wm.WindowContainerTests + * atest FrameworksServicesTests:com.android.server.wm.WindowContainerTests */ @SmallTest @Presubmit @@ -644,6 +650,37 @@ public class WindowContainerTests extends WindowTestsBase { assertEquals(1, child2.getPrefixOrderIndex()); } + /** + * Ensure children of a {@link WindowContainer} do not have + * {@link WindowContainer#onParentResize()} called when {@link WindowContainer#onParentResize()} + * is invoked with overridden bounds. + */ + @Test + public void testOnParentResizePropagation() throws Exception { + final TestWindowContainerBuilder builder = new TestWindowContainerBuilder(); + final TestWindowContainer root = builder.build(); + + final TestWindowContainer child = root.addChildWindow(); + child.setBounds(new Rect(1,1,2,2)); + + final TestWindowContainer grandChild = mock(TestWindowContainer.class); + + child.addChildWindow(grandChild); + root.onResize(); + + // Make sure the child does not propagate resize through onParentResize when bounds are set. + verify(grandChild, never()).onParentResize(); + + child.removeChild(grandChild); + + child.setBounds(null); + child.addChildWindow(grandChild); + root.onResize(); + + // Make sure the child propagates resize through onParentResize when no bounds set. + verify(grandChild, times(1)).onParentResize(); + } + /* Used so we can gain access to some protected members of the {@link WindowContainer} class */ private class TestWindowContainer extends WindowContainer<TestWindowContainer> { private final int mLayer; diff --git a/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java index 69b13787ef93..7918901f7e2d 100644 --- a/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java +++ b/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java @@ -84,6 +84,16 @@ class WindowTestsBase { WindowState mChildAppWindowBelow; HashSet<WindowState> mCommonWindows; + private final SurfaceAnimationRunner mSurfaceAnimationRunner; + + public WindowTestsBase() { + this(new SurfaceAnimationRunner()); + } + + public WindowTestsBase(SurfaceAnimationRunner surfaceAnimationRunner) { + mSurfaceAnimationRunner = surfaceAnimationRunner; + } + @Before public void setUp() throws Exception { if (!sOneTimeSetupDone) { @@ -98,7 +108,7 @@ class WindowTestsBase { final Context context = InstrumentationRegistry.getTargetContext(); AttributeCache.init(context); - sWm = TestWindowManagerPolicy.getWindowManagerService(context); + sWm = TestWindowManagerPolicy.getWindowManagerService(context, mSurfaceAnimationRunner); beforeCreateDisplay(); context.getDisplay().getDisplayInfo(mDisplayInfo); diff --git a/services/tests/uiservicestests/src/com/android/server/slice/PinnedSliceStateTest.java b/services/tests/uiservicestests/src/com/android/server/slice/PinnedSliceStateTest.java index d3bb80485dfb..cfd155e81a67 100644 --- a/services/tests/uiservicestests/src/com/android/server/slice/PinnedSliceStateTest.java +++ b/services/tests/uiservicestests/src/com/android/server/slice/PinnedSliceStateTest.java @@ -1,5 +1,7 @@ package com.android.server.slice; +import static android.content.pm.PackageManager.PERMISSION_GRANTED; + import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -11,7 +13,9 @@ import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.clearInvocations; +import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -20,6 +24,7 @@ import android.app.slice.Slice; import android.app.slice.SliceProvider; import android.app.slice.SliceSpec; import android.content.ContentProvider; +import android.content.Context; import android.content.IContentProvider; import android.net.Uri; import android.os.Binder; @@ -70,8 +75,8 @@ public class PinnedSliceStateTest extends UiServiceTestCase { @Before public void setup() { mSliceService = mock(SliceManagerService.class); - when(mSliceService.getLock()).thenReturn(new Object()); when(mSliceService.getContext()).thenReturn(mContext); + when(mSliceService.getLock()).thenReturn(new Object()); when(mSliceService.getHandler()).thenReturn(new Handler(TestableLooper.get(this).getLooper())); mContentProvider = mock(ContentProvider.class); mIContentProvider = mock(IContentProvider.class); @@ -99,8 +104,11 @@ public class PinnedSliceStateTest extends UiServiceTestCase { } @Test - public void testSendPinnedOnCreate() throws RemoteException { - // When created, a pinned message should be sent. + public void testSendPinnedOnPin() throws RemoteException { + TestableLooper.get(this).processAllMessages(); + + // When pinned for the first time, a pinned message should be sent. + mPinnedSliceManager.pin("pkg", FIRST_SPECS); TestableLooper.get(this).processAllMessages(); verify(mIContentProvider).call(anyString(), eq(SliceProvider.METHOD_PIN), eq(null), @@ -111,10 +119,46 @@ public class PinnedSliceStateTest extends UiServiceTestCase { } @Test + public void testSendPinnedOnListen() throws RemoteException { + TestableLooper.get(this).processAllMessages(); + + // When a listener is added for the first time, a pinned message should be sent. + ISliceListener listener = mock(ISliceListener.class); + when(listener.asBinder()).thenReturn(new Binder()); + + mPinnedSliceManager.addSliceListener(listener, mContext.getPackageName(), FIRST_SPECS, + true); + TestableLooper.get(this).processAllMessages(); + + verify(mIContentProvider).call(anyString(), eq(SliceProvider.METHOD_PIN), eq(null), + argThat(b -> { + assertEquals(TEST_URI, b.getParcelable(SliceProvider.EXTRA_BIND_URI)); + return true; + })); + } + + @Test + public void testNoSendPinnedWithoutPermission() throws RemoteException { + TestableLooper.get(this).processAllMessages(); + + // When a listener is added for the first time, a pinned message should be sent. + ISliceListener listener = mock(ISliceListener.class); + when(listener.asBinder()).thenReturn(new Binder()); + + mPinnedSliceManager.addSliceListener(listener, mContext.getPackageName(), FIRST_SPECS, + false); + TestableLooper.get(this).processAllMessages(); + + verify(mIContentProvider, never()).call(anyString(), eq(SliceProvider.METHOD_PIN), eq(null), + any()); + } + + @Test public void testSendUnpinnedOnDestroy() throws RemoteException { TestableLooper.get(this).processAllMessages(); clearInvocations(mIContentProvider); + mPinnedSliceManager.pin("pkg", FIRST_SPECS); mPinnedSliceManager.destroy(); TestableLooper.get(this).processAllMessages(); @@ -127,39 +171,40 @@ public class PinnedSliceStateTest extends UiServiceTestCase { @Test public void testPkgPin() { - assertFalse(mPinnedSliceManager.isPinned()); + assertFalse(mPinnedSliceManager.hasPinOrListener()); mPinnedSliceManager.pin("pkg", FIRST_SPECS); - assertTrue(mPinnedSliceManager.isPinned()); + assertTrue(mPinnedSliceManager.hasPinOrListener()); assertTrue(mPinnedSliceManager.unpin("pkg")); - assertFalse(mPinnedSliceManager.isPinned()); + assertFalse(mPinnedSliceManager.hasPinOrListener()); } @Test public void testMultiPkgPin() { - assertFalse(mPinnedSliceManager.isPinned()); + assertFalse(mPinnedSliceManager.hasPinOrListener()); mPinnedSliceManager.pin("pkg", FIRST_SPECS); - assertTrue(mPinnedSliceManager.isPinned()); + assertTrue(mPinnedSliceManager.hasPinOrListener()); mPinnedSliceManager.pin("pkg2", FIRST_SPECS); assertFalse(mPinnedSliceManager.unpin("pkg")); assertTrue(mPinnedSliceManager.unpin("pkg2")); - assertFalse(mPinnedSliceManager.isPinned()); + assertFalse(mPinnedSliceManager.hasPinOrListener()); } @Test public void testListenerPin() { ISliceListener listener = mock(ISliceListener.class); when(listener.asBinder()).thenReturn(new Binder()); - assertFalse(mPinnedSliceManager.isPinned()); + assertFalse(mPinnedSliceManager.hasPinOrListener()); - mPinnedSliceManager.addSliceListener(listener, mContext.getPackageName(), FIRST_SPECS); - assertTrue(mPinnedSliceManager.isPinned()); + mPinnedSliceManager.addSliceListener(listener, mContext.getPackageName(), FIRST_SPECS, + true); + assertTrue(mPinnedSliceManager.hasPinOrListener()); assertTrue(mPinnedSliceManager.removeSliceListener(listener)); - assertFalse(mPinnedSliceManager.isPinned()); + assertFalse(mPinnedSliceManager.hasPinOrListener()); } @Test @@ -170,15 +215,17 @@ public class PinnedSliceStateTest extends UiServiceTestCase { ISliceListener listener2 = mock(ISliceListener.class); Binder value2 = new Binder(); when(listener2.asBinder()).thenReturn(value2); - assertFalse(mPinnedSliceManager.isPinned()); + assertFalse(mPinnedSliceManager.hasPinOrListener()); - mPinnedSliceManager.addSliceListener(listener, mContext.getPackageName(), FIRST_SPECS); - assertTrue(mPinnedSliceManager.isPinned()); - mPinnedSliceManager.addSliceListener(listener2, mContext.getPackageName(), FIRST_SPECS); + mPinnedSliceManager.addSliceListener(listener, mContext.getPackageName(), FIRST_SPECS, + true); + assertTrue(mPinnedSliceManager.hasPinOrListener()); + mPinnedSliceManager.addSliceListener(listener2, mContext.getPackageName(), FIRST_SPECS, + true); assertFalse(mPinnedSliceManager.removeSliceListener(listener)); assertTrue(mPinnedSliceManager.removeSliceListener(listener2)); - assertFalse(mPinnedSliceManager.isPinned()); + assertFalse(mPinnedSliceManager.hasPinOrListener()); } @Test @@ -187,10 +234,11 @@ public class PinnedSliceStateTest extends UiServiceTestCase { IBinder binder = mock(IBinder.class); when(binder.isBinderAlive()).thenReturn(true); when(listener.asBinder()).thenReturn(binder); - assertFalse(mPinnedSliceManager.isPinned()); + assertFalse(mPinnedSliceManager.hasPinOrListener()); - mPinnedSliceManager.addSliceListener(listener, mContext.getPackageName(), FIRST_SPECS); - assertTrue(mPinnedSliceManager.isPinned()); + mPinnedSliceManager.addSliceListener(listener, mContext.getPackageName(), FIRST_SPECS, + true); + assertTrue(mPinnedSliceManager.hasPinOrListener()); ArgumentCaptor<DeathRecipient> arg = ArgumentCaptor.forClass(DeathRecipient.class); verify(binder).linkToDeath(arg.capture(), anyInt()); @@ -198,23 +246,25 @@ public class PinnedSliceStateTest extends UiServiceTestCase { when(binder.isBinderAlive()).thenReturn(false); arg.getValue().binderDied(); + verify(mSliceService).unlisten(eq(TEST_URI)); verify(mSliceService).removePinnedSlice(eq(TEST_URI)); - assertFalse(mPinnedSliceManager.isPinned()); + assertFalse(mPinnedSliceManager.hasPinOrListener()); } @Test public void testPkgListenerPin() { ISliceListener listener = mock(ISliceListener.class); when(listener.asBinder()).thenReturn(new Binder()); - assertFalse(mPinnedSliceManager.isPinned()); + assertFalse(mPinnedSliceManager.hasPinOrListener()); - mPinnedSliceManager.addSliceListener(listener, mContext.getPackageName(), FIRST_SPECS); - assertTrue(mPinnedSliceManager.isPinned()); + mPinnedSliceManager.addSliceListener(listener, mContext.getPackageName(), FIRST_SPECS, + true); + assertTrue(mPinnedSliceManager.hasPinOrListener()); mPinnedSliceManager.pin("pkg", FIRST_SPECS); assertFalse(mPinnedSliceManager.removeSliceListener(listener)); assertTrue(mPinnedSliceManager.unpin("pkg")); - assertFalse(mPinnedSliceManager.isPinned()); + assertFalse(mPinnedSliceManager.hasPinOrListener()); } @Test @@ -230,9 +280,10 @@ public class PinnedSliceStateTest extends UiServiceTestCase { when(mIContentProvider.call(anyString(), eq(SliceProvider.METHOD_SLICE), eq(null), any())).thenReturn(b); - assertFalse(mPinnedSliceManager.isPinned()); + assertFalse(mPinnedSliceManager.hasPinOrListener()); - mPinnedSliceManager.addSliceListener(listener, mContext.getPackageName(), FIRST_SPECS); + mPinnedSliceManager.addSliceListener(listener, mContext.getPackageName(), FIRST_SPECS, + true); mPinnedSliceManager.onChange(); TestableLooper.get(this).processAllMessages(); @@ -244,4 +295,30 @@ public class PinnedSliceStateTest extends UiServiceTestCase { })); verify(listener).onSliceUpdated(eq(s)); } + + @Test + public void testRecheckPackage() throws RemoteException { + TestableLooper.get(this).processAllMessages(); + + ISliceListener listener = mock(ISliceListener.class); + when(listener.asBinder()).thenReturn(new Binder()); + + mPinnedSliceManager.addSliceListener(listener, mContext.getPackageName(), FIRST_SPECS, + false); + TestableLooper.get(this).processAllMessages(); + + verify(mIContentProvider, never()).call(anyString(), eq(SliceProvider.METHOD_PIN), eq(null), + any()); + + when(mSliceService.checkAccess(any(), any(), anyInt(), anyInt())) + .thenReturn(PERMISSION_GRANTED); + mPinnedSliceManager.recheckPackage(mContext.getPackageName()); + TestableLooper.get(this).processAllMessages(); + + verify(mIContentProvider).call(anyString(), eq(SliceProvider.METHOD_PIN), eq(null), + argThat(b -> { + assertEquals(TEST_URI, b.getParcelable(SliceProvider.EXTRA_BIND_URI)); + return true; + })); + } }
\ No newline at end of file diff --git a/services/tests/uiservicestests/src/com/android/server/slice/SliceFullAccessListTest.java b/services/tests/uiservicestests/src/com/android/server/slice/SliceFullAccessListTest.java new file mode 100644 index 000000000000..7c14d083d3c9 --- /dev/null +++ b/services/tests/uiservicestests/src/com/android/server/slice/SliceFullAccessListTest.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.slice; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import android.support.test.filters.SmallTest; +import android.util.Xml.Encoding; + +import com.android.server.UiServiceTestCase; + +import org.junit.Before; +import org.junit.Test; +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; +import org.xmlpull.v1.XmlPullParserFactory; +import org.xmlpull.v1.XmlSerializer; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; + +@SmallTest +public class SliceFullAccessListTest extends UiServiceTestCase { + + private static final String TEST_XML = "<slice-access-list version=\"1\"><user " + + "user=\"0\"><pkg>pkg</pkg><pkg>pkg1</pkg></user><user " + + "user=\"1\"><pkg>pkg</pkg></user><user " + + "user=\"3\"><pkg>pkg2</pkg></user></slice-access-list>"; + + private SliceFullAccessList mAccessList; + + @Before + public void setup() { + mAccessList = new SliceFullAccessList(mContext); + } + + @Test + public void testNoDefaultAccess() { + assertFalse(mAccessList.hasFullAccess("pkg", 0)); + } + + @Test + public void testGrantAccess() { + mAccessList.grantFullAccess("pkg", 0); + assertTrue(mAccessList.hasFullAccess("pkg", 0)); + } + + @Test + public void testUserSeparation() { + mAccessList.grantFullAccess("pkg", 1); + assertFalse(mAccessList.hasFullAccess("pkg", 0)); + } + + @Test + public void testSerialization() throws XmlPullParserException, IOException { + mAccessList.grantFullAccess("pkg", 0); + mAccessList.grantFullAccess("pkg1", 0); + mAccessList.grantFullAccess("pkg", 1); + mAccessList.grantFullAccess("pkg2", 3); + + ByteArrayOutputStream output = new ByteArrayOutputStream(); + XmlSerializer out = XmlPullParserFactory.newInstance().newSerializer(); + out.setOutput(output, Encoding.UTF_8.name()); + mAccessList.writeXml(out); + out.flush(); + + assertEquals(TEST_XML, output.toString(Encoding.UTF_8.name())); + } + + @Test + public void testDeSerialization() throws XmlPullParserException, IOException { + ByteArrayInputStream input = new ByteArrayInputStream(TEST_XML.getBytes()); + XmlPullParser parser = XmlPullParserFactory.newInstance().newPullParser(); + parser.setInput(input, Encoding.UTF_8.name()); + + mAccessList.readXml(parser); + + assertTrue(mAccessList.hasFullAccess("pkg", 0)); + assertTrue(mAccessList.hasFullAccess("pkg1", 0)); + assertTrue(mAccessList.hasFullAccess("pkg", 1)); + assertTrue(mAccessList.hasFullAccess("pkg2", 3)); + + assertFalse(mAccessList.hasFullAccess("pkg3", 0)); + assertFalse(mAccessList.hasFullAccess("pkg1", 1)); + assertFalse(mAccessList.hasFullAccess("pkg", 3)); + assertFalse(mAccessList.hasFullAccess("pkg", 2)); + } +}
\ No newline at end of file diff --git a/services/usb/java/com/android/server/usb/UsbProfileGroupSettingsManager.java b/services/usb/java/com/android/server/usb/UsbProfileGroupSettingsManager.java index 917e651bb425..5f1f5e496de1 100644 --- a/services/usb/java/com/android/server/usb/UsbProfileGroupSettingsManager.java +++ b/services/usb/java/com/android/server/usb/UsbProfileGroupSettingsManager.java @@ -208,7 +208,7 @@ class UsbProfileGroupSettingsManager { mParentUser = user; mSettingsFile = new AtomicFile(new File( Environment.getUserSystemDirectory(user.getIdentifier()), - "usb_device_manager.xml")); + "usb_device_manager.xml"), "usb-state"); mDisablePermissionDialogs = context.getResources().getBoolean( com.android.internal.R.bool.config_disableUsbPermissionDialogs); diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java index 91d86c698f84..a34e9f9481fa 100644 --- a/telephony/java/android/telephony/CarrierConfigManager.java +++ b/telephony/java/android/telephony/CarrierConfigManager.java @@ -27,8 +27,8 @@ import android.os.PersistableBundle; import android.os.RemoteException; import android.os.ServiceManager; import android.service.carrier.CarrierService; +import android.telephony.ims.ImsReasonInfo; -import com.android.ims.ImsReasonInfo; import com.android.internal.telephony.ICarrierConfigLoader; /** @@ -1390,7 +1390,7 @@ public class CarrierConfigManager { "allow_video_calling_fallback_bool"; /** - * Defines operator-specific {@link com.android.ims.ImsReasonInfo} mappings. + * Defines operator-specific {@link ImsReasonInfo} mappings. * * Format: "ORIGINAL_CODE|MESSAGE|NEW_CODE" * Where {@code ORIGINAL_CODE} corresponds to a {@link ImsReasonInfo#getCode()} code, diff --git a/telephony/java/android/telephony/CellSignalStrengthCdma.java b/telephony/java/android/telephony/CellSignalStrengthCdma.java index 047436207ada..dfaaab918012 100644 --- a/telephony/java/android/telephony/CellSignalStrengthCdma.java +++ b/telephony/java/android/telephony/CellSignalStrengthCdma.java @@ -20,6 +20,8 @@ import android.os.Parcel; import android.os.Parcelable; import android.telephony.Rlog; +import java.util.Objects; + /** * Signal strength related information. */ @@ -293,9 +295,7 @@ public final class CellSignalStrengthCdma extends CellSignalStrength implements @Override public int hashCode() { - int primeNum = 31; - return ((mCdmaDbm * primeNum) + (mCdmaEcio * primeNum) - + (mEvdoDbm * primeNum) + (mEvdoEcio * primeNum) + (mEvdoSnr * primeNum)); + return Objects.hash(mCdmaDbm, mCdmaEcio, mEvdoDbm, mEvdoEcio, mEvdoSnr); } @Override diff --git a/telephony/java/android/telephony/CellSignalStrengthGsm.java b/telephony/java/android/telephony/CellSignalStrengthGsm.java index 4137853e79aa..f68d2cad1226 100644 --- a/telephony/java/android/telephony/CellSignalStrengthGsm.java +++ b/telephony/java/android/telephony/CellSignalStrengthGsm.java @@ -20,6 +20,8 @@ import android.os.Parcel; import android.os.Parcelable; import android.telephony.Rlog; +import java.util.Objects; + /** * GSM signal strength related information. */ @@ -185,8 +187,7 @@ public final class CellSignalStrengthGsm extends CellSignalStrength implements P @Override public int hashCode() { - int primeNum = 31; - return (mSignalStrength * primeNum) + (mBitErrorRate * primeNum); + return Objects.hash(mSignalStrength, mBitErrorRate, mTimingAdvance); } @Override diff --git a/telephony/java/android/telephony/CellSignalStrengthLte.java b/telephony/java/android/telephony/CellSignalStrengthLte.java index 0d07a40822d4..6ffc8b6e497c 100644 --- a/telephony/java/android/telephony/CellSignalStrengthLte.java +++ b/telephony/java/android/telephony/CellSignalStrengthLte.java @@ -20,6 +20,8 @@ import android.os.Parcel; import android.os.Parcelable; import android.telephony.Rlog; +import java.util.Objects; + /** * LTE signal strength related information. */ @@ -231,10 +233,7 @@ public final class CellSignalStrengthLte extends CellSignalStrength implements P @Override public int hashCode() { - int primeNum = 31; - return (mSignalStrength * primeNum) + (mRsrp * primeNum) - + (mRsrq * primeNum) + (mRssnr * primeNum) + (mCqi * primeNum) - + (mTimingAdvance * primeNum); + return Objects.hash(mSignalStrength, mRsrp, mRsrq, mRssnr, mCqi, mTimingAdvance); } @Override diff --git a/telephony/java/android/telephony/CellSignalStrengthWcdma.java b/telephony/java/android/telephony/CellSignalStrengthWcdma.java index b94b01da87bb..2cd56b8500a6 100644 --- a/telephony/java/android/telephony/CellSignalStrengthWcdma.java +++ b/telephony/java/android/telephony/CellSignalStrengthWcdma.java @@ -20,6 +20,8 @@ import android.os.Parcel; import android.os.Parcelable; import android.telephony.Rlog; +import java.util.Objects; + /** * Wcdma signal strength related information. */ @@ -156,8 +158,7 @@ public final class CellSignalStrengthWcdma extends CellSignalStrength implements @Override public int hashCode() { - int primeNum = 31; - return (mSignalStrength * primeNum) + (mBitErrorRate * primeNum); + return Objects.hash(mSignalStrength, mBitErrorRate); } @Override diff --git a/telephony/java/android/telephony/ModemActivityInfo.java b/telephony/java/android/telephony/ModemActivityInfo.java index 03ce2d8e6f6d..521adef8497f 100644 --- a/telephony/java/android/telephony/ModemActivityInfo.java +++ b/telephony/java/android/telephony/ModemActivityInfo.java @@ -36,12 +36,12 @@ public class ModemActivityInfo implements Parcelable { */ public static final int TX_POWER_LEVELS = 5; - private final long mTimestamp; - private final int mSleepTimeMs; - private final int mIdleTimeMs; - private final int [] mTxTimeMs = new int[TX_POWER_LEVELS]; - private final int mRxTimeMs; - private final int mEnergyUsed; + private long mTimestamp; + private int mSleepTimeMs; + private int mIdleTimeMs; + private int [] mTxTimeMs = new int[TX_POWER_LEVELS]; + private int mRxTimeMs; + private int mEnergyUsed; public ModemActivityInfo(long timestamp, int sleepTimeMs, int idleTimeMs, int[] txTimeMs, int rxTimeMs, int energyUsed) { @@ -110,6 +110,10 @@ public class ModemActivityInfo implements Parcelable { return mTimestamp; } + public void setTimestamp(long timestamp) { + mTimestamp = timestamp; + } + /** * @return tx time in ms. It's an array of tx times * with each index... @@ -118,6 +122,10 @@ public class ModemActivityInfo implements Parcelable { return mTxTimeMs; } + public void setTxTimeMillis(int[] txTimeMs) { + mTxTimeMs = txTimeMs; + } + /** * @return sleep time in ms. */ @@ -125,6 +133,10 @@ public class ModemActivityInfo implements Parcelable { return mSleepTimeMs; } + public void setSleepTimeMillis(int sleepTimeMillis) { + mSleepTimeMs = sleepTimeMillis; + } + /** * @return idle time in ms. */ @@ -132,6 +144,10 @@ public class ModemActivityInfo implements Parcelable { return mIdleTimeMs; } + public void setIdleTimeMillis(int idleTimeMillis) { + mIdleTimeMs = idleTimeMillis; + } + /** * @return rx time in ms. */ @@ -139,6 +155,10 @@ public class ModemActivityInfo implements Parcelable { return mRxTimeMs; } + public void setRxTimeMillis(int rxTimeMillis) { + mRxTimeMs = rxTimeMillis; + } + /** * product of current(mA), voltage(V) and time(ms) * @return energy used @@ -147,6 +167,10 @@ public class ModemActivityInfo implements Parcelable { return mEnergyUsed; } + public void setEnergyUsed(int energyUsed) { + mEnergyUsed = energyUsed; + } + /** * @return if the record is valid */ diff --git a/telephony/java/android/telephony/SmsMessage.java b/telephony/java/android/telephony/SmsMessage.java index a6dbc06647d0..577ea7dfc004 100644 --- a/telephony/java/android/telephony/SmsMessage.java +++ b/telephony/java/android/telephony/SmsMessage.java @@ -18,8 +18,8 @@ package android.telephony; import static android.telephony.TelephonyManager.PHONE_TYPE_CDMA; +import android.annotation.Nullable; import android.annotation.StringDef; -import android.app.PendingIntent; import android.content.res.Resources; import android.os.Binder; import android.text.TextUtils; @@ -544,8 +544,16 @@ public class SmsMessage { /** * Returns the originating address (sender) of this SMS message in String - * form or null if unavailable + * form or null if unavailable. + * + * <p>If the address is a GSM-formatted address, it will be in a format specified by 3GPP + * 23.040 Sec 9.1.2.5. If it is a CDMA address, it will be a format specified by 3GPP2 + * C.S005-D Table 2.7.1.3.2.4-2. The choice of format is carrier-specific, so callers of the + * should be careful to avoid assumptions about the returned content. + * + * @return a String representation of the address; null if unavailable. */ + @Nullable public String getOriginatingAddress() { return mWrappedSmsMessage.getOriginatingAddress(); } diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java index 0a6d960421b2..5c290da44e3d 100644 --- a/telephony/java/android/telephony/TelephonyManager.java +++ b/telephony/java/android/telephony/TelephonyManager.java @@ -48,12 +48,13 @@ import android.telecom.PhoneAccount; import android.telecom.PhoneAccountHandle; import android.telecom.TelecomManager; import android.telephony.VisualVoicemailService.VisualVoicemailTask; +import android.telephony.ims.aidl.IImsConfig; +import android.telephony.ims.aidl.IImsMmTelFeature; +import android.telephony.ims.aidl.IImsRcsFeature; +import android.telephony.ims.aidl.IImsRegistration; import android.telephony.ims.feature.ImsFeature; import android.util.Log; -import com.android.ims.internal.IImsMMTelFeature; -import com.android.ims.internal.IImsRcsFeature; -import com.android.ims.internal.IImsRegistration; import com.android.ims.internal.IImsServiceFeatureCallback; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.telecom.ITelecomService; @@ -4462,7 +4463,7 @@ public class TelephonyManager { try { ITelephony telephony = getITelephony(); if (telephony != null) - return telephony.iccTransmitApduBasicChannel(subId, cla, + return telephony.iccTransmitApduBasicChannel(subId, getOpPackageName(), cla, instruction, p1, p2, p3, data); } catch (RemoteException ex) { } catch (NullPointerException ex) { @@ -4951,28 +4952,6 @@ public class TelephonyManager { } } - /** - * Returns the response of ISIM Authetification through RIL. - * Returns null if the Authentification hasn't been successed or isn't present iphonesubinfo. - * @return the response of ISIM Authetification, or null if not available - * @hide - * @deprecated - * @see getIccAuthentication with appType=PhoneConstants.APPTYPE_ISIM - */ - public String getIsimChallengeResponse(String nonce){ - try { - IPhoneSubInfo info = getSubscriberInfo(); - if (info == null) - return null; - return info.getIsimChallengeResponse(nonce); - } catch (RemoteException ex) { - return null; - } catch (NullPointerException ex) { - // This could happen before phone restarts due to crashing - return null; - } - } - // ICC SIM Application Types /** UICC application type is SIM */ public static final int APPTYPE_SIM = PhoneConstants.APPTYPE_SIM; @@ -5094,57 +5073,60 @@ public class TelephonyManager { } } - /** @hide */ - @IntDef({ImsFeature.EMERGENCY_MMTEL, ImsFeature.MMTEL, ImsFeature.RCS}) - @Retention(RetentionPolicy.SOURCE) - public @interface Feature {} + /** + * Enables IMS for the framework. This will trigger IMS registration and ImsFeature capability + * status updates, if not already enabled. + * @hide + */ + public void enableIms(int slotId) { + try { + ITelephony telephony = getITelephony(); + if (telephony != null) { + telephony.enableIms(slotId); + } + } catch (RemoteException e) { + Rlog.e(TAG, "enableIms, RemoteException: " + + e.getMessage()); + } + } /** - * Returns the {@link IImsMMTelFeature} that corresponds to the given slot Id and MMTel - * feature or {@link null} if the service is not available. If an MMTelFeature is available, the - * {@link IImsServiceFeatureCallback} callback is registered as a listener for feature updates. - * @param slotIndex The SIM slot that we are requesting the {@link IImsMMTelFeature} for. - * @param callback Listener that will send updates to ImsManager when there are updates to - * ImsServiceController. - * @return {@link IImsMMTelFeature} interface for the feature specified or {@code null} if - * it is unavailable. + * Disables IMS for the framework. This will trigger IMS de-registration and trigger ImsFeature + * status updates to disabled. * @hide */ - public @Nullable IImsMMTelFeature getImsMMTelFeatureAndListen(int slotIndex, - IImsServiceFeatureCallback callback) { + public void disableIms(int slotId) { try { ITelephony telephony = getITelephony(); if (telephony != null) { - return telephony.getMMTelFeatureAndListen(slotIndex, callback); + telephony.disableIms(slotId); } } catch (RemoteException e) { - Rlog.e(TAG, "getImsMMTelFeatureAndListen, RemoteException: " + Rlog.e(TAG, "disableIms, RemoteException: " + e.getMessage()); } - return null; } /** - * Returns the {@link IImsMMTelFeature} that corresponds to the given slot Id and MMTel - * feature for emergency calling or {@link null} if the service is not available. If an - * MMTelFeature is available, the {@link IImsServiceFeatureCallback} callback is registered as a - * listener for feature updates. - * @param slotIndex The SIM slot that we are requesting the {@link IImsMMTelFeature} for. + * Returns the {@link IImsMmTelFeature} that corresponds to the given slot Id and MMTel + * feature or {@link null} if the service is not available. If an MMTelFeature is available, the + * {@link IImsServiceFeatureCallback} callback is registered as a listener for feature updates. + * @param slotIndex The SIM slot that we are requesting the {@link IImsMmTelFeature} for. * @param callback Listener that will send updates to ImsManager when there are updates to * ImsServiceController. - * @return {@link IImsMMTelFeature} interface for the feature specified or {@code null} if + * @return {@link IImsMmTelFeature} interface for the feature specified or {@code null} if * it is unavailable. * @hide */ - public @Nullable IImsMMTelFeature getImsEmergencyMMTelFeatureAndListen(int slotIndex, + public @Nullable IImsMmTelFeature getImsMmTelFeatureAndListen(int slotIndex, IImsServiceFeatureCallback callback) { try { ITelephony telephony = getITelephony(); if (telephony != null) { - return telephony.getEmergencyMMTelFeatureAndListen(slotIndex, callback); + return telephony.getMmTelFeatureAndListen(slotIndex, callback); } } catch (RemoteException e) { - Rlog.e(TAG, "getImsEmergencyMMTelFeatureAndListen, RemoteException: " + Rlog.e(TAG, "getImsMmTelFeatureAndListen, RemoteException: " + e.getMessage()); } return null; @@ -5196,6 +5178,25 @@ public class TelephonyManager { } /** + * @return the {@IImsConfig} interface that corresponds with the slot index and feature. + * @param slotIndex The SIM slot corresponding to the ImsService ImsConfig is active for. + * @param feature An integer indicating the feature that we wish to get the ImsConfig for. + * Corresponds to features defined in ImsFeature. + * @hide + */ + public @Nullable IImsConfig getImsConfig(int slotIndex, int feature) { + try { + ITelephony telephony = getITelephony(); + if (telephony != null) { + return telephony.getImsConfig(slotIndex, feature); + } + } catch (RemoteException e) { + Rlog.e(TAG, "getImsRegistration, RemoteException: " + e.getMessage()); + } + return null; + } + + /** * Set IMS registration state * * @param Registration state diff --git a/telephony/java/android/telephony/data/DataCallResponse.java b/telephony/java/android/telephony/data/DataCallResponse.java index ef3a183f7800..25f51333350b 100644 --- a/telephony/java/android/telephony/data/DataCallResponse.java +++ b/telephony/java/android/telephony/data/DataCallResponse.java @@ -27,6 +27,7 @@ import android.os.Parcelable; import java.net.InetAddress; import java.util.ArrayList; import java.util.List; +import java.util.Objects; /** * Description of the response of a setup data call connection request. @@ -220,17 +221,8 @@ public final class DataCallResponse implements Parcelable { @Override public int hashCode() { - return mStatus * 31 - + mSuggestedRetryTime * 37 - + mCid * 41 - + mActive * 43 - + mType.hashCode() * 47 - + mIfname.hashCode() * 53 - + mAddresses.hashCode() * 59 - + mDnses.hashCode() * 61 - + mGateways.hashCode() * 67 - + mPcscfs.hashCode() * 71 - + mMtu * 73; + return Objects.hash(mStatus, mSuggestedRetryTime, mCid, mActive, mType, mIfname, mAddresses, + mDnses, mGateways, mPcscfs, mMtu); } @Override diff --git a/telephony/java/com/android/ims/ImsCallForwardInfo.aidl b/telephony/java/android/telephony/ims/ImsCallForwardInfo.aidl index a7c3f9a5f722..b322b39be7e4 100644 --- a/telephony/java/com/android/ims/ImsCallForwardInfo.aidl +++ b/telephony/java/android/telephony/ims/ImsCallForwardInfo.aidl @@ -14,6 +14,6 @@ * limitations under the License. */ -package com.android.ims; +package android.telephony.ims; parcelable ImsCallForwardInfo; diff --git a/telephony/java/com/android/ims/ImsCallForwardInfo.java b/telephony/java/android/telephony/ims/ImsCallForwardInfo.java index eeee0fc938bc..6d7218179067 100644 --- a/telephony/java/com/android/ims/ImsCallForwardInfo.java +++ b/telephony/java/android/telephony/ims/ImsCallForwardInfo.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013 The Android Open Source Project + * Copyright (C) 2018 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -11,11 +11,12 @@ * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and - * limitations under the License. + * limitations under the License */ -package com.android.ims; +package android.telephony.ims; +import android.annotation.SystemApi; import android.os.Parcel; import android.os.Parcelable; @@ -24,23 +25,32 @@ import android.os.Parcelable; * * @hide */ -public class ImsCallForwardInfo implements Parcelable { +@SystemApi +public final class ImsCallForwardInfo implements Parcelable { // Refer to ImsUtInterface#CDIV_CF_XXX + /** @hide */ public int mCondition; // 0: disabled, 1: enabled + /** @hide */ public int mStatus; // 0x91: International, 0x81: Unknown + /** @hide */ public int mToA; // Service class + /** @hide */ public int mServiceClass; // Number (it will not include the "sip" or "tel" URI scheme) + /** @hide */ public String mNumber; // No reply timer for CF + /** @hide */ public int mTimeSeconds; + /** @hide */ public ImsCallForwardInfo() { } + /** @hide */ public ImsCallForwardInfo(Parcel in) { readFromParcel(in); } @@ -91,4 +101,28 @@ public class ImsCallForwardInfo implements Parcelable { return new ImsCallForwardInfo[size]; } }; + + public int getCondition() { + return mCondition; + } + + public int getStatus() { + return mStatus; + } + + public int getToA() { + return mToA; + } + + public int getServiceClass() { + return mServiceClass; + } + + public String getNumber() { + return mNumber; + } + + public int getTimeSeconds() { + return mTimeSeconds; + } } diff --git a/telephony/java/com/android/ims/ImsCallProfile.aidl b/telephony/java/android/telephony/ims/ImsCallProfile.aidl index a356d1352eb2..e24e14530916 100644 --- a/telephony/java/com/android/ims/ImsCallProfile.aidl +++ b/telephony/java/android/telephony/ims/ImsCallProfile.aidl @@ -14,6 +14,6 @@ * limitations under the License. */ -package com.android.ims; +package android.telephony.ims; parcelable ImsCallProfile; diff --git a/telephony/java/com/android/ims/ImsCallProfile.java b/telephony/java/android/telephony/ims/ImsCallProfile.java index 693aaff8ce0f..27e5f943982b 100644 --- a/telephony/java/com/android/ims/ImsCallProfile.java +++ b/telephony/java/android/telephony/ims/ImsCallProfile.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013 The Android Open Source Project + * Copyright (C) 2018 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -11,11 +11,12 @@ * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and - * limitations under the License. + * limitations under the License */ -package com.android.ims; +package android.telephony.ims; +import android.annotation.SystemApi; import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; @@ -32,7 +33,8 @@ import com.android.internal.telephony.PhoneConstants; * * @hide */ -public class ImsCallProfile implements Parcelable { +@SystemApi +public final class ImsCallProfile implements Parcelable { private static final String TAG = "ImsCallProfile"; /** @@ -110,52 +112,92 @@ public class ImsCallProfile implements Parcelable { * the video during voice call. * conference_avail : Indicates if the session can be extended to the conference. */ + /** + * @hide + */ public static final String EXTRA_CONFERENCE = "conference"; + /** + * @hide + */ public static final String EXTRA_E_CALL = "e_call"; + /** + * @hide + */ public static final String EXTRA_VMS = "vms"; + /** + * @hide + */ public static final String EXTRA_CALL_MODE_CHANGEABLE = "call_mode_changeable"; + /** + * @hide + */ public static final String EXTRA_CONFERENCE_AVAIL = "conference_avail"; // Extra string for internal use only. OEMs should not use // this for packing extras. + /** + * @hide + */ public static final String EXTRA_OEM_EXTRAS = "OemCallExtras"; /** - * Integer extra properties - * oir : Rule for originating identity (number) presentation, MO/MT. + * Rule for originating identity (number) presentation, MO/MT. * {@link ImsCallProfile#OIR_DEFAULT} * {@link ImsCallProfile#OIR_PRESENTATION_RESTRICTED} * {@link ImsCallProfile#OIR_PRESENTATION_NOT_RESTRICTED} - * cnap : Rule for calling name presentation + */ + public static final String EXTRA_OIR = "oir"; + /** + * Rule for calling name presentation * {@link ImsCallProfile#OIR_DEFAULT} * {@link ImsCallProfile#OIR_PRESENTATION_RESTRICTED} * {@link ImsCallProfile#OIR_PRESENTATION_NOT_RESTRICTED} - * dialstring : To identify the Ims call type, MO - * {@link ImsCallProfile#DIALSTRING_NORMAL_CALL} + */ + public static final String EXTRA_CNAP = "cnap"; + /** + * To identify the Ims call type, MO + * {@link ImsCallProfile#DIALSTRING_NORMAL} * {@link ImsCallProfile#DIALSTRING_SS_CONF} * {@link ImsCallProfile#DIALSTRING_USSD} */ - public static final String EXTRA_OIR = "oir"; - public static final String EXTRA_CNAP = "cnap"; public static final String EXTRA_DIALSTRING = "dialstring"; /** * Values for EXTRA_OIR / EXTRA_CNAP */ + /** + * Default presentation for Originating Identity. + */ public static final int OIR_DEFAULT = 0; // "user subscription default value" + /** + * Restricted presentation for Originating Identity. + */ public static final int OIR_PRESENTATION_RESTRICTED = 1; + /** + * Not restricted presentation for Originating Identity. + */ public static final int OIR_PRESENTATION_NOT_RESTRICTED = 2; + /** + * Presentation unknown for Originating Identity. + */ public static final int OIR_PRESENTATION_UNKNOWN = 3; + /** + * Payphone presentation for Originating Identity. + */ public static final int OIR_PRESENTATION_PAYPHONE = 4; + //Values for EXTRA_DIALSTRING /** - * Values for EXTRA_DIALSTRING + * A default or normal normal call. */ - // default (normal call) public static final int DIALSTRING_NORMAL = 0; - // Call for SIP-based user configuration + /** + * Call for SIP-based user configuration + */ public static final int DIALSTRING_SS_CONF = 1; - // Call for USSD message + /** + * Call for USSD message + */ public static final int DIALSTRING_USSD = 2; /** @@ -215,8 +257,11 @@ public class ImsCallProfile implements Parcelable { */ public static final String EXTRA_CALL_RAT_TYPE_ALT = "callRadioTech"; + /** @hide */ public int mServiceType; + /** @hide */ public int mCallType; + /** @hide */ public int mRestrictCause = CALL_RESTRICT_CAUSE_NONE; /** @@ -241,13 +286,17 @@ public class ImsCallProfile implements Parcelable { * Invalid types will be removed when the {@link ImsCallProfile} is parceled for transmit across * a {@link android.os.Binder}. */ + /** @hide */ public Bundle mCallExtras; + /** @hide */ public ImsStreamMediaProfile mMediaProfile; + /** @hide */ public ImsCallProfile(Parcel in) { readFromParcel(in); } + /** @hide */ public ImsCallProfile() { mServiceType = SERVICE_TYPE_NORMAL; mCallType = CALL_TYPE_VOICE_N_VIDEO; @@ -255,6 +304,7 @@ public class ImsCallProfile implements Parcelable { mMediaProfile = new ImsStreamMediaProfile(); } + /** @hide */ public ImsCallProfile(int serviceType, int callType) { mServiceType = serviceType; mCallType = callType; @@ -366,8 +416,28 @@ public class ImsCallProfile implements Parcelable { } }; + public int getServiceType() { + return mServiceType; + } + + public int getCallType() { + return mCallType; + } + + public int getRestrictCause() { + return mRestrictCause; + } + + public Bundle getCallExtras() { + return mCallExtras; + } + + public ImsStreamMediaProfile getMediaProfile() { + return mMediaProfile; + } + /** - * Converts from the call types defined in {@link com.android.ims.ImsCallProfile} to the + * Converts from the call types defined in {@link ImsCallProfile} to the * video state values defined in {@link VideoProfile}. * * @param callProfile The call profile. @@ -434,9 +504,9 @@ public class ImsCallProfile implements Parcelable { } /** - * Translate presentation value to OIR value - * @param presentation - * @return OIR valuse + * Badly named old method, kept for compatibility. + * See {@link #presentationToOir(int)}. + * @hide */ public static int presentationToOIR(int presentation) { switch (presentation) { @@ -454,9 +524,19 @@ public class ImsCallProfile implements Parcelable { } /** + * Translate presentation value to OIR value + * @param presentation + * @return OIR values + */ + public static int presentationToOir(int presentation) { + return presentationToOIR(presentation); + } + + /** * Translate OIR value to presentation value * @param oir value * @return presentation value + * @hide */ public static int OIRToPresentation(int oir) { switch(oir) { diff --git a/telephony/java/com/android/ims/internal/ImsCallSession.java b/telephony/java/android/telephony/ims/ImsCallSession.java index 1736b80c562b..c3d103f88db3 100644 --- a/telephony/java/com/android/ims/internal/ImsCallSession.java +++ b/telephony/java/android/telephony/ims/ImsCallSession.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013 The Android Open Source Project + * Copyright (C) 2018 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -11,23 +11,26 @@ * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and - * limitations under the License. + * limitations under the License */ -package com.android.ims.internal; +package android.telephony.ims; +import android.annotation.CallbackExecutor; +import android.annotation.NonNull; +import android.annotation.SystemApi; import android.os.Message; import android.os.RemoteException; +import android.telephony.ims.aidl.IImsCallSessionListener; import java.util.Objects; +import java.util.concurrent.Executor; -import android.telephony.ims.stub.ImsCallSessionListenerImplBase; +import android.telephony.ims.stub.ImsCallSessionImplBase; import android.util.Log; -import com.android.ims.ImsCallProfile; -import com.android.ims.ImsConferenceState; -import com.android.ims.ImsReasonInfo; -import com.android.ims.ImsStreamMediaProfile; -import com.android.ims.ImsSuppServiceNotification; + +import com.android.ims.internal.IImsCallSession; +import com.android.ims.internal.IImsVideoCallProvider; /** * Provides the call initiation/termination, and media exchange between two IMS endpoints. @@ -39,7 +42,8 @@ public class ImsCallSession { private static final String TAG = "ImsCallSession"; /** - * Defines IMS call session state. + * Defines IMS call session state. Please use {@link ImsCallSessionImplBase.State} definition. + * This is kept around for capability reasons. */ public static class State { public static final int IDLE = 0; @@ -92,6 +96,7 @@ public class ImsCallSession { * Listener for events relating to an IMS session, such as when a session is being * recieved ("on ringing") or a call is outgoing ("on calling"). * <p>Many of these events are also received by {@link ImsCall.Listener}.</p> + * @hide */ public static class Listener { /** @@ -449,6 +454,7 @@ public class ImsCallSession { private boolean mClosed = false; private Listener mListener; + /** @hide */ public ImsCallSession(IImsCallSession iSession) { miSession = iSession; @@ -462,6 +468,7 @@ public class ImsCallSession { } } + /** @hide */ public ImsCallSession(IImsCallSession iSession, Listener listener) { this(iSession); setListener(listener); @@ -470,15 +477,17 @@ public class ImsCallSession { /** * Closes this object. This object is not usable after being closed. */ - public synchronized void close() { - if (mClosed) { - return; - } + public void close() { + synchronized (this) { + if (mClosed) { + return; + } - try { - miSession.close(); - mClosed = true; - } catch (RemoteException e) { + try { + miSession.close(); + mClosed = true; + } catch (RemoteException e) { + } } } @@ -554,6 +563,7 @@ public class ImsCallSession { * Gets the video call provider for the session. * * @return The video call provider. + * @hide */ public IImsVideoCallProvider getVideoCallProvider() { if (mClosed) { @@ -659,6 +669,7 @@ public class ImsCallSession { * override the previous listener. * * @param listener to listen to the session events of this object + * @hide */ public void setListener(Listener listener) { mListener = listener; @@ -987,7 +998,6 @@ public class ImsCallSession { * Sends Rtt Message * * @param rttMessage rtt text to be sent - * @throws ImsException if call is absent */ public void sendRttMessage(String rttMessage) { if (mClosed) { @@ -1004,7 +1014,6 @@ public class ImsCallSession { * Sends RTT Upgrade request * * @param to : expected profile - * @throws CallStateException */ public void sendRttModifyRequest(ImsCallProfile to) { if (mClosed) { @@ -1021,7 +1030,6 @@ public class ImsCallSession { * Sends RTT Upgrade response * * @param response : response for upgrade - * @throws CallStateException */ public void sendRttModifyResponse(boolean response) { if (mClosed) { @@ -1040,37 +1048,33 @@ public class ImsCallSession { * the application is notified by having one of the methods called on * the {@link IImsCallSessionListener}. */ - private class IImsCallSessionListenerProxy extends ImsCallSessionListenerImplBase { + private class IImsCallSessionListenerProxy extends IImsCallSessionListener.Stub { /** * Notifies the result of the basic session operation (setup / terminate). */ @Override - public void callSessionProgressing(IImsCallSession session, - ImsStreamMediaProfile profile) { + public void callSessionProgressing(ImsStreamMediaProfile profile) { if (mListener != null) { mListener.callSessionProgressing(ImsCallSession.this, profile); } } @Override - public void callSessionStarted(IImsCallSession session, - ImsCallProfile profile) { + public void callSessionInitiated(ImsCallProfile profile) { if (mListener != null) { mListener.callSessionStarted(ImsCallSession.this, profile); } } @Override - public void callSessionStartFailed(IImsCallSession session, - ImsReasonInfo reasonInfo) { + public void callSessionInitiatedFailed(ImsReasonInfo reasonInfo) { if (mListener != null) { mListener.callSessionStartFailed(ImsCallSession.this, reasonInfo); } } @Override - public void callSessionTerminated(IImsCallSession session, - ImsReasonInfo reasonInfo) { + public void callSessionTerminated(ImsReasonInfo reasonInfo) { if (mListener != null) { mListener.callSessionTerminated(ImsCallSession.this, reasonInfo); } @@ -1080,48 +1084,42 @@ public class ImsCallSession { * Notifies the result of the call hold/resume operation. */ @Override - public void callSessionHeld(IImsCallSession session, - ImsCallProfile profile) { + public void callSessionHeld(ImsCallProfile profile) { if (mListener != null) { mListener.callSessionHeld(ImsCallSession.this, profile); } } @Override - public void callSessionHoldFailed(IImsCallSession session, - ImsReasonInfo reasonInfo) { + public void callSessionHoldFailed(ImsReasonInfo reasonInfo) { if (mListener != null) { mListener.callSessionHoldFailed(ImsCallSession.this, reasonInfo); } } @Override - public void callSessionHoldReceived(IImsCallSession session, - ImsCallProfile profile) { + public void callSessionHoldReceived(ImsCallProfile profile) { if (mListener != null) { mListener.callSessionHoldReceived(ImsCallSession.this, profile); } } @Override - public void callSessionResumed(IImsCallSession session, - ImsCallProfile profile) { + public void callSessionResumed(ImsCallProfile profile) { if (mListener != null) { mListener.callSessionResumed(ImsCallSession.this, profile); } } @Override - public void callSessionResumeFailed(IImsCallSession session, - ImsReasonInfo reasonInfo) { + public void callSessionResumeFailed(ImsReasonInfo reasonInfo) { if (mListener != null) { mListener.callSessionResumeFailed(ImsCallSession.this, reasonInfo); } } @Override - public void callSessionResumeReceived(IImsCallSession session, - ImsCallProfile profile) { + public void callSessionResumeReceived(ImsCallProfile profile) { if (mListener != null) { mListener.callSessionResumeReceived(ImsCallSession.this, profile); } @@ -1130,13 +1128,11 @@ public class ImsCallSession { /** * Notifies the start of a call merge operation. * - * @param session The call session. * @param newSession The merged call session. * @param profile The call profile. */ @Override - public void callSessionMergeStarted(IImsCallSession session, - IImsCallSession newSession, ImsCallProfile profile) { + public void callSessionMergeStarted(IImsCallSession newSession, ImsCallProfile profile) { // This callback can be used for future use to add additional // functionality that may be needed between conference start and complete Log.d(TAG, "callSessionMergeStarted"); @@ -1173,12 +1169,10 @@ public class ImsCallSession { /** * Notifies of a failure to perform a call merge operation. * - * @param session The call session. * @param reasonInfo The merge failure reason. */ @Override - public void callSessionMergeFailed(IImsCallSession session, - ImsReasonInfo reasonInfo) { + public void callSessionMergeFailed(ImsReasonInfo reasonInfo) { if (mListener != null) { mListener.callSessionMergeFailed(ImsCallSession.this, reasonInfo); } @@ -1188,24 +1182,21 @@ public class ImsCallSession { * Notifies the result of call upgrade / downgrade or any other call updates. */ @Override - public void callSessionUpdated(IImsCallSession session, - ImsCallProfile profile) { + public void callSessionUpdated(ImsCallProfile profile) { if (mListener != null) { mListener.callSessionUpdated(ImsCallSession.this, profile); } } @Override - public void callSessionUpdateFailed(IImsCallSession session, - ImsReasonInfo reasonInfo) { + public void callSessionUpdateFailed(ImsReasonInfo reasonInfo) { if (mListener != null) { mListener.callSessionUpdateFailed(ImsCallSession.this, reasonInfo); } } @Override - public void callSessionUpdateReceived(IImsCallSession session, - ImsCallProfile profile) { + public void callSessionUpdateReceived(ImsCallProfile profile) { if (mListener != null) { mListener.callSessionUpdateReceived(ImsCallSession.this, profile); } @@ -1215,8 +1206,8 @@ public class ImsCallSession { * Notifies the result of conference extension. */ @Override - public void callSessionConferenceExtended(IImsCallSession session, - IImsCallSession newSession, ImsCallProfile profile) { + public void callSessionConferenceExtended(IImsCallSession newSession, + ImsCallProfile profile) { if (mListener != null) { mListener.callSessionConferenceExtended(ImsCallSession.this, new ImsCallSession(newSession), profile); @@ -1224,16 +1215,15 @@ public class ImsCallSession { } @Override - public void callSessionConferenceExtendFailed(IImsCallSession session, - ImsReasonInfo reasonInfo) { + public void callSessionConferenceExtendFailed(ImsReasonInfo reasonInfo) { if (mListener != null) { mListener.callSessionConferenceExtendFailed(ImsCallSession.this, reasonInfo); } } @Override - public void callSessionConferenceExtendReceived(IImsCallSession session, - IImsCallSession newSession, ImsCallProfile profile) { + public void callSessionConferenceExtendReceived(IImsCallSession newSession, + ImsCallProfile profile) { if (mListener != null) { mListener.callSessionConferenceExtendReceived(ImsCallSession.this, new ImsCallSession(newSession), profile); @@ -1245,15 +1235,14 @@ public class ImsCallSession { * the conference session. */ @Override - public void callSessionInviteParticipantsRequestDelivered(IImsCallSession session) { + public void callSessionInviteParticipantsRequestDelivered() { if (mListener != null) { mListener.callSessionInviteParticipantsRequestDelivered(ImsCallSession.this); } } @Override - public void callSessionInviteParticipantsRequestFailed(IImsCallSession session, - ImsReasonInfo reasonInfo) { + public void callSessionInviteParticipantsRequestFailed(ImsReasonInfo reasonInfo) { if (mListener != null) { mListener.callSessionInviteParticipantsRequestFailed(ImsCallSession.this, reasonInfo); @@ -1261,15 +1250,14 @@ public class ImsCallSession { } @Override - public void callSessionRemoveParticipantsRequestDelivered(IImsCallSession session) { + public void callSessionRemoveParticipantsRequestDelivered() { if (mListener != null) { mListener.callSessionRemoveParticipantsRequestDelivered(ImsCallSession.this); } } @Override - public void callSessionRemoveParticipantsRequestFailed(IImsCallSession session, - ImsReasonInfo reasonInfo) { + public void callSessionRemoveParticipantsRequestFailed(ImsReasonInfo reasonInfo) { if (mListener != null) { mListener.callSessionRemoveParticipantsRequestFailed(ImsCallSession.this, reasonInfo); @@ -1280,8 +1268,7 @@ public class ImsCallSession { * Notifies the changes of the conference info. in the conference session. */ @Override - public void callSessionConferenceStateUpdated(IImsCallSession session, - ImsConferenceState state) { + public void callSessionConferenceStateUpdated(ImsConferenceState state) { if (mListener != null) { mListener.callSessionConferenceStateUpdated(ImsCallSession.this, state); } @@ -1291,17 +1278,15 @@ public class ImsCallSession { * Notifies the incoming USSD message. */ @Override - public void callSessionUssdMessageReceived(IImsCallSession session, - int mode, String ussdMessage) { + public void callSessionUssdMessageReceived(int mode, String ussdMessage) { if (mListener != null) { mListener.callSessionUssdMessageReceived(ImsCallSession.this, mode, ussdMessage); } } /** - * Notifies of a case where a {@link com.android.ims.internal.ImsCallSession} may + * Notifies of a case where a {@link ImsCallSession} may * potentially handover from one radio technology to another. - * @param session * @param srcAccessTech The source radio access technology; one of the access technology * constants defined in {@link android.telephony.ServiceState}. For * example @@ -1312,8 +1297,7 @@ public class ImsCallSession { * {@link android.telephony.ServiceState#RIL_RADIO_TECHNOLOGY_LTE}. */ @Override - public void callSessionMayHandover(IImsCallSession session, - int srcAccessTech, int targetAccessTech) { + public void callSessionMayHandover(int srcAccessTech, int targetAccessTech) { if (mListener != null) { mListener.callSessionMayHandover(ImsCallSession.this, srcAccessTech, targetAccessTech); @@ -1324,9 +1308,8 @@ public class ImsCallSession { * Notifies of handover information for this call */ @Override - public void callSessionHandover(IImsCallSession session, - int srcAccessTech, int targetAccessTech, - ImsReasonInfo reasonInfo) { + public void callSessionHandover(int srcAccessTech, int targetAccessTech, + ImsReasonInfo reasonInfo) { if (mListener != null) { mListener.callSessionHandover(ImsCallSession.this, srcAccessTech, targetAccessTech, reasonInfo); @@ -1337,9 +1320,8 @@ public class ImsCallSession { * Notifies of handover failure info for this call */ @Override - public void callSessionHandoverFailed(IImsCallSession session, - int srcAccessTech, int targetAccessTech, - ImsReasonInfo reasonInfo) { + public void callSessionHandoverFailed(int srcAccessTech, int targetAccessTech, + ImsReasonInfo reasonInfo) { if (mListener != null) { mListener.callSessionHandoverFailed(ImsCallSession.this, srcAccessTech, targetAccessTech, reasonInfo); @@ -1350,8 +1332,7 @@ public class ImsCallSession { * Notifies the TTY mode received from remote party. */ @Override - public void callSessionTtyModeReceived(IImsCallSession session, - int mode) { + public void callSessionTtyModeReceived(int mode) { if (mListener != null) { mListener.callSessionTtyModeReceived(ImsCallSession.this, mode); } @@ -1360,21 +1341,17 @@ public class ImsCallSession { /** * Notifies of a change to the multiparty state for this {@code ImsCallSession}. * - * @param session The call session. * @param isMultiParty {@code true} if the session became multiparty, {@code false} * otherwise. */ - public void callSessionMultipartyStateChanged(IImsCallSession session, - boolean isMultiParty) { - + public void callSessionMultipartyStateChanged(boolean isMultiParty) { if (mListener != null) { mListener.callSessionMultipartyStateChanged(ImsCallSession.this, isMultiParty); } } @Override - public void callSessionSuppServiceReceived(IImsCallSession session, - ImsSuppServiceNotification suppServiceInfo ) { + public void callSessionSuppServiceReceived(ImsSuppServiceNotification suppServiceInfo ) { if (mListener != null) { mListener.callSessionSuppServiceReceived(ImsCallSession.this, suppServiceInfo); } @@ -1384,8 +1361,7 @@ public class ImsCallSession { * Received RTT modify request from remote party */ @Override - public void callSessionRttModifyRequestReceived(IImsCallSession session, - ImsCallProfile callProfile) { + public void callSessionRttModifyRequestReceived(ImsCallProfile callProfile) { if (mListener != null) { mListener.callSessionRttModifyRequestReceived(ImsCallSession.this, callProfile); } diff --git a/telephony/java/android/telephony/ims/ImsCallSessionListener.java b/telephony/java/android/telephony/ims/ImsCallSessionListener.java new file mode 100644 index 000000000000..a7f124a5b791 --- /dev/null +++ b/telephony/java/android/telephony/ims/ImsCallSessionListener.java @@ -0,0 +1,603 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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.telephony.ims; + +import android.annotation.SystemApi; +import android.os.RemoteException; +import android.telephony.ims.aidl.IImsCallSessionListener; +import android.telephony.ims.stub.ImsCallSessionImplBase; + +import com.android.ims.internal.IImsCallSession; + +/** + * Listener interface for notifying the Framework's {@link ImsCallSession} for updates to an ongoing + * IMS call. + * + * @hide + */ +// DO NOT remove or change the existing APIs, only add new ones to this implementation or you +// will break other implementations of ImsCallSessionListener maintained by other ImsServices. +// TODO: APIs in here do not conform to API guidelines yet. This can be changed if +// ImsCallSessionListenerConverter is also changed. +@SystemApi +public class ImsCallSessionListener { + + private final IImsCallSessionListener mListener; + + /** @hide */ + public ImsCallSessionListener(IImsCallSessionListener l) { + mListener = l; + } + + /** + * A request has been sent out to initiate a new IMS call session and a 1xx response has been + * received from the network. + */ + public void callSessionProgressing(ImsStreamMediaProfile profile) { + try { + mListener.callSessionProgressing(profile); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + } + + /** + * The IMS call session has been initiated. + * + * @param profile the associated {@link ImsCallProfile}. + */ + public void callSessionInitiated(ImsCallProfile profile) { + try { + mListener.callSessionInitiated(profile); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + } + + /** + * The IMS call session establishment has failed. + * + * @param reasonInfo {@link ImsReasonInfo} detailing the reason of the IMS call session + * establishment failure. + */ + public void callSessionInitiatedFailed(ImsReasonInfo reasonInfo) { + try { + mListener.callSessionInitiatedFailed(reasonInfo); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + } + + /** + * The IMS call session has been terminated. + * + * @param reasonInfo {@link ImsReasonInfo} detailing the reason of the session termination. + */ + public void callSessionTerminated(ImsReasonInfo reasonInfo) { + try { + mListener.callSessionTerminated(reasonInfo); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + } + + /** + * The IMS call session has started the process of holding the call. If it fails, + * {@link #callSessionHoldFailed(ImsReasonInfo)} should be called. + * + * If the IMS call session is resumed, call {@link #callSessionResumed(ImsCallProfile)}. + * + * @param profile The associated {@link ImsCallProfile} of the call session that has been put + * on hold. + */ + public void callSessionHeld(ImsCallProfile profile) { + try { + mListener.callSessionHeld(profile); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + } + + /** + * The IMS call session has failed to be held. + * + * @param reasonInfo {@link ImsReasonInfo} detailing the reason of the session hold failure. + */ + public void callSessionHoldFailed(ImsReasonInfo reasonInfo) { + try { + mListener.callSessionHoldFailed(reasonInfo); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + } + + /** + * This IMS Call session has been put on hold by the remote party. + * + * @param profile The {@link ImsCallProfile} associated with this IMS call session. + */ + public void callSessionHoldReceived(ImsCallProfile profile) { + try { + mListener.callSessionHoldReceived(profile); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + } + + /** + * The IMS call session has started the process of resuming the call. If the process of resuming + * the call fails, call {@link #callSessionResumeFailed(ImsReasonInfo)}. + * + * @param profile The {@link ImsCallProfile} associated with this IMS call session. + */ + public void callSessionResumed(ImsCallProfile profile) { + try { + mListener.callSessionResumed(profile); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + } + + /** + * The IMS call session resume has failed. + * + * @param reasonInfo {@link ImsReasonInfo} containing the detailed reason of the session resume + * failure. + */ + public void callSessionResumeFailed(ImsReasonInfo reasonInfo) { + try { + mListener.callSessionResumeFailed(reasonInfo); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + } + + /** + * The remote party has resumed this IMS call session. + * + * @param profile {@link ImsCallProfile} associated with the IMS call session. + */ + public void callSessionResumeReceived(ImsCallProfile profile) { + try { + mListener.callSessionResumeReceived(profile); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + } + + /** + * The IMS call session merge has been started. At this point, the {@code newSession} + * represents the IMS call session which represents the new merged conference and has been + * initiated to the IMS conference server. + * + * @param newSession the {@link ImsCallSessionImplBase} that represents the merged active & held + * sessions. + * @param profile The {@link ImsCallProfile} associated with this IMS call session. + */ + public void callSessionMergeStarted(ImsCallSessionImplBase newSession, ImsCallProfile profile) + { + try { + mListener.callSessionMergeStarted(newSession != null ? + newSession.getServiceImpl() : null, profile); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + } + + /** + * Compatibility method for older implementations. + * See {@link #callSessionMergeStarted(ImsCallSessionImplBase, ImsCallProfile)}. + * + * @hide + */ + public void callSessionMergeStarted(IImsCallSession newSession, ImsCallProfile profile) + { + try { + mListener.callSessionMergeStarted(newSession, profile); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + } + + /** + * The session merge is successful and the merged {@link ImsCallSession} is active. + * + * @param newSession the new {@link ImsCallSessionImplBase} + * that represents the conference IMS call + * session. + */ + public void callSessionMergeComplete(ImsCallSessionImplBase newSession) { + try { + mListener.callSessionMergeComplete(newSession != null ? + newSession.getServiceImpl() : null); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + } + + /** + * Compatibility method for older implementations of ImsService. + * + * See {@link #callSessionMergeComplete(ImsCallSessionImplBase)}}. + * + * @hide + */ + public void callSessionMergeComplete(IImsCallSession newSession) { + try { + mListener.callSessionMergeComplete(newSession); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + } + + /** + * The IMS call session merge has failed. + * + * @param reasonInfo {@link ImsReasonInfo} contining the reason for the call merge failure. + */ + public void callSessionMergeFailed(ImsReasonInfo reasonInfo) { + try { + mListener.callSessionMergeFailed(reasonInfo); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + } + + /** + * The IMS call session profile has been updated. Does not include holding or resuming a call. + * + * @param profile The {@link ImsCallProfile} associated with the updated IMS call session. + */ + public void callSessionUpdated(ImsCallProfile profile) { + try { + mListener.callSessionUpdated(profile); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + } + + /** + * The IMS call session profile update has failed. + * + * @param reasonInfo {@link ImsReasonInfo} containing a reason for the session update failure. + */ + public void callSessionUpdateFailed(ImsReasonInfo reasonInfo) { + try { + mListener.callSessionUpdateFailed(reasonInfo); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + } + + /** + * The IMS call session profile has received an update from the remote user. + * + * @param profile The new {@link ImsCallProfile} associated with the update. + */ + public void callSessionUpdateReceived(ImsCallProfile profile) { + try { + mListener.callSessionUpdateReceived(profile); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + } + + /** + * Called when the session has been extended to a conference session. + * + * If the conference extension fails, call + * {@link #callSessionConferenceExtendFailed(ImsReasonInfo)}. + * + * @param newSession the session object that is extended to the conference from the active + * IMS Call session. + * @param profile The {@link ImsCallProfile} associated with the IMS call session. + */ + public void callSessionConferenceExtended(ImsCallSessionImplBase newSession, + ImsCallProfile profile) { + try { + mListener.callSessionConferenceExtended( + newSession != null ? newSession.getServiceImpl() : null, profile); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + } + + /** + * Compatibility method to interface with older versions of ImsService. + * See {@link #callSessionConferenceExtended(ImsCallSessionImplBase, ImsCallProfile)}. + * + * @hide + */ + public void callSessionConferenceExtended(IImsCallSession newSession, ImsCallProfile profile) { + try { + mListener.callSessionConferenceExtended(newSession, profile); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + } + + /** + * The previous conference extension has failed. + * + * @param reasonInfo {@link ImsReasonInfo} containing the detailed reason of the conference + * extension failure. + */ + public void callSessionConferenceExtendFailed(ImsReasonInfo reasonInfo) { + try { + mListener.callSessionConferenceExtendFailed(reasonInfo); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + } + + /** + * A conference extension has been received received from the remote party. + * + * @param newSession An {@link ImsCallSessionImplBase} + * representing the extended IMS call session. + * @param profile The {@link ImsCallProfile} associated with the new IMS call session. + */ + public void callSessionConferenceExtendReceived(ImsCallSessionImplBase newSession, + ImsCallProfile profile) { + try { + mListener.callSessionConferenceExtendReceived(newSession != null + ? newSession.getServiceImpl() : null, profile); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + } + + /** + * Compatibility method to interface with older versions of ImsService. + * See {@link #callSessionConferenceExtendReceived(ImsCallSessionImplBase, ImsCallProfile)}. + * + * @hide + */ + public void callSessionConferenceExtendReceived(IImsCallSession newSession, + ImsCallProfile profile) { + try { + mListener.callSessionConferenceExtendReceived(newSession, profile); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + } + + /** + * The request to invite participants to the conference has been delivered to the conference + * server. + */ + public void callSessionInviteParticipantsRequestDelivered() { + try { + mListener.callSessionInviteParticipantsRequestDelivered(); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + } + + /** + * The previous request to invite participants to the conference (see + * {@link #callSessionInviteParticipantsRequestDelivered()}) has failed. + * + * @param reasonInfo {@link ImsReasonInfo} detailing the reason forthe conference invitation + * failure. + */ + public void callSessionInviteParticipantsRequestFailed(ImsReasonInfo reasonInfo) + { + try { + mListener.callSessionInviteParticipantsRequestFailed(reasonInfo); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + } + + /** + * The request to remove participants from the conference has been delivered to the conference + * server. + */ + public void callSessionRemoveParticipantsRequestDelivered() { + try { + mListener.callSessionRemoveParticipantsRequestDelivered(); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + } + + /** + * The previous request to remove participants from the conference (see + * {@link #callSessionRemoveParticipantsRequestDelivered()}) has failed. + * + * @param reasonInfo {@link ImsReasonInfo} detailing the reason for the conference removal + * failure. + */ + public void callSessionRemoveParticipantsRequestFailed(ImsReasonInfo reasonInfo) + { + try { + mListener.callSessionInviteParticipantsRequestFailed(reasonInfo); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + } + + /** + * The IMS call session's conference state has changed. + * + * @param state The new {@link ImsConferenceState} associated with the conference. + */ + public void callSessionConferenceStateUpdated(ImsConferenceState state) { + try { + mListener.callSessionConferenceStateUpdated(state); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + } + + /** + * The IMS call session has received a Ussd message. + * + * @param mode The mode of the USSD message, either + * {@link ImsCallSessionImplBase#USSD_MODE_NOTIFY} or + * {@link ImsCallSessionImplBase#USSD_MODE_REQUEST}. + * @param ussdMessage The USSD message. + */ + public void callSessionUssdMessageReceived(int mode, String ussdMessage) + { + try { + mListener.callSessionUssdMessageReceived(mode, ussdMessage); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + } + + /** + * An {@link ImsCallSession} may potentially handover from one radio + * technology to another. + * + * @param srcAccessTech The source radio access technology; one of the access technology + * constants defined in {@link android.telephony.ServiceState}. For example + * {@link android.telephony.ServiceState#RIL_RADIO_TECHNOLOGY_LTE}. + * @param targetAccessTech The target radio access technology; one of the access technology + * constants defined in {@link android.telephony.ServiceState}. For example + * {@link android.telephony.ServiceState#RIL_RADIO_TECHNOLOGY_LTE}. + */ + public void callSessionMayHandover(int srcAccessTech, int targetAccessTech) + { + try { + mListener.callSessionMayHandover(srcAccessTech, targetAccessTech); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + } + + /** + * The IMS call session's access technology has changed. + * + * @param srcAccessTech original access technology, defined in + * {@link android.telephony.ServiceState}. + * @param targetAccessTech new access technology, defined in + * {@link android.telephony.ServiceState}. + * @param reasonInfo The {@link ImsReasonInfo} associated with this handover. + */ + public void callSessionHandover(int srcAccessTech, int targetAccessTech, + ImsReasonInfo reasonInfo) { + try { + mListener.callSessionHandover(srcAccessTech, targetAccessTech, reasonInfo); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + } + + /** + * The IMS call session's access technology change has failed.. + * + * @param srcAccessTech original access technology + * @param targetAccessTech new access technology + * @param reasonInfo An {@link ImsReasonInfo} detailing the reason for the failure. + */ + public void callSessionHandoverFailed(int srcAccessTech, int targetAccessTech, + ImsReasonInfo reasonInfo) { + try { + mListener.callSessionHandoverFailed(srcAccessTech, targetAccessTech, reasonInfo); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + } + + /** + * The TTY mode has been changed by the remote party. + * + * @param mode one of the following: - + * {@link com.android.internal.telephony.Phone#TTY_MODE_OFF} - + * {@link com.android.internal.telephony.Phone#TTY_MODE_FULL} - + * {@link com.android.internal.telephony.Phone#TTY_MODE_HCO} - + * {@link com.android.internal.telephony.Phone#TTY_MODE_VCO} + */ + public void callSessionTtyModeReceived(int mode) { + try { + mListener.callSessionTtyModeReceived(mode); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + } + + /** + * The multiparty state has been changed for this {@code ImsCallSession}. + * + * @param isMultiParty {@code true} if the session became multiparty, {@code false} otherwise. + */ + public void callSessionMultipartyStateChanged(boolean isMultiParty) { + try { + mListener.callSessionMultipartyStateChanged(isMultiParty); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + } + + /** + * Supplementary service information has been received for the current IMS call session. + * + * @param suppSrvNotification The {@link ImsSuppServiceNotification} containing the change. + */ + public void callSessionSuppServiceReceived(ImsSuppServiceNotification suppSrvNotification) + { + try { + mListener.callSessionSuppServiceReceived(suppSrvNotification); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + } + + /** + * An RTT modify request has been received from the remote party. + * + * @param callProfile An {@link ImsCallProfile} with the updated attributes + */ + public void callSessionRttModifyRequestReceived(ImsCallProfile callProfile) + { + try { + mListener.callSessionRttModifyRequestReceived(callProfile); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + } + + /** + * An RTT modify response has been received. + * + * @param status the received response for RTT modify request. + */ + public void callSessionRttModifyResponseReceived(int status) { + try { + mListener.callSessionRttModifyResponseReceived(status); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + } + + /** + * An RTT message has been received from the remote party. + * + * @param rttMessage The RTT message that has been received. + */ + public void callSessionRttMessageReceived(String rttMessage) { + try { + mListener.callSessionRttMessageReceived(rttMessage); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + } +} + diff --git a/telephony/java/com/android/ims/ImsConferenceState.aidl b/telephony/java/android/telephony/ims/ImsConferenceState.aidl index 2fc029f57902..e2b371c1440e 100644 --- a/telephony/java/com/android/ims/ImsConferenceState.aidl +++ b/telephony/java/android/telephony/ims/ImsConferenceState.aidl @@ -14,6 +14,6 @@ * limitations under the License. */ -package com.android.ims; +package android.telephony.ims; parcelable ImsConferenceState; diff --git a/telephony/java/com/android/ims/ImsConferenceState.java b/telephony/java/android/telephony/ims/ImsConferenceState.java index 0afde88b8918..66d2f8d929d3 100644 --- a/telephony/java/com/android/ims/ImsConferenceState.java +++ b/telephony/java/android/telephony/ims/ImsConferenceState.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013 The Android Open Source Project + * Copyright (C) 2018 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -11,16 +11,17 @@ * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and - * limitations under the License. + * limitations under the License */ -package com.android.ims; +package android.telephony.ims; import java.util.HashMap; import java.util.Iterator; import java.util.Map.Entry; import java.util.Set; +import android.annotation.SystemApi; import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; @@ -32,7 +33,8 @@ import android.telecom.Connection; * * @hide */ -public class ImsConferenceState implements Parcelable { +@SystemApi +public final class ImsConferenceState implements Parcelable { /** * conference-info : user */ @@ -87,12 +89,13 @@ public class ImsConferenceState implements Parcelable { */ public static final String SIP_STATUS_CODE = "sipstatuscode"; - public HashMap<String, Bundle> mParticipants = new HashMap<String, Bundle>(); + public final HashMap<String, Bundle> mParticipants = new HashMap<String, Bundle>(); + /** @hide */ public ImsConferenceState() { } - public ImsConferenceState(Parcel in) { + private ImsConferenceState(Parcel in) { readFromParcel(in); } diff --git a/telephony/java/com/android/ims/ImsExternalCallState.aidl b/telephony/java/android/telephony/ims/ImsExternalCallState.aidl index c208702a8c34..99d293566806 100644 --- a/telephony/java/com/android/ims/ImsExternalCallState.aidl +++ b/telephony/java/android/telephony/ims/ImsExternalCallState.aidl @@ -14,6 +14,6 @@ * limitations under the License. */ -package com.android.ims; +package android.telephony.ims; parcelable ImsExternalCallState; diff --git a/telephony/java/com/android/ims/ImsExternalCallState.java b/telephony/java/android/telephony/ims/ImsExternalCallState.java index da2607356d6c..e82c115cb4b3 100644 --- a/telephony/java/com/android/ims/ImsExternalCallState.java +++ b/telephony/java/android/telephony/ims/ImsExternalCallState.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016 The Android Open Source Project + * Copyright (C) 2018 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -11,11 +11,12 @@ * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and - * limitations under the License. + * limitations under the License */ -package com.android.ims; +package android.telephony.ims; +import android.annotation.SystemApi; import android.net.Uri; import android.os.Parcel; import android.os.Parcelable; @@ -32,7 +33,8 @@ import android.telephony.Rlog; * Parcelable object to handle MultiEndpoint Dialog Information * @hide */ -public class ImsExternalCallState implements Parcelable { +@SystemApi +public final class ImsExternalCallState implements Parcelable { private static final String TAG = "ImsExternalCallState"; @@ -50,9 +52,11 @@ public class ImsExternalCallState implements Parcelable { private int mCallType; private boolean mIsHeld; + /** @hide */ public ImsExternalCallState() { } + /** @hide */ public ImsExternalCallState(int callId, Uri address, boolean isPullable, int callState, int callType, boolean isCallheld) { mCallId = callId; @@ -64,6 +68,7 @@ public class ImsExternalCallState implements Parcelable { Rlog.d(TAG, "ImsExternalCallState = " + this); } + /** @hide */ public ImsExternalCallState(Parcel in) { mCallId = in.readInt(); ClassLoader classLoader = ImsExternalCallState.class.getClassLoader(); diff --git a/telephony/java/com/android/ims/ImsReasonInfo.aidl b/telephony/java/android/telephony/ims/ImsReasonInfo.aidl index 17e6d3a729e9..604b323d1292 100644 --- a/telephony/java/com/android/ims/ImsReasonInfo.aidl +++ b/telephony/java/android/telephony/ims/ImsReasonInfo.aidl @@ -14,6 +14,6 @@ * limitations under the License. */ -package com.android.ims; +package android.telephony.ims; parcelable ImsReasonInfo; diff --git a/telephony/java/com/android/ims/ImsReasonInfo.java b/telephony/java/android/telephony/ims/ImsReasonInfo.java index 83d9bd94013e..7b7749102590 100644 --- a/telephony/java/com/android/ims/ImsReasonInfo.java +++ b/telephony/java/android/telephony/ims/ImsReasonInfo.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013 The Android Open Source Project + * Copyright (C) 2018 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -11,11 +11,12 @@ * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and - * limitations under the License. + * limitations under the License */ -package com.android.ims; +package android.telephony.ims; +import android.annotation.SystemApi; import android.os.Parcel; import android.os.Parcelable; @@ -24,7 +25,8 @@ import android.os.Parcelable; * * @hide */ -public class ImsReasonInfo implements Parcelable { +@SystemApi +public final class ImsReasonInfo implements Parcelable { /** * Specific code of each types @@ -418,27 +420,36 @@ public class ImsReasonInfo implements Parcelable { // For main reason code + /** @hide */ public int mCode; // For the extra code value; it depends on the code value. + /** @hide */ public int mExtraCode; // For the additional message of the reason info. + /** @hide */ public String mExtraMessage; + + /** @hide */ public ImsReasonInfo() { mCode = CODE_UNSPECIFIED; mExtraCode = CODE_UNSPECIFIED; mExtraMessage = null; } - public ImsReasonInfo(Parcel in) { - readFromParcel(in); + private ImsReasonInfo(Parcel in) { + mCode = in.readInt(); + mExtraCode = in.readInt(); + mExtraMessage = in.readString(); } + /** @hide */ public ImsReasonInfo(int code, int extraCode) { mCode = code; mExtraCode = extraCode; mExtraMessage = null; } + /** @hide */ public ImsReasonInfo(int code, int extraCode, String extraMessage) { mCode = code; mExtraCode = extraCode; @@ -487,12 +498,6 @@ public class ImsReasonInfo implements Parcelable { out.writeString(mExtraMessage); } - private void readFromParcel(Parcel in) { - mCode = in.readInt(); - mExtraCode = in.readInt(); - mExtraMessage = in.readString(); - } - public static final Creator<ImsReasonInfo> CREATOR = new Creator<ImsReasonInfo>() { @Override public ImsReasonInfo createFromParcel(Parcel in) { diff --git a/telephony/java/android/telephony/ims/ImsService.java b/telephony/java/android/telephony/ims/ImsService.java index aaa0f08594d1..2748cb5470bf 100644 --- a/telephony/java/android/telephony/ims/ImsService.java +++ b/telephony/java/android/telephony/ims/ImsService.java @@ -16,25 +16,28 @@ package android.telephony.ims; -import android.annotation.Nullable; import android.annotation.SystemApi; import android.app.Service; import android.content.Intent; import android.os.IBinder; import android.os.RemoteException; import android.telephony.CarrierConfigManager; +import android.telephony.ims.aidl.IImsConfig; +import android.telephony.ims.aidl.IImsMmTelFeature; +import android.telephony.ims.aidl.IImsRcsFeature; +import android.telephony.ims.aidl.IImsRegistration; +import android.telephony.ims.aidl.IImsServiceController; +import android.telephony.ims.aidl.IImsServiceControllerListener; import android.telephony.ims.feature.ImsFeature; -import android.telephony.ims.feature.MMTelFeature; +import android.telephony.ims.feature.MmTelFeature; import android.telephony.ims.feature.RcsFeature; +import android.telephony.ims.stub.ImsConfigImplBase; +import android.telephony.ims.stub.ImsFeatureConfiguration; import android.telephony.ims.stub.ImsRegistrationImplBase; import android.util.Log; import android.util.SparseArray; import com.android.ims.internal.IImsFeatureStatusCallback; -import com.android.ims.internal.IImsMMTelFeature; -import com.android.ims.internal.IImsRcsFeature; -import com.android.ims.internal.IImsRegistration; -import com.android.ims.internal.IImsServiceController; import com.android.internal.annotations.VisibleForTesting; import static android.Manifest.permission.MODIFY_PHONE_STATE; @@ -48,9 +51,7 @@ import static android.Manifest.permission.MODIFY_PHONE_STATE; * ... * <service android:name=".EgImsService" * android:permission="android.permission.BIND_IMS_SERVICE" > - * <!-- Apps must declare which features they support as metadata. The different categories are - * defined below. In this example, the RCS_FEATURE feature is supported. --> - * <meta-data android:name="android.telephony.ims.RCS_FEATURE" android:value="true" /> + * ... * <intent-filter> * <action android:name="android.telephony.ims.ImsService" /> * </intent-filter> @@ -64,13 +65,31 @@ import static android.Manifest.permission.MODIFY_PHONE_STATE; * 2) Defined as a Carrier Provided ImsService in the Carrier Configuration using * {@link CarrierConfigManager#KEY_CONFIG_IMS_PACKAGE_OVERRIDE_STRING}. * + * There are two ways to define to the platform which {@link ImsFeature}s this {@link ImsService} + * supports, dynamic or static definitions. + * + * In the static definition, the {@link ImsFeature}s that are supported are defined in the service + * definition of the AndroidManifest.xml file as metadata: + * <!-- Apps must declare which features they support as metadata. The different categories are + * defined below. In this example, the MMTEL_FEATURE feature is supported. --> + * <meta-data android:name="android.telephony.ims.MMTEL_FEATURE" android:value="true" /> + * * The features that are currently supported in an ImsService are: * - RCS_FEATURE: This ImsService implements the RcsFeature class. - * - MMTEL_FEATURE: This ImsService implements the MMTelFeature class. - * - EMERGENCY_MMTEL_FEATURE: This ImsService implements the MMTelFeature class and will be - * available to place emergency calls at all times. This MUST be implemented by the default - * ImsService provided in the device overlay. - * @hide + * - MMTEL_FEATURE: This ImsService implements the MmTelFeature class. + * - EMERGENCY_MMTEL_FEATURE: This ImsService supports Emergency Calling for MMTEL, must be + * declared along with the MMTEL_FEATURE. If this is not specified, the framework will use + * circuit switch for emergency calling. + * + * In the dynamic definition, the supported features are not specified in the service definition + * of the AndroidManifest. Instead, the framework binds to this service and calls + * {@link #querySupportedImsFeatures()}. The {@link ImsService} then returns an + * {@link ImsFeatureConfiguration}, which the framework uses to initialize the supported + * {@link ImsFeature}s. If at any time, the list of supported {@link ImsFeature}s changes, + * {@link #onUpdateSupportedImsFeatures(ImsFeatureConfiguration)} can be called to tell the + * framework of the changes. + * + * @hide */ @SystemApi public class ImsService extends Service { @@ -89,20 +108,36 @@ public class ImsService extends Service { // call ImsFeature#onFeatureRemoved. private final SparseArray<SparseArray<ImsFeature>> mFeaturesBySlot = new SparseArray<>(); + private IImsServiceControllerListener mListener; + + /** + * Listener that notifies the framework of ImsService changes. * @hide */ - protected final IBinder mImsServiceController = new IImsServiceController.Stub() { + public static class Listener extends IImsServiceControllerListener.Stub { + /** + * The IMS features that this ImsService supports has changed. + * @param c a new {@link ImsFeatureConfiguration} containing {@link ImsFeature.FeatureType}s + * that this ImsService supports. This may trigger the addition/removal of feature + * in this service. + */ + public void onUpdateSupportedImsFeatures(ImsFeatureConfiguration c) { + } + } + /** + * @hide + */ + protected final IBinder mImsServiceController = new IImsServiceController.Stub() { @Override - public IImsMMTelFeature createEmergencyMMTelFeature(int slotId, - IImsFeatureStatusCallback c) { - return createEmergencyMMTelFeatureInternal(slotId, c); + public void setListener(IImsServiceControllerListener l) { + mListener = l; } @Override - public IImsMMTelFeature createMMTelFeature(int slotId, IImsFeatureStatusCallback c) { - return createMMTelFeatureInternal(slotId, c); + public IImsMmTelFeature createMmTelFeature(int slotId, IImsFeatureStatusCallback c) { + return createMmTelFeatureInternal(slotId, c); } @Override @@ -111,16 +146,46 @@ public class ImsService extends Service { } @Override - public void removeImsFeature(int slotId, int featureType, IImsFeatureStatusCallback c) - throws RemoteException { + public void removeImsFeature(int slotId, int featureType, IImsFeatureStatusCallback c) { ImsService.this.removeImsFeature(slotId, featureType, c); } @Override - public IImsRegistration getRegistration(int slotId) throws RemoteException { + public ImsFeatureConfiguration querySupportedImsFeatures() { + return ImsService.this.querySupportedImsFeatures(); + } + + @Override + public void notifyImsServiceReadyForFeatureCreation() { + ImsService.this.readyForFeatureCreation(); + } + + @Override + public void notifyImsFeatureReady(int slotId, int featureType) { + ImsService.this.notifyImsFeatureReady(slotId, featureType); + } + + @Override + public IImsConfig getConfig(int slotId) { + ImsConfigImplBase c = ImsService.this.getConfig(slotId); + return c != null ? c.getIImsConfig() : null; + } + + @Override + public IImsRegistration getRegistration(int slotId) { ImsRegistrationImplBase r = ImsService.this.getRegistration(slotId); return r != null ? r.getBinder() : null; } + + @Override + public void enableIms(int slotId) { + ImsService.this.enableIms(slotId); + } + + @Override + public void disableIms(int slotId) { + ImsService.this.disableIms(slotId); + } }; /** @@ -143,47 +208,35 @@ public class ImsService extends Service { return mFeaturesBySlot.get(slotId); } - private IImsMMTelFeature createEmergencyMMTelFeatureInternal(int slotId, - IImsFeatureStatusCallback c) { - MMTelFeature f = onCreateEmergencyMMTelImsFeature(slotId); - if (f != null) { - setupFeature(f, slotId, ImsFeature.EMERGENCY_MMTEL, c); - return f.getBinder(); - } else { - return null; - } - } - - private IImsMMTelFeature createMMTelFeatureInternal(int slotId, + private IImsMmTelFeature createMmTelFeatureInternal(int slotId, IImsFeatureStatusCallback c) { - MMTelFeature f = onCreateMMTelImsFeature(slotId); + MmTelFeature f = createMmTelFeature(slotId); if (f != null) { - setupFeature(f, slotId, ImsFeature.MMTEL, c); + setupFeature(f, slotId, ImsFeature.FEATURE_MMTEL, c); return f.getBinder(); } else { + Log.e(LOG_TAG, "createMmTelFeatureInternal: null feature returned."); return null; } } private IImsRcsFeature createRcsFeatureInternal(int slotId, IImsFeatureStatusCallback c) { - RcsFeature f = onCreateRcsFeature(slotId); + RcsFeature f = createRcsFeature(slotId); if (f != null) { - setupFeature(f, slotId, ImsFeature.RCS, c); + setupFeature(f, slotId, ImsFeature.FEATURE_RCS, c); return f.getBinder(); } else { + Log.e(LOG_TAG, "createRcsFeatureInternal: null feature returned."); return null; } } private void setupFeature(ImsFeature f, int slotId, int featureType, IImsFeatureStatusCallback c) { - f.setContext(this); - f.setSlotId(slotId); f.addImsFeatureStatusCallback(c); + f.initialize(this, slotId); addImsFeature(slotId, featureType, f); - // TODO: Remove once new onFeatureReady AIDL is merged in. - f.onFeatureReady(); } private void addImsFeature(int slotId, int featureType, ImsFeature f) { @@ -221,38 +274,122 @@ public class ImsService extends Service { } } + private void notifyImsFeatureReady(int slotId, int featureType) { + synchronized (mFeaturesBySlot) { + // get ImsFeature associated with the slot/feature + SparseArray<ImsFeature> features = mFeaturesBySlot.get(slotId); + if (features == null) { + Log.w(LOG_TAG, "Can not notify ImsFeature ready. No ImsFeatures exist on " + + "slot " + slotId); + return; + } + ImsFeature f = features.get(featureType); + if (f == null) { + Log.w(LOG_TAG, "Can not notify ImsFeature ready. No feature with type " + + featureType + " exists on slot " + slotId); + return; + } + f.onFeatureReady(); + } + } + /** - * @return An implementation of MMTelFeature that will be used by the system for MMTel - * functionality. Must be able to handle emergency calls at any time as well. - * @hide + * When called, provide the {@link ImsFeatureConfiguration} that this {@link ImsService} + * currently supports. This will trigger the framework to set up the {@link ImsFeature}s that + * correspond to the {@link ImsFeature}s configured here. + * + * Use {@link #onUpdateSupportedImsFeatures(ImsFeatureConfiguration)} to change the supported + * {@link ImsFeature}s. + * + * @return an {@link ImsFeatureConfiguration} containing Features this ImsService supports. */ - public @Nullable MMTelFeature onCreateEmergencyMMTelImsFeature(int slotId) { - return null; + public ImsFeatureConfiguration querySupportedImsFeatures() { + // Return empty for base implementation + return new ImsFeatureConfiguration(); } /** - * @return An implementation of MMTelFeature that will be used by the system for MMTel - * functionality. - * @hide + * Updates the framework with a new {@link ImsFeatureConfiguration} containing the updated + * features, that this {@link ImsService} supports. This may trigger the framework to add/remove + * new ImsFeatures, depending on the configuration. */ - public @Nullable MMTelFeature onCreateMMTelImsFeature(int slotId) { + public final void onUpdateSupportedImsFeatures(ImsFeatureConfiguration c) + throws RemoteException { + if (mListener == null) { + throw new IllegalStateException("Framework is not ready"); + } + mListener.onUpdateSupportedImsFeatures(c); + } + + /** + * The ImsService has been bound and is ready for ImsFeature creation based on the Features that + * the ImsService has registered for with the framework, either in the manifest or via + * {@link #querySupportedImsFeatures()}. + * + * The ImsService should use this signal instead of onCreate/onBind or similar to perform + * feature initialization because the framework may bind to this service multiple times to + * query the ImsService's {@link ImsFeatureConfiguration} via + * {@link #querySupportedImsFeatures()}before creating features. + */ + public void readyForFeatureCreation() { + } + + /** + * The framework has enabled IMS for the slot specified, the ImsService should register for IMS + * and perform all appropriate initialization to bring up all ImsFeatures. + */ + public void enableIms(int slotId) { + } + + /** + * The framework has disabled IMS for the slot specified. The ImsService must deregister for IMS + * and set capability status to false for all ImsFeatures. + */ + public void disableIms(int slotId) { + } + + /** + * When called, the framework is requesting that a new {@link MmTelFeature} is created for the + * specified slot. + * + * @param slotId The slot ID that the MMTEL Feature is being created for. + * @return The newly created {@link MmTelFeature} associated with the slot or null if the + * feature is not supported. + */ + public MmTelFeature createMmTelFeature(int slotId) { return null; } /** - * @return An implementation of RcsFeature that will be used by the system for RCS. - * @hide + * When called, the framework is requesting that a new {@link RcsFeature} is created for the + * specified slot. + * + * @param slotId The slot ID that the RCS Feature is being created for. + * @return The newly created {@link RcsFeature} associated with the slot or null if the feature + * is not supported. */ - public @Nullable RcsFeature onCreateRcsFeature(int slotId) { + public RcsFeature createRcsFeature(int slotId) { return null; } /** + * Return the {@link ImsConfigImplBase} implementation associated with the provided slot. This + * will be used by the platform to get/set specific IMS related configurations. + * + * @param slotId The slot that the IMS configuration is associated with. + * @return ImsConfig implementation that is associated with the specified slot. + */ + public ImsConfigImplBase getConfig(int slotId) { + return new ImsConfigImplBase(); + } + + /** + * Return the {@link ImsRegistrationImplBase} implementation associated with the provided slot. + * * @param slotId The slot that is associated with the IMS Registration. * @return the ImsRegistration implementation associated with the slot. - * @hide */ public ImsRegistrationImplBase getRegistration(int slotId) { return new ImsRegistrationImplBase(); } -} +}
\ No newline at end of file diff --git a/telephony/java/com/android/ims/ImsSsData.aidl b/telephony/java/android/telephony/ims/ImsSsData.aidl index 33f83067a4df..eff3a6b0acc7 100644 --- a/telephony/java/com/android/ims/ImsSsData.aidl +++ b/telephony/java/android/telephony/ims/ImsSsData.aidl @@ -14,6 +14,6 @@ * limitations under the License. */ -package com.android.ims; +package android.telephony.ims; parcelable ImsSsData; diff --git a/telephony/java/com/android/ims/ImsSsData.java b/telephony/java/android/telephony/ims/ImsSsData.java index 7336c133af96..1ddf1994f26b 100644 --- a/telephony/java/com/android/ims/ImsSsData.java +++ b/telephony/java/android/telephony/ims/ImsSsData.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017 The Android Open Source Project + * Copyright (C) 2018 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -11,21 +11,21 @@ * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and - * limitations under the License. + * limitations under the License */ -package com.android.ims; +package android.telephony.ims; +import android.annotation.SystemApi; import android.os.Parcel; import android.os.Parcelable; -import java.util.ArrayList; - /** * Provided STK Call Control Suplementary Service information * * {@hide} */ -public class ImsSsData implements Parcelable { +@SystemApi +public final class ImsSsData implements Parcelable { //ServiceType public static final int SS_CFU = 0; @@ -68,30 +68,38 @@ public class ImsSsData implements Parcelable { public static final int SS_ALL_TELESERVICES_EXCEPT_SMS = 5; // Refer to ServiceType + /** @hide */ public int serviceType; // Refere to SSRequestType + /** @hide */ public int requestType; // Refer to TeleserviceType + /** @hide */ public int teleserviceType; // Service Class + /** @hide */ public int serviceClass; // Error information + /** @hide */ public int result; + /** @hide */ public int[] ssInfo; /* Valid for all supplementary services. This field will be empty for RequestType SS_INTERROGATION and ServiceType SS_CF_*, SS_INCOMING_BARRING_DN, SS_INCOMING_BARRING_ANONYMOUS.*/ + /** @hide */ public ImsCallForwardInfo[] cfInfo; /* Valid only for supplementary services ServiceType SS_CF_* and RequestType SS_INTERROGATION */ + /** @hide */ public ImsSsInfo[] imsSsInfo; /* Valid only for ServiceType SS_INCOMING_BARRING_DN and ServiceType SS_INCOMING_BARRING_ANONYMOUS */ public ImsSsData() {} - public ImsSsData(Parcel in) { + private ImsSsData(Parcel in) { readFromParcel(in); } @@ -133,20 +141,36 @@ public class ImsSsData implements Parcelable { return 0; } + /** + * Old method, kept for compatibility. See {@link #isTypeCf()} + * @hide + */ public boolean isTypeCF() { return (serviceType == SS_CFU || serviceType == SS_CF_BUSY || serviceType == SS_CF_NO_REPLY || serviceType == SS_CF_NOT_REACHABLE || serviceType == SS_CF_ALL || serviceType == SS_CF_ALL_CONDITIONAL); } + public boolean isTypeCf() { + return isTypeCF(); + } + public boolean isTypeUnConditional() { return (serviceType == SS_CFU || serviceType == SS_CF_ALL); } + /** + * Old method, kept for compatibility. See {@link #isTypeCf()} + * @hide + */ public boolean isTypeCW() { return (serviceType == SS_WAIT); } + public boolean isTypeCw() { + return isTypeCW(); + } + public boolean isTypeClip() { return (serviceType == SS_CLIP); } diff --git a/telephony/java/com/android/ims/ImsSsInfo.aidl b/telephony/java/android/telephony/ims/ImsSsInfo.aidl index 0ac598b70218..66d49507c127 100644 --- a/telephony/java/com/android/ims/ImsSsInfo.aidl +++ b/telephony/java/android/telephony/ims/ImsSsInfo.aidl @@ -14,6 +14,6 @@ * limitations under the License. */ -package com.android.ims; +package android.telephony.ims; parcelable ImsSsInfo; diff --git a/telephony/java/com/android/ims/ImsSsInfo.java b/telephony/java/android/telephony/ims/ImsSsInfo.java index 7acc3bfd45c9..1d1292fb9f72 100644 --- a/telephony/java/com/android/ims/ImsSsInfo.java +++ b/telephony/java/android/telephony/ims/ImsSsInfo.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013 The Android Open Source Project + * Copyright (C) 2018 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -11,11 +11,12 @@ * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and - * limitations under the License. + * limitations under the License */ -package com.android.ims; +package android.telephony.ims; +import android.annotation.SystemApi; import android.os.Parcel; import android.os.Parcelable; @@ -24,7 +25,8 @@ import android.os.Parcelable; * * @hide */ -public class ImsSsInfo implements Parcelable { +@SystemApi +public final class ImsSsInfo implements Parcelable { /** * For the status of service registration or activation/deactivation. */ @@ -33,13 +35,15 @@ public class ImsSsInfo implements Parcelable { public static final int ENABLED = 1; // 0: disabled, 1: enabled + /** @hide */ public int mStatus; + /** @hide */ public String mIcbNum; public ImsSsInfo() { } - public ImsSsInfo(Parcel in) { + private ImsSsInfo(Parcel in) { readFromParcel(in); } @@ -76,4 +80,12 @@ public class ImsSsInfo implements Parcelable { return new ImsSsInfo[size]; } }; + + public int getStatus() { + return mStatus; + } + + public String getIcbNum() { + return mIcbNum; + } } diff --git a/telephony/java/com/android/ims/ImsStreamMediaProfile.aidl b/telephony/java/android/telephony/ims/ImsStreamMediaProfile.aidl index d648a3569c11..ee321aec29d8 100644 --- a/telephony/java/com/android/ims/ImsStreamMediaProfile.aidl +++ b/telephony/java/android/telephony/ims/ImsStreamMediaProfile.aidl @@ -14,6 +14,6 @@ * limitations under the License. */ -package com.android.ims; +package android.telephony.ims; parcelable ImsStreamMediaProfile; diff --git a/telephony/java/com/android/ims/ImsStreamMediaProfile.java b/telephony/java/android/telephony/ims/ImsStreamMediaProfile.java index cfe37b524347..243352bdd180 100644 --- a/telephony/java/com/android/ims/ImsStreamMediaProfile.java +++ b/telephony/java/android/telephony/ims/ImsStreamMediaProfile.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013 The Android Open Source Project + * Copyright (C) 2018 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -11,11 +11,12 @@ * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and - * limitations under the License. + * limitations under the License */ -package com.android.ims; +package android.telephony.ims; +import android.annotation.SystemApi; import android.os.Parcel; import android.os.Parcelable; @@ -25,7 +26,8 @@ import android.os.Parcelable; * * @hide */ -public class ImsStreamMediaProfile implements Parcelable { +@SystemApi +public final class ImsStreamMediaProfile implements Parcelable { private static final String TAG = "ImsStreamMediaProfile"; /** @@ -79,18 +81,25 @@ public class ImsStreamMediaProfile implements Parcelable { public static final int RTT_MODE_FULL = 1; // Audio related information + /** @hide */ public int mAudioQuality; + /** @hide */ public int mAudioDirection; // Video related information + /** @hide */ public int mVideoQuality; + /** @hide */ public int mVideoDirection; // Rtt related information + /** @hide */ public int mRttMode; + /** @hide */ public ImsStreamMediaProfile(Parcel in) { readFromParcel(in); } + /** @hide */ public ImsStreamMediaProfile() { mAudioQuality = AUDIO_QUALITY_NONE; mAudioDirection = DIRECTION_SEND_RECEIVE; @@ -99,6 +108,7 @@ public class ImsStreamMediaProfile implements Parcelable { mRttMode = RTT_MODE_DISABLED; } + /** @hide */ public ImsStreamMediaProfile(int audioQuality, int audioDirection, int videoQuality, int videoDirection) { mAudioQuality = audioQuality; @@ -107,6 +117,7 @@ public class ImsStreamMediaProfile implements Parcelable { mVideoDirection = videoDirection; } + /** @hide */ public ImsStreamMediaProfile(int rttMode) { mRttMode = rttMode; } @@ -178,4 +189,23 @@ public class ImsStreamMediaProfile implements Parcelable { mRttMode = rttMode; } + public int getAudioQuality() { + return mAudioQuality; + } + + public int getAudioDirection() { + return mAudioDirection; + } + + public int getVideoQuality() { + return mVideoQuality; + } + + public int getVideoDirection() { + return mVideoDirection; + } + + public int getRttMode() { + return mRttMode; + } } diff --git a/telephony/java/com/android/ims/ImsSuppServiceNotification.aidl b/telephony/java/android/telephony/ims/ImsSuppServiceNotification.aidl index 6b4479f467c2..0552780c7dd3 100644 --- a/telephony/java/com/android/ims/ImsSuppServiceNotification.aidl +++ b/telephony/java/android/telephony/ims/ImsSuppServiceNotification.aidl @@ -15,6 +15,6 @@ */ -package com.android.ims; +package android.telephony.ims; parcelable ImsSuppServiceNotification; diff --git a/telephony/java/com/android/ims/ImsSuppServiceNotification.java b/telephony/java/android/telephony/ims/ImsSuppServiceNotification.java index faf749972b91..efaade825224 100644 --- a/telephony/java/com/android/ims/ImsSuppServiceNotification.java +++ b/telephony/java/android/telephony/ims/ImsSuppServiceNotification.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015 The Android Open Source Project + * Copyright (C) 2018 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -11,12 +11,13 @@ * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and - * limitations under the License. + * limitations under the License */ -package com.android.ims; +package android.telephony.ims; +import android.annotation.SystemApi; import android.os.Parcel; import android.os.Parcelable; @@ -28,27 +29,42 @@ import java.util.Arrays; * * @hide */ -public class ImsSuppServiceNotification implements Parcelable { +@SystemApi +public final class ImsSuppServiceNotification implements Parcelable { private static final String TAG = "ImsSuppServiceNotification"; /** Type of notification: 0 = MO; 1 = MT */ - public int notificationType; + public final int notificationType; /** TS 27.007 7.17 "code1" or "code2" */ - public int code; + public final int code; /** TS 27.007 7.17 "index" - Not used currently*/ - public int index; + public final int index; /** TS 27.007 7.17 "type" (MT only) - Not used currently */ - public int type; + public final int type; /** TS 27.007 7.17 "number" (MT only) */ - public String number; + public final String number; /** List of forwarded numbers, if any */ - public String[] history; + public final String[] history; - public ImsSuppServiceNotification() { + + public ImsSuppServiceNotification(int notificationType, int code, int index, int type, + String number, String[] history) { + this.notificationType = notificationType; + this.code = code; + this.index = index; + this.type = type; + this.number = number; + this.history = history; } + /** @hide */ public ImsSuppServiceNotification(Parcel in) { - readFromParcel(in); + notificationType = in.readInt(); + code = in.readInt(); + index = in.readInt(); + type = in.readInt(); + number = in.readString(); + history = in.createStringArray(); } @Override @@ -77,15 +93,6 @@ public class ImsSuppServiceNotification implements Parcelable { out.writeStringArray(history); } - private void readFromParcel(Parcel in) { - notificationType = in.readInt(); - code = in.readInt(); - index = in.readInt(); - type = in.readInt(); - number = in.readString(); - history = in.createStringArray(); - } - public static final Creator<ImsSuppServiceNotification> CREATOR = new Creator<ImsSuppServiceNotification>() { @Override diff --git a/telephony/java/android/telephony/ims/ImsUtListener.java b/telephony/java/android/telephony/ims/ImsUtListener.java new file mode 100644 index 000000000000..d50a0f738b25 --- /dev/null +++ b/telephony/java/android/telephony/ims/ImsUtListener.java @@ -0,0 +1,108 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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.telephony.ims; + +import android.annotation.SystemApi; +import android.os.Bundle; +import android.os.RemoteException; +import android.util.Log; + +import com.android.ims.internal.IImsUtListener; + +/** + * Base implementation of the IMS UT listener interface, which implements stubs. + * Override these methods to implement functionality. + * @hide + */ +// DO NOT remove or change the existing APIs, only add new ones to this Base implementation or you +// will break other implementations of ImsUt maintained by other ImsServices. +@SystemApi +public class ImsUtListener { + private IImsUtListener mServiceInterface; + private static final String LOG_TAG = "ImsUtListener"; + + public void onUtConfigurationUpdated(int id) { + try { + mServiceInterface.utConfigurationUpdated(null, id); + } catch (RemoteException e) { + Log.w(LOG_TAG, "utConfigurationUpdated: remote exception"); + } + } + + public void onUtConfigurationUpdateFailed(int id, ImsReasonInfo error) { + try { + mServiceInterface.utConfigurationUpdateFailed(null, id, error); + } catch (RemoteException e) { + Log.w(LOG_TAG, "utConfigurationUpdateFailed: remote exception"); + } + } + + public void onUtConfigurationQueried(int id, Bundle ssInfo) { + try { + mServiceInterface.utConfigurationQueried(null, id, ssInfo); + } catch (RemoteException e) { + Log.w(LOG_TAG, "utConfigurationQueried: remote exception"); + } + } + + public void onUtConfigurationQueryFailed(int id, ImsReasonInfo error) { + try { + mServiceInterface.utConfigurationQueryFailed(null, id, error); + } catch (RemoteException e) { + Log.w(LOG_TAG, "utConfigurationQueryFailed: remote exception"); + } + } + + public void onUtConfigurationCallBarringQueried(int id, ImsSsInfo[] cbInfo) { + try { + mServiceInterface.utConfigurationCallBarringQueried(null, id, cbInfo); + } catch (RemoteException e) { + Log.w(LOG_TAG, "utConfigurationCallBarringQueried: remote exception"); + } + } + + public void onUtConfigurationCallForwardQueried(int id, ImsCallForwardInfo[] cfInfo) { + try { + mServiceInterface.utConfigurationCallForwardQueried(null, id, cfInfo); + } catch (RemoteException e) { + Log.w(LOG_TAG, "utConfigurationCallForwardQueried: remote exception"); + } + } + + public void onUtConfigurationCallWaitingQueried(int id, ImsSsInfo[] cwInfo) { + try { + mServiceInterface.utConfigurationCallWaitingQueried(null, id, cwInfo); + } catch (RemoteException e) { + Log.w(LOG_TAG, "utConfigurationCallWaitingQueried: remote exception"); + } + } + + public void onSupplementaryServiceIndication(ImsSsData ssData) { + try { + mServiceInterface.onSupplementaryServiceIndication(ssData); + } catch (RemoteException e) { + Log.w(LOG_TAG, "onSupplementaryServiceIndication: remote exception"); + } + } + + /** + * @hide + */ + public ImsUtListener(IImsUtListener serviceInterface) { + mServiceInterface = serviceInterface; + } +} diff --git a/telephony/java/com/android/ims/internal/ImsVideoCallProvider.java b/telephony/java/android/telephony/ims/ImsVideoCallProvider.java index 432dc3905737..b4f60b952a00 100644 --- a/telephony/java/com/android/ims/internal/ImsVideoCallProvider.java +++ b/telephony/java/android/telephony/ims/ImsVideoCallProvider.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2017 The Android Open Source Project + * Copyright (C) 2018 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,8 +14,9 @@ * limitations under the License */ -package com.android.ims.internal; +package android.telephony.ims; +import android.annotation.SystemApi; import android.net.Uri; import android.os.Handler; import android.os.Looper; @@ -26,11 +27,14 @@ import android.telecom.VideoProfile; import android.telecom.VideoProfile.CameraCapabilities; import android.view.Surface; +import com.android.ims.internal.IImsVideoCallCallback; +import com.android.ims.internal.IImsVideoCallProvider; import com.android.internal.os.SomeArgs; /** * @hide */ +@SystemApi public abstract class ImsVideoCallProvider { private static final int MSG_SET_CALLBACK = 1; private static final int MSG_SET_CAMERA = 2; @@ -173,6 +177,7 @@ public abstract class ImsVideoCallProvider { /** * Returns binder object which can be used across IPC methods. + * @hide */ public final IImsVideoCallProvider getInterface() { return mBinder; diff --git a/telephony/java/android/telephony/ims/internal/aidl/IImsCallSessionListener.aidl b/telephony/java/android/telephony/ims/aidl/IImsCallSessionListener.aidl index 2fb67442fa34..f25b4b148605 100644 --- a/telephony/java/android/telephony/ims/internal/aidl/IImsCallSessionListener.aidl +++ b/telephony/java/android/telephony/ims/aidl/IImsCallSessionListener.aidl @@ -14,14 +14,14 @@ * limitations under the License. */ -package android.telephony.ims.internal.aidl; +package android.telephony.ims.aidl; -import com.android.ims.ImsStreamMediaProfile; -import com.android.ims.ImsCallProfile; -import com.android.ims.ImsReasonInfo; -import com.android.ims.ImsConferenceState; +import android.telephony.ims.ImsStreamMediaProfile; +import android.telephony.ims.ImsCallProfile; +import android.telephony.ims.ImsReasonInfo; +import android.telephony.ims.ImsConferenceState; import com.android.ims.internal.IImsCallSession; -import com.android.ims.ImsSuppServiceNotification; +import android.telephony.ims.ImsSuppServiceNotification; /** * A listener type for receiving notification on IMS call session events. diff --git a/telephony/java/android/telephony/ims/internal/aidl/IImsCapabilityCallback.aidl b/telephony/java/android/telephony/ims/aidl/IImsCapabilityCallback.aidl index fd2eb24610ec..c755703c042a 100644 --- a/telephony/java/android/telephony/ims/internal/aidl/IImsCapabilityCallback.aidl +++ b/telephony/java/android/telephony/ims/aidl/IImsCapabilityCallback.aidl @@ -14,7 +14,7 @@ * limitations under the License. */ -package android.telephony.ims.internal.aidl; +package android.telephony.ims.aidl; /** * See ImsFeature#CapabilityCallback for more information. diff --git a/telephony/java/android/telephony/ims/internal/aidl/IImsConfig.aidl b/telephony/java/android/telephony/ims/aidl/IImsConfig.aidl index 3d424a33012d..4433c1c03c1f 100644 --- a/telephony/java/android/telephony/ims/internal/aidl/IImsConfig.aidl +++ b/telephony/java/android/telephony/ims/aidl/IImsConfig.aidl @@ -15,9 +15,9 @@ */ -package android.telephony.ims.internal.aidl; +package android.telephony.ims.aidl; -import android.telephony.ims.internal.aidl.IImsConfigCallback; +import android.telephony.ims.aidl.IImsConfigCallback; import com.android.ims.ImsConfigListener; diff --git a/telephony/java/android/telephony/ims/internal/aidl/IImsConfigCallback.aidl b/telephony/java/android/telephony/ims/aidl/IImsConfigCallback.aidl index 52efd2322c17..2b3f1ca7196a 100644 --- a/telephony/java/android/telephony/ims/internal/aidl/IImsConfigCallback.aidl +++ b/telephony/java/android/telephony/ims/aidl/IImsConfigCallback.aidl @@ -15,7 +15,7 @@ */ -package android.telephony.ims.internal.aidl; +package android.telephony.ims.aidl; /** * Provides callback interface for ImsConfig when a value has changed. diff --git a/telephony/java/android/telephony/ims/internal/aidl/IImsMmTelFeature.aidl b/telephony/java/android/telephony/ims/aidl/IImsMmTelFeature.aidl index e226adaac07f..b9a6b3c38a92 100644 --- a/telephony/java/android/telephony/ims/internal/aidl/IImsMmTelFeature.aidl +++ b/telephony/java/android/telephony/ims/aidl/IImsMmTelFeature.aidl @@ -14,15 +14,15 @@ * limitations under the License. */ -package android.telephony.ims.internal.aidl; +package android.telephony.ims.aidl; import android.os.Message; -import android.telephony.ims.internal.aidl.IImsMmTelListener; -import android.telephony.ims.internal.aidl.IImsCapabilityCallback; -import android.telephony.ims.internal.aidl.IImsCallSessionListener; -import android.telephony.ims.internal.feature.CapabilityChangeRequest; +import android.telephony.ims.aidl.IImsMmTelListener; +import android.telephony.ims.aidl.IImsSmsListener; +import android.telephony.ims.aidl.IImsCapabilityCallback; +import android.telephony.ims.feature.CapabilityChangeRequest; -import com.android.ims.ImsCallProfile; +import android.telephony.ims.ImsCallProfile; import com.android.ims.internal.IImsCallSession; import com.android.ims.internal.IImsEcbm; import com.android.ims.internal.IImsMultiEndpoint; @@ -30,14 +30,15 @@ import com.android.ims.internal.IImsRegistrationListener; import com.android.ims.internal.IImsUt; /** - * See SmsImplBase for more information. + * See MmTelFeature for more information. * {@hide} */ interface IImsMmTelFeature { void setListener(IImsMmTelListener l); int getFeatureState(); ImsCallProfile createCallProfile(int callSessionType, int callType); - IImsCallSession createCallSession(in ImsCallProfile profile, IImsCallSessionListener listener); + IImsCallSession createCallSession(in ImsCallProfile profile); + int shouldProcessCall(in String[] uris); IImsUt getUtInterface(); IImsEcbm getEcbmInterface(); void setUiTtyMode(int uiTtyMode, in Message onCompleteMessage); @@ -49,4 +50,12 @@ interface IImsMmTelFeature { IImsCapabilityCallback c); oneway void queryCapabilityConfiguration(int capability, int radioTech, IImsCapabilityCallback c); + // SMS APIs + void setSmsListener(IImsSmsListener l); + oneway void sendSms(in int token, int messageRef, String format, String smsc, boolean retry, + in byte[] pdu); + oneway void acknowledgeSms(int token, int messageRef, int result); + oneway void acknowledgeSmsReport(int token, int messageRef, int result); + String getSmsFormat(); + oneway void onSmsReady(); } diff --git a/telephony/java/android/telephony/ims/internal/aidl/IImsMmTelListener.aidl b/telephony/java/android/telephony/ims/aidl/IImsMmTelListener.aidl index 43f5098af3ca..904e7cad9c14 100644 --- a/telephony/java/android/telephony/ims/internal/aidl/IImsMmTelListener.aidl +++ b/telephony/java/android/telephony/ims/aidl/IImsMmTelListener.aidl @@ -14,7 +14,9 @@ * limitations under the License. */ -package android.telephony.ims.internal.aidl; +package android.telephony.ims.aidl; + +import android.os.Bundle; import com.android.ims.internal.IImsCallSession; @@ -23,6 +25,6 @@ import com.android.ims.internal.IImsCallSession; * {@hide} */ oneway interface IImsMmTelListener { - void onIncomingCall(IImsCallSession c); + void onIncomingCall(IImsCallSession c, in Bundle extras); void onVoiceMessageCountUpdate(int count); }
\ No newline at end of file diff --git a/telephony/java/android/telephony/ims/internal/aidl/IImsRcsFeature.aidl b/telephony/java/android/telephony/ims/aidl/IImsRcsFeature.aidl index f6005b66bd3c..691cfba9a28c 100644 --- a/telephony/java/android/telephony/ims/internal/aidl/IImsRcsFeature.aidl +++ b/telephony/java/android/telephony/ims/aidl/IImsRcsFeature.aidl @@ -14,7 +14,7 @@ * limitations under the License. */ -package android.telephony.ims.internal.aidl; +package android.telephony.ims.aidl; /** * See RcsFeature for more information. diff --git a/telephony/java/com/android/ims/internal/IImsRegistration.aidl b/telephony/java/android/telephony/ims/aidl/IImsRegistration.aidl index 6de264ec90fb..4ae0a75ad027 100644 --- a/telephony/java/com/android/ims/internal/IImsRegistration.aidl +++ b/telephony/java/android/telephony/ims/aidl/IImsRegistration.aidl @@ -15,9 +15,9 @@ */ -package com.android.ims.internal; +package android.telephony.ims.aidl; -import com.android.ims.internal.IImsRegistrationCallback; +import android.telephony.ims.aidl.IImsRegistrationCallback; /** * See ImsRegistration for more information. diff --git a/telephony/java/com/android/ims/internal/IImsRegistrationCallback.aidl b/telephony/java/android/telephony/ims/aidl/IImsRegistrationCallback.aidl index 5f21167422dc..4f37caa33680 100644 --- a/telephony/java/com/android/ims/internal/IImsRegistrationCallback.aidl +++ b/telephony/java/android/telephony/ims/aidl/IImsRegistrationCallback.aidl @@ -15,12 +15,12 @@ */ -package com.android.ims.internal; +package android.telephony.ims.aidl; import android.net.Uri; -import android.telephony.ims.internal.stub.ImsFeatureConfiguration; +import android.telephony.ims.stub.ImsFeatureConfiguration; -import com.android.ims.ImsReasonInfo; +import android.telephony.ims.ImsReasonInfo; /** * See ImsRegistrationImplBase.Callback for more information. diff --git a/telephony/java/android/telephony/ims/internal/aidl/IImsServiceController.aidl b/telephony/java/android/telephony/ims/aidl/IImsServiceController.aidl index 82a85254bbca..86f8606ac39f 100644 --- a/telephony/java/android/telephony/ims/internal/aidl/IImsServiceController.aidl +++ b/telephony/java/android/telephony/ims/aidl/IImsServiceController.aidl @@ -14,16 +14,16 @@ * limitations under the License. */ -package android.telephony.ims.internal.aidl; +package android.telephony.ims.aidl; -import android.telephony.ims.internal.aidl.IImsMmTelFeature; -import android.telephony.ims.internal.aidl.IImsRcsFeature; -import android.telephony.ims.internal.aidl.IImsConfig; -import android.telephony.ims.internal.aidl.IImsServiceControllerListener; -import android.telephony.ims.internal.stub.ImsFeatureConfiguration; +import android.telephony.ims.aidl.IImsMmTelFeature; +import android.telephony.ims.aidl.IImsRcsFeature; +import android.telephony.ims.aidl.IImsConfig; +import android.telephony.ims.aidl.IImsRegistration; +import android.telephony.ims.aidl.IImsServiceControllerListener; +import android.telephony.ims.stub.ImsFeatureConfiguration; import com.android.ims.internal.IImsFeatureStatusCallback; -import com.android.ims.internal.IImsRegistration; /** * See ImsService and MmTelFeature for more information. @@ -41,4 +41,6 @@ interface IImsServiceController { void removeImsFeature(int slotId, int featureType, in IImsFeatureStatusCallback c); IImsConfig getConfig(int slotId); IImsRegistration getRegistration(int slotId); + oneway void enableIms(int slotId); + oneway void disableIms(int slotId); } diff --git a/telephony/java/android/telephony/ims/internal/aidl/IImsServiceControllerListener.aidl b/telephony/java/android/telephony/ims/aidl/IImsServiceControllerListener.aidl index 01cca2db0978..54f6120aa99c 100644 --- a/telephony/java/android/telephony/ims/internal/aidl/IImsServiceControllerListener.aidl +++ b/telephony/java/android/telephony/ims/aidl/IImsServiceControllerListener.aidl @@ -14,9 +14,9 @@ * limitations under the License. */ -package android.telephony.ims.internal.aidl; +package android.telephony.ims.aidl; -import android.telephony.ims.internal.stub.ImsFeatureConfiguration; +import android.telephony.ims.stub.ImsFeatureConfiguration; /** * See ImsService#Listener for more information. diff --git a/telephony/java/com/android/ims/internal/IImsSmsListener.aidl b/telephony/java/android/telephony/ims/aidl/IImsSmsListener.aidl index 5a4b7e489025..606df15b1782 100644 --- a/telephony/java/com/android/ims/internal/IImsSmsListener.aidl +++ b/telephony/java/android/telephony/ims/aidl/IImsSmsListener.aidl @@ -14,13 +14,13 @@ * limitations under the License. */ -package com.android.ims.internal; +package android.telephony.ims.aidl; /** * See SmsImplBase for more information. * {@hide} */ -interface IImsSmsListener { +oneway interface IImsSmsListener { void onSendSmsResult(int token, int messageRef, int status, int reason); void onSmsStatusReportReceived(int token, int messageRef, in String format, in byte[] pdu); diff --git a/telephony/java/android/telephony/ims/compat/ImsService.java b/telephony/java/android/telephony/ims/compat/ImsService.java new file mode 100644 index 000000000000..cf1efb34bd0a --- /dev/null +++ b/telephony/java/android/telephony/ims/compat/ImsService.java @@ -0,0 +1,237 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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.telephony.ims.compat; + +import android.annotation.Nullable; +import android.app.Service; +import android.content.Intent; +import android.os.IBinder; +import android.os.RemoteException; +import android.telephony.CarrierConfigManager; +import android.telephony.ims.compat.feature.ImsFeature; +import android.telephony.ims.compat.feature.MMTelFeature; +import android.telephony.ims.compat.feature.RcsFeature; +import android.util.Log; +import android.util.SparseArray; + +import com.android.ims.internal.IImsFeatureStatusCallback; +import com.android.ims.internal.IImsMMTelFeature; +import com.android.ims.internal.IImsRcsFeature; +import com.android.ims.internal.IImsServiceController; +import com.android.internal.annotations.VisibleForTesting; + +/** + * Main ImsService implementation, which binds via the Telephony ImsResolver. Services that extend + * ImsService must register the service in their AndroidManifest to be detected by the framework. + * First, the application must declare that they use the "android.permission.BIND_IMS_SERVICE" + * permission. Then, the ImsService definition in the manifest must follow the following format: + * + * ... + * <service android:name=".EgImsService" + * android:permission="android.permission.BIND_IMS_SERVICE" > + * <!-- Apps must declare which features they support as metadata. The different categories are + * defined below. In this example, the RCS_FEATURE feature is supported. --> + * <meta-data android:name="android.telephony.ims.RCS_FEATURE" android:value="true" /> + * <intent-filter> + * <action android:name="android.telephony.ims.compat.ImsService" /> + * </intent-filter> + * </service> + * ... + * + * The telephony framework will then bind to the ImsService you have defined in your manifest + * if you are either: + * 1) Defined as the default ImsService for the device in the device overlay using + * "config_ims_package". + * 2) Defined as a Carrier Provided ImsService in the Carrier Configuration using + * {@link CarrierConfigManager#KEY_CONFIG_IMS_PACKAGE_OVERRIDE_STRING}. + * + * The features that are currently supported in an ImsService are: + * - RCS_FEATURE: This ImsService implements the RcsFeature class. + * - MMTEL_FEATURE: This ImsService implements the MMTelFeature class. + * - EMERGENCY_MMTEL_FEATURE: This ImsService implements the MMTelFeature class and will be + * available to place emergency calls at all times. This MUST be implemented by the default + * ImsService provided in the device overlay. + * @hide + */ +public class ImsService extends Service { + + private static final String LOG_TAG = "ImsService(Compat)"; + + /** + * The intent that must be defined as an intent-filter in the AndroidManifest of the ImsService. + * @hide + */ + public static final String SERVICE_INTERFACE = "android.telephony.ims.compat.ImsService"; + + // A map of slot Id -> map of features (indexed by ImsFeature feature id) corresponding to that + // slot. + // We keep track of this to facilitate cleanup of the IImsFeatureStatusCallback and + // call ImsFeature#onFeatureRemoved. + private final SparseArray<SparseArray<ImsFeature>> mFeaturesBySlot = new SparseArray<>(); + + /** + * @hide + */ + protected final IBinder mImsServiceController = new IImsServiceController.Stub() { + + @Override + public IImsMMTelFeature createEmergencyMMTelFeature(int slotId, + IImsFeatureStatusCallback c) { + return createEmergencyMMTelFeatureInternal(slotId, c); + } + + @Override + public IImsMMTelFeature createMMTelFeature(int slotId, IImsFeatureStatusCallback c) { + return createMMTelFeatureInternal(slotId, c); + } + + @Override + public IImsRcsFeature createRcsFeature(int slotId, IImsFeatureStatusCallback c) { + return createRcsFeatureInternal(slotId, c); + } + + @Override + public void removeImsFeature(int slotId, int featureType, IImsFeatureStatusCallback c) + throws RemoteException { + ImsService.this.removeImsFeature(slotId, featureType, c); + } + }; + + /** + * @hide + */ + @Override + public IBinder onBind(Intent intent) { + if(SERVICE_INTERFACE.equals(intent.getAction())) { + Log.i(LOG_TAG, "ImsService(Compat) Bound."); + return mImsServiceController; + } + return null; + } + + /** + * @hide + */ + @VisibleForTesting + public SparseArray<ImsFeature> getFeatures(int slotId) { + return mFeaturesBySlot.get(slotId); + } + + private IImsMMTelFeature createEmergencyMMTelFeatureInternal(int slotId, + IImsFeatureStatusCallback c) { + MMTelFeature f = onCreateEmergencyMMTelImsFeature(slotId); + if (f != null) { + setupFeature(f, slotId, ImsFeature.EMERGENCY_MMTEL, c); + return f.getBinder(); + } else { + return null; + } + } + + private IImsMMTelFeature createMMTelFeatureInternal(int slotId, + IImsFeatureStatusCallback c) { + MMTelFeature f = onCreateMMTelImsFeature(slotId); + if (f != null) { + setupFeature(f, slotId, ImsFeature.MMTEL, c); + return f.getBinder(); + } else { + return null; + } + } + + private IImsRcsFeature createRcsFeatureInternal(int slotId, + IImsFeatureStatusCallback c) { + RcsFeature f = onCreateRcsFeature(slotId); + if (f != null) { + setupFeature(f, slotId, ImsFeature.RCS, c); + return f.getBinder(); + } else { + return null; + } + } + + private void setupFeature(ImsFeature f, int slotId, int featureType, + IImsFeatureStatusCallback c) { + f.setContext(this); + f.setSlotId(slotId); + f.addImsFeatureStatusCallback(c); + addImsFeature(slotId, featureType, f); + // TODO: Remove once new onFeatureReady AIDL is merged in. + f.onFeatureReady(); + } + + private void addImsFeature(int slotId, int featureType, ImsFeature f) { + synchronized (mFeaturesBySlot) { + // Get SparseArray for Features, by querying slot Id + SparseArray<ImsFeature> features = mFeaturesBySlot.get(slotId); + if (features == null) { + // Populate new SparseArray of features if it doesn't exist for this slot yet. + features = new SparseArray<>(); + mFeaturesBySlot.put(slotId, features); + } + features.put(featureType, f); + } + } + + private void removeImsFeature(int slotId, int featureType, + IImsFeatureStatusCallback c) { + synchronized (mFeaturesBySlot) { + // get ImsFeature associated with the slot/feature + SparseArray<ImsFeature> features = mFeaturesBySlot.get(slotId); + if (features == null) { + Log.w(LOG_TAG, "Can not remove ImsFeature. No ImsFeatures exist on slot " + + slotId); + return; + } + ImsFeature f = features.get(featureType); + if (f == null) { + Log.w(LOG_TAG, "Can not remove ImsFeature. No feature with type " + + featureType + " exists on slot " + slotId); + return; + } + f.removeImsFeatureStatusCallback(c); + f.onFeatureRemoved(); + features.remove(featureType); + } + } + + /** + * @return An implementation of MMTelFeature that will be used by the system for MMTel + * functionality. Must be able to handle emergency calls at any time as well. + * @hide + */ + public @Nullable MMTelFeature onCreateEmergencyMMTelImsFeature(int slotId) { + return null; + } + + /** + * @return An implementation of MMTelFeature that will be used by the system for MMTel + * functionality. + * @hide + */ + public @Nullable MMTelFeature onCreateMMTelImsFeature(int slotId) { + return null; + } + + /** + * @return An implementation of RcsFeature that will be used by the system for RCS. + * @hide + */ + public @Nullable RcsFeature onCreateRcsFeature(int slotId) { + return null; + } +} diff --git a/telephony/java/android/telephony/ims/compat/feature/ImsFeature.java b/telephony/java/android/telephony/ims/compat/feature/ImsFeature.java new file mode 100644 index 000000000000..0a12cae26af4 --- /dev/null +++ b/telephony/java/android/telephony/ims/compat/feature/ImsFeature.java @@ -0,0 +1,202 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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.telephony.ims.compat.feature; + +import android.annotation.IntDef; +import android.content.Context; +import android.content.Intent; +import android.os.IInterface; +import android.os.RemoteException; +import android.telephony.SubscriptionManager; +import android.util.Log; + +import com.android.ims.internal.IImsFeatureStatusCallback; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.Collections; +import java.util.Iterator; +import java.util.Set; +import java.util.WeakHashMap; + +/** + * Base class for all IMS features that are supported by the framework. + * @hide + */ +public abstract class ImsFeature { + + private static final String LOG_TAG = "ImsFeature"; + + /** + * Action to broadcast when ImsService is up. + * Internal use only. + * Only defined here separately compatibility purposes with the old ImsService. + * @hide + */ + public static final String ACTION_IMS_SERVICE_UP = + "com.android.ims.IMS_SERVICE_UP"; + + /** + * Action to broadcast when ImsService is down. + * Internal use only. + * Only defined here separately for compatibility purposes with the old ImsService. + * @hide + */ + public static final String ACTION_IMS_SERVICE_DOWN = + "com.android.ims.IMS_SERVICE_DOWN"; + + /** + * Part of the ACTION_IMS_SERVICE_UP or _DOWN intents. + * A long value; the phone ID corresponding to the IMS service coming up or down. + * Only defined here separately for compatibility purposes with the old ImsService. + * @hide + */ + public static final String EXTRA_PHONE_ID = "android:phone_id"; + + // Invalid feature value + public static final int INVALID = -1; + // ImsFeatures that are defined in the Manifests. Ensure that these values match the previously + // defined values in ImsServiceClass for compatibility purposes. + public static final int EMERGENCY_MMTEL = 0; + public static final int MMTEL = 1; + public static final int RCS = 2; + // Total number of features defined + public static final int MAX = 3; + + // Integer values defining the state of the ImsFeature at any time. + @IntDef(flag = true, + value = { + STATE_NOT_AVAILABLE, + STATE_INITIALIZING, + STATE_READY, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface ImsState {} + public static final int STATE_NOT_AVAILABLE = 0; + public static final int STATE_INITIALIZING = 1; + public static final int STATE_READY = 2; + + private final Set<IImsFeatureStatusCallback> mStatusCallbacks = Collections.newSetFromMap( + new WeakHashMap<IImsFeatureStatusCallback, Boolean>()); + private @ImsState int mState = STATE_NOT_AVAILABLE; + private int mSlotId = SubscriptionManager.INVALID_SIM_SLOT_INDEX; + protected Context mContext; + + public void setContext(Context context) { + mContext = context; + } + + public void setSlotId(int slotId) { + mSlotId = slotId; + } + + public int getFeatureState() { + return mState; + } + + protected final void setFeatureState(@ImsState int state) { + if (mState != state) { + mState = state; + notifyFeatureState(state); + } + } + + public void addImsFeatureStatusCallback(IImsFeatureStatusCallback c) { + if (c == null) { + return; + } + try { + // If we have just connected, send queued status. + c.notifyImsFeatureStatus(mState); + // Add the callback if the callback completes successfully without a RemoteException. + synchronized (mStatusCallbacks) { + mStatusCallbacks.add(c); + } + } catch (RemoteException e) { + Log.w(LOG_TAG, "Couldn't notify feature state: " + e.getMessage()); + } + } + + public void removeImsFeatureStatusCallback(IImsFeatureStatusCallback c) { + if (c == null) { + return; + } + synchronized (mStatusCallbacks) { + mStatusCallbacks.remove(c); + } + } + + /** + * Internal method called by ImsFeature when setFeatureState has changed. + * @param state + */ + private void notifyFeatureState(@ImsState int state) { + synchronized (mStatusCallbacks) { + for (Iterator<IImsFeatureStatusCallback> iter = mStatusCallbacks.iterator(); + iter.hasNext(); ) { + IImsFeatureStatusCallback callback = iter.next(); + try { + Log.i(LOG_TAG, "notifying ImsFeatureState=" + state); + callback.notifyImsFeatureStatus(state); + } catch (RemoteException e) { + // remove if the callback is no longer alive. + iter.remove(); + Log.w(LOG_TAG, "Couldn't notify feature state: " + e.getMessage()); + } + } + } + sendImsServiceIntent(state); + } + + /** + * Provide backwards compatibility using deprecated service UP/DOWN intents. + */ + private void sendImsServiceIntent(@ImsState int state) { + if(mContext == null || mSlotId == SubscriptionManager.INVALID_SIM_SLOT_INDEX) { + return; + } + Intent intent; + switch (state) { + case ImsFeature.STATE_NOT_AVAILABLE: + case ImsFeature.STATE_INITIALIZING: + intent = new Intent(ACTION_IMS_SERVICE_DOWN); + break; + case ImsFeature.STATE_READY: + intent = new Intent(ACTION_IMS_SERVICE_UP); + break; + default: + intent = new Intent(ACTION_IMS_SERVICE_DOWN); + } + intent.putExtra(EXTRA_PHONE_ID, mSlotId); + mContext.sendBroadcast(intent); + } + + /** + * Called when the feature is ready to use. + */ + public abstract void onFeatureReady(); + + /** + * Called when the feature is being removed and must be cleaned up. + */ + public abstract void onFeatureRemoved(); + + /** + * @return Binder instance + */ + public abstract IInterface getBinder(); +} diff --git a/telephony/java/android/telephony/ims/feature/MMTelFeature.java b/telephony/java/android/telephony/ims/compat/feature/MMTelFeature.java index 93c316f3dcb9..d3d17f4fb198 100644 --- a/telephony/java/android/telephony/ims/feature/MMTelFeature.java +++ b/telephony/java/android/telephony/ims/compat/feature/MMTelFeature.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2017 The Android Open Source Project + * Copyright (C) 2018 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,14 +14,13 @@ * limitations under the License */ -package android.telephony.ims.feature; +package android.telephony.ims.compat.feature; import android.app.PendingIntent; import android.os.Message; import android.os.RemoteException; -import android.telephony.ims.internal.stub.SmsImplBase; -import com.android.ims.ImsCallProfile; +import android.telephony.ims.ImsCallProfile; import com.android.ims.internal.IImsCallSession; import com.android.ims.internal.IImsCallSessionListener; import com.android.ims.internal.IImsConfig; @@ -29,9 +28,12 @@ import com.android.ims.internal.IImsEcbm; import com.android.ims.internal.IImsMMTelFeature; import com.android.ims.internal.IImsMultiEndpoint; import com.android.ims.internal.IImsRegistrationListener; -import com.android.ims.internal.IImsSmsListener; import com.android.ims.internal.IImsUt; -import com.android.ims.internal.ImsCallSession; +import android.telephony.ims.ImsCallSession; +import android.telephony.ims.compat.stub.ImsCallSessionImplBase; +import android.telephony.ims.stub.ImsEcbmImplBase; +import android.telephony.ims.stub.ImsMultiEndpointImplBase; +import android.telephony.ims.stub.ImsUtImplBase; /** * Base implementation for MMTel. @@ -110,10 +112,10 @@ public class MMTelFeature extends ImsFeature { } @Override - public IImsCallSession createCallSession(int sessionId, ImsCallProfile profile, - IImsCallSessionListener listener) throws RemoteException { + public IImsCallSession createCallSession(int sessionId, ImsCallProfile profile) + throws RemoteException { synchronized (mLock) { - return MMTelFeature.this.createCallSession(sessionId, profile, listener); + return MMTelFeature.this.createCallSession(sessionId, profile, null); } } @@ -128,7 +130,8 @@ public class MMTelFeature extends ImsFeature { @Override public IImsUt getUtInterface() throws RemoteException { synchronized (mLock) { - return MMTelFeature.this.getUtInterface(); + ImsUtImplBase implBase = MMTelFeature.this.getUtInterface(); + return implBase != null ? implBase.getInterface() : null; } } @@ -156,7 +159,8 @@ public class MMTelFeature extends ImsFeature { @Override public IImsEcbm getEcbmInterface() throws RemoteException { synchronized (mLock) { - return MMTelFeature.this.getEcbmInterface(); + ImsEcbmImplBase implBase = MMTelFeature.this.getEcbmInterface(); + return implBase != null ? implBase.getImsEcbm() : null; } } @@ -170,50 +174,8 @@ public class MMTelFeature extends ImsFeature { @Override public IImsMultiEndpoint getMultiEndpointInterface() throws RemoteException { synchronized (mLock) { - return MMTelFeature.this.getMultiEndpointInterface(); - } - } - - @Override - public void setSmsListener(IImsSmsListener l) throws RemoteException { - synchronized (mLock) { - MMTelFeature.this.setSmsListener(l); - } - } - - @Override - public void sendSms(int token, int messageRef, String format, String smsc, boolean retry, - byte[] pdu) { - synchronized (mLock) { - MMTelFeature.this.sendSms(token, messageRef, format, smsc, retry, pdu); - } - } - - @Override - public void acknowledgeSms(int token, int messageRef, int result) { - synchronized (mLock) { - MMTelFeature.this.acknowledgeSms(token, messageRef, result); - } - } - - @Override - public void acknowledgeSmsReport(int token, int messageRef, int result) { - synchronized (mLock) { - MMTelFeature.this.acknowledgeSmsReport(token, messageRef, result); - } - } - - @Override - public String getSmsFormat() { - synchronized (mLock) { - return MMTelFeature.this.getSmsFormat(); - } - } - - @Override - public void onSmsReady() { - synchronized (mLock) { - MMTelFeature.this.onSmsReady(); + ImsMultiEndpointImplBase implBase = MMTelFeature.this.getMultiEndpointInterface(); + return implBase != null ? implBase.getIImsMultiEndpoint() : null; } } }; @@ -326,7 +288,6 @@ public class MMTelFeature extends ImsFeature { * * @param sessionId a session id which is obtained from {@link #startSession} * @param profile a call profile to make the call - * @param listener An implementation of IImsCallSessionListener. */ public IImsCallSession createCallSession(int sessionId, ImsCallProfile profile, IImsCallSessionListener listener) { @@ -346,7 +307,7 @@ public class MMTelFeature extends ImsFeature { /** * @return The Ut interface for the supplementary service configuration. */ - public IImsUt getUtInterface() { + public ImsUtImplBase getUtInterface() { return null; } @@ -372,7 +333,7 @@ public class MMTelFeature extends ImsFeature { /** * @return The Emergency call-back mode interface for emergency VoLTE calls that support it. */ - public IImsEcbm getEcbmInterface() { + public ImsEcbmImplBase getEcbmInterface() { return null; } @@ -387,47 +348,10 @@ public class MMTelFeature extends ImsFeature { /** * @return MultiEndpoint interface for DEP notifications */ - public IImsMultiEndpoint getMultiEndpointInterface() { + public ImsMultiEndpointImplBase getMultiEndpointInterface() { return null; } - private void setSmsListener(IImsSmsListener listener) { - getSmsImplementation().registerSmsListener(listener); - } - - private void sendSms(int token, int messageRef, String format, String smsc, boolean isRetry, - byte[] pdu) { - getSmsImplementation().sendSms(token, messageRef, format, smsc, isRetry, pdu); - } - - private void acknowledgeSms(int token, int messageRef, - @SmsImplBase.DeliverStatusResult int result) { - getSmsImplementation().acknowledgeSms(token, messageRef, result); - } - - private void acknowledgeSmsReport(int token, int messageRef, - @SmsImplBase.StatusReportResult int result) { - getSmsImplementation().acknowledgeSmsReport(token, messageRef, result); - } - - private void onSmsReady() { - getSmsImplementation().onReady(); - } - - /** - * Must be overridden by IMS Provider to be able to support SMS over IMS. Otherwise a default - * non-functional implementation is returned. - * - * @return an instance of {@link SmsImplBase} which should be implemented by the IMS Provider. - */ - protected SmsImplBase getSmsImplementation() { - return new SmsImplBase(); - } - - public String getSmsFormat() { - return getSmsImplementation().getSmsFormat(); - } - @Override public void onFeatureReady() { diff --git a/telephony/java/android/telephony/ims/internal/feature/RcsFeature.java b/telephony/java/android/telephony/ims/compat/feature/RcsFeature.java index 8d1bd9d27f7c..228b33045107 100644 --- a/telephony/java/android/telephony/ims/internal/feature/RcsFeature.java +++ b/telephony/java/android/telephony/ims/compat/feature/RcsFeature.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2017 The Android Open Source Project + * Copyright (C) 2018 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,9 +14,10 @@ * limitations under the License */ -package android.telephony.ims.internal.feature; +package android.telephony.ims.compat.feature; -import android.telephony.ims.internal.aidl.IImsRcsFeature; + +import com.android.ims.internal.IImsRcsFeature; /** * Base implementation of the RcsFeature APIs. Any ImsService wishing to support RCS should extend @@ -36,19 +37,12 @@ public class RcsFeature extends ImsFeature { } @Override - public void changeEnabledCapabilities(CapabilityChangeRequest request, - CapabilityCallbackProxy c) { - // Do nothing for base implementation. - } - - @Override - public void onFeatureRemoved() { + public void onFeatureReady() { } - /**{@inheritDoc}*/ @Override - public void onFeatureReady() { + public void onFeatureRemoved() { } diff --git a/telephony/java/android/telephony/ims/compat/stub/ImsCallSessionImplBase.java b/telephony/java/android/telephony/ims/compat/stub/ImsCallSessionImplBase.java new file mode 100644 index 000000000000..00cb1e25f66d --- /dev/null +++ b/telephony/java/android/telephony/ims/compat/stub/ImsCallSessionImplBase.java @@ -0,0 +1,585 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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.telephony.ims.compat.stub; + +import android.os.Message; +import android.os.RemoteException; + +import android.telephony.ims.ImsCallProfile; +import android.telephony.ims.ImsConferenceState; +import android.telephony.ims.ImsReasonInfo; +import android.telephony.ims.ImsStreamMediaProfile; +import android.telephony.ims.ImsSuppServiceNotification; +import android.telephony.ims.aidl.IImsCallSessionListener; +import com.android.ims.internal.IImsCallSession; +import com.android.ims.internal.IImsVideoCallProvider; + +import android.telephony.ims.ImsCallSession; + +/** + * Compat implementation of ImsCallSessionImplBase for older implementations. + * + * DO NOT remove or change the existing APIs, only add new ones to this Base implementation or you + * will break other implementations of ImsCallSession maintained by other ImsServices. + * + * @hide + */ + +public class ImsCallSessionImplBase extends IImsCallSession.Stub { + + @Override + // convert to old implementation of listener + public final void setListener(IImsCallSessionListener listener) + throws RemoteException { + setListener(new ImsCallSessionListenerConverter(listener)); + } + + /** + * Sets the listener to listen to the session events. An {@link ImsCallSession} + * can only hold one listener at a time. Subsequent calls to this method + * override the previous listener. + * + * @param listener to listen to the session events of this object + */ + public void setListener(com.android.ims.internal.IImsCallSessionListener listener) { + + } + + /** + * Closes the object. This {@link ImsCallSessionImplBase} is not usable after being closed. + */ + @Override + public void close() { + + } + + /** + * @return A String containing the unique call ID of this {@link ImsCallSessionImplBase}. + */ + @Override + public String getCallId() { + return null; + } + + /** + * @return The {@link ImsCallProfile} that this {@link ImsCallSessionImplBase} is associated + * with. + */ + @Override + public ImsCallProfile getCallProfile() { + return null; + } + + /** + * @return The local {@link ImsCallProfile} that this {@link ImsCallSessionImplBase} is + * associated with. + */ + @Override + public ImsCallProfile getLocalCallProfile() { + return null; + } + + /** + * @return The remote {@link ImsCallProfile} that this {@link ImsCallSessionImplBase} is + * associated with. + */ + @Override + public ImsCallProfile getRemoteCallProfile() { + return null; + } + + /** + * @param name The String extra key. + * @return The string extra value associated with the specified property. + */ + @Override + public String getProperty(String name) { + return null; + } + + /** + * @return The {@link ImsCallSessionImplBase} state. + */ + @Override + public int getState() { + return -1; + } + + /** + * @return true if the {@link ImsCallSessionImplBase} is in a call, false otherwise. + */ + @Override + public boolean isInCall() { + return false; + } + + /** + * Mutes or unmutes the mic for the active call. + * + * @param muted true if the call should be muted, false otherwise. + */ + @Override + public void setMute(boolean muted) { + } + + /** + * Initiates an IMS call with the specified number and call profile. + * The session listener set in {@link #setListener(IImsCallSessionListener)} is called back upon + * defined session events. + * Only valid to call when the session state is in + * {@link ImsCallSession.State#IDLE}. + * + * @param callee dialed string to make the call to + * @param profile call profile to make the call with the specified service type, + * call type and media information + * @see {@link ImsCallSession.Listener#callSessionStarted}, + * {@link ImsCallSession.Listener#callSessionStartFailed} + */ + @Override + public void start(String callee, ImsCallProfile profile) { + } + + /** + * Initiates an IMS call with the specified participants and call profile. + * The session listener set in {@link #setListener(IImsCallSessionListener)} is called back upon + * defined session events. + * The method is only valid to call when the session state is in + * {@link ImsCallSession.State#IDLE}. + * + * @param participants participant list to initiate an IMS conference call + * @param profile call profile to make the call with the specified service type, + * call type and media information + * @see {@link ImsCallSession.Listener#callSessionStarted}, + * {@link ImsCallSession.Listener#callSessionStartFailed} + */ + @Override + public void startConference(String[] participants, ImsCallProfile profile) { + } + + /** + * Accepts an incoming call or session update. + * + * @param callType call type specified in {@link ImsCallProfile} to be answered + * @param profile stream media profile {@link ImsStreamMediaProfile} to be answered + * @see {@link ImsCallSession.Listener#callSessionStarted} + */ + @Override + public void accept(int callType, ImsStreamMediaProfile profile) { + } + + /** + * Rejects an incoming call or session update. + * + * @param reason reason code to reject an incoming call, defined in {@link ImsReasonInfo}. + * {@link ImsCallSession.Listener#callSessionStartFailed} + */ + @Override + public void reject(int reason) { + } + + /** + * Terminates a call. + * + * @param reason reason code to terminate a call, defined in {@link ImsReasonInfo}. + * + * @see {@link ImsCallSession.Listener#callSessionTerminated} + */ + @Override + public void terminate(int reason) { + } + + /** + * Puts a call on hold. When it succeeds, {@link ImsCallSession.Listener#callSessionHeld} is + * called. + * + * @param profile stream media profile {@link ImsStreamMediaProfile} to hold the call + * @see {@link ImsCallSession.Listener#callSessionHeld}, + * {@link ImsCallSession.Listener#callSessionHoldFailed} + */ + @Override + public void hold(ImsStreamMediaProfile profile) { + } + + /** + * Continues a call that's on hold. When it succeeds, + * {@link ImsCallSession.Listener#callSessionResumed} is called. + * + * @param profile stream media profile with {@link ImsStreamMediaProfile} to resume the call + * @see {@link ImsCallSession.Listener#callSessionResumed}, + * {@link ImsCallSession.Listener#callSessionResumeFailed} + */ + @Override + public void resume(ImsStreamMediaProfile profile) { + } + + /** + * Merges the active and held call. When the merge starts, + * {@link ImsCallSession.Listener#callSessionMergeStarted} is called. + * {@link ImsCallSession.Listener#callSessionMergeComplete} is called if the merge is + * successful, and {@link ImsCallSession.Listener#callSessionMergeFailed} is called if the merge + * fails. + * + * @see {@link ImsCallSession.Listener#callSessionMergeStarted}, + * {@link ImsCallSession.Listener#callSessionMergeComplete}, + * {@link ImsCallSession.Listener#callSessionMergeFailed} + */ + @Override + public void merge() { + } + + /** + * Updates the current call's properties (ex. call mode change: video upgrade / downgrade). + * + * @param callType call type specified in {@link ImsCallProfile} to be updated + * @param profile stream media profile {@link ImsStreamMediaProfile} to be updated + * @see {@link ImsCallSession.Listener#callSessionUpdated}, + * {@link ImsCallSession.Listener#callSessionUpdateFailed} + */ + @Override + public void update(int callType, ImsStreamMediaProfile profile) { + } + + /** + * Extends this call to the conference call with the specified recipients. + * + * @param participants participant list to be invited to the conference call after extending the + * call + * @see {@link ImsCallSession.Listener#callSessionConferenceExtended}, + * {@link ImsCallSession.Listener#callSessionConferenceExtendFailed} + */ + @Override + public void extendToConference(String[] participants) { + } + + /** + * Requests the conference server to invite an additional participants to the conference. + * + * @param participants participant list to be invited to the conference call + * @see {@link ImsCallSession.Listener#callSessionInviteParticipantsRequestDelivered}, + * {@link ImsCallSession.Listener#callSessionInviteParticipantsRequestFailed} + */ + @Override + public void inviteParticipants(String[] participants) { + } + + /** + * Requests the conference server to remove the specified participants from the conference. + * + * @param participants participant list to be removed from the conference call + * @see {@link ImsCallSession.Listener#callSessionRemoveParticipantsRequestDelivered}, + * {@link ImsCallSession.Listener#callSessionRemoveParticipantsRequestFailed} + */ + @Override + public void removeParticipants(String[] participants) { + } + + /** + * Sends a DTMF code. According to <a href="http://tools.ietf.org/html/rfc2833">RFC 2833</a>, + * event 0 ~ 9 maps to decimal value 0 ~ 9, '*' to 10, '#' to 11, event 'A' ~ 'D' to 12 ~ 15, + * and event flash to 16. Currently, event flash is not supported. + * + * @param c the DTMF to send. '0' ~ '9', 'A' ~ 'D', '*', '#' are valid inputs. + */ + @Override + public void sendDtmf(char c, Message result) { + } + + /** + * Start a DTMF code. According to <a href="http://tools.ietf.org/html/rfc2833">RFC 2833</a>, + * event 0 ~ 9 maps to decimal value 0 ~ 9, '*' to 10, '#' to 11, event 'A' ~ 'D' to 12 ~ 15, + * and event flash to 16. Currently, event flash is not supported. + * + * @param c the DTMF to send. '0' ~ '9', 'A' ~ 'D', '*', '#' are valid inputs. + */ + @Override + public void startDtmf(char c) { + } + + /** + * Stop a DTMF code. + */ + @Override + public void stopDtmf() { + } + + /** + * Sends an USSD message. + * + * @param ussdMessage USSD message to send + */ + @Override + public void sendUssd(String ussdMessage) { + } + + @Override + public IImsVideoCallProvider getVideoCallProvider() { + return null; + } + + /** + * Determines if the current session is multiparty. + * @return {@code True} if the session is multiparty. + */ + @Override + public boolean isMultiparty() { + return false; + } + + /** + * Device issues RTT modify request + * @param toProfile The profile with requested changes made + */ + @Override + public void sendRttModifyRequest(ImsCallProfile toProfile) { + } + + /** + * Device responds to Remote RTT modify request + * @param status true if the the request was accepted or false of the request is defined. + */ + @Override + public void sendRttModifyResponse(boolean status) { + } + + /** + * Device sends RTT message + * @param rttMessage RTT message to be sent + */ + @Override + public void sendRttMessage(String rttMessage) { + } + + /** + * There are two different ImsCallSessionListeners that need to reconciled here, we need to + * convert the "old" version of the com.android.ims.internal.IImsCallSessionListener to the + * "new" version of the Listener android.telephony.ims.ImsCallSessionListener when calling + * back to the framework. + */ + private class ImsCallSessionListenerConverter + extends com.android.ims.internal.IImsCallSessionListener.Stub { + + private final IImsCallSessionListener mNewListener; + + public ImsCallSessionListenerConverter(IImsCallSessionListener listener) { + mNewListener = listener; + } + + @Override + public void callSessionProgressing(IImsCallSession i, + ImsStreamMediaProfile imsStreamMediaProfile) throws RemoteException { + mNewListener.callSessionProgressing(imsStreamMediaProfile); + } + + @Override + public void callSessionStarted(IImsCallSession i, ImsCallProfile imsCallProfile) + throws RemoteException { + mNewListener.callSessionInitiated(imsCallProfile); + } + + @Override + public void callSessionStartFailed(IImsCallSession i, ImsReasonInfo imsReasonInfo) + throws RemoteException { + mNewListener.callSessionInitiatedFailed(imsReasonInfo); + } + + @Override + public void callSessionTerminated(IImsCallSession i, ImsReasonInfo imsReasonInfo) + throws RemoteException { + mNewListener.callSessionTerminated(imsReasonInfo); + } + + @Override + public void callSessionHeld(IImsCallSession i, ImsCallProfile imsCallProfile) + throws RemoteException { + mNewListener.callSessionHeld(imsCallProfile); + } + + @Override + public void callSessionHoldFailed(IImsCallSession i, ImsReasonInfo imsReasonInfo) + throws RemoteException { + mNewListener.callSessionHoldFailed(imsReasonInfo); + } + + @Override + public void callSessionHoldReceived(IImsCallSession i, ImsCallProfile imsCallProfile) + throws RemoteException { + mNewListener.callSessionHoldReceived(imsCallProfile); + } + + @Override + public void callSessionResumed(IImsCallSession i, ImsCallProfile imsCallProfile) + throws RemoteException { + mNewListener.callSessionResumed(imsCallProfile); + } + + @Override + public void callSessionResumeFailed(IImsCallSession i, ImsReasonInfo imsReasonInfo) + throws RemoteException { + mNewListener.callSessionResumeFailed(imsReasonInfo); + } + + @Override + public void callSessionResumeReceived(IImsCallSession i, ImsCallProfile imsCallProfile) + throws RemoteException { + mNewListener.callSessionResumeReceived(imsCallProfile); + } + + @Override + public void callSessionMergeStarted(IImsCallSession i, IImsCallSession newSession, + ImsCallProfile profile) + throws RemoteException { + mNewListener.callSessionMergeStarted(newSession, profile); + } + + @Override + public void callSessionMergeComplete(IImsCallSession iImsCallSession) + throws RemoteException { + mNewListener.callSessionMergeComplete(iImsCallSession); + } + + @Override + public void callSessionMergeFailed(IImsCallSession i, ImsReasonInfo imsReasonInfo) + throws RemoteException { + mNewListener.callSessionMergeFailed(imsReasonInfo); + } + + @Override + public void callSessionUpdated(IImsCallSession i, ImsCallProfile imsCallProfile) + throws RemoteException { + mNewListener.callSessionUpdated(imsCallProfile); + } + + @Override + public void callSessionUpdateFailed(IImsCallSession i, ImsReasonInfo imsReasonInfo) + throws RemoteException { + mNewListener.callSessionUpdateFailed(imsReasonInfo); + } + + @Override + public void callSessionUpdateReceived(IImsCallSession i, ImsCallProfile imsCallProfile) + throws RemoteException { + mNewListener.callSessionUpdateReceived(imsCallProfile); + } + + @Override + public void callSessionConferenceExtended(IImsCallSession i, IImsCallSession newSession, + ImsCallProfile imsCallProfile) throws RemoteException { + mNewListener.callSessionConferenceExtended(newSession, imsCallProfile); + } + + @Override + public void callSessionConferenceExtendFailed(IImsCallSession i, + ImsReasonInfo imsReasonInfo) throws RemoteException { + mNewListener.callSessionConferenceExtendFailed(imsReasonInfo); + } + + @Override + public void callSessionConferenceExtendReceived(IImsCallSession i, + IImsCallSession newSession, ImsCallProfile imsCallProfile) + throws RemoteException { + mNewListener.callSessionConferenceExtendReceived(newSession, imsCallProfile); + } + + @Override + public void callSessionInviteParticipantsRequestDelivered(IImsCallSession i) + throws RemoteException { + mNewListener.callSessionInviteParticipantsRequestDelivered(); + } + + @Override + public void callSessionInviteParticipantsRequestFailed(IImsCallSession i, + ImsReasonInfo imsReasonInfo) throws RemoteException { + mNewListener.callSessionInviteParticipantsRequestFailed(imsReasonInfo); + } + + @Override + public void callSessionRemoveParticipantsRequestDelivered(IImsCallSession i) + throws RemoteException { + mNewListener.callSessionRemoveParticipantsRequestDelivered(); + } + + @Override + public void callSessionRemoveParticipantsRequestFailed(IImsCallSession i, + ImsReasonInfo imsReasonInfo) throws RemoteException { + mNewListener.callSessionRemoveParticipantsRequestFailed(imsReasonInfo); + } + + @Override + public void callSessionConferenceStateUpdated(IImsCallSession i, + ImsConferenceState imsConferenceState) throws RemoteException { + mNewListener.callSessionConferenceStateUpdated(imsConferenceState); + } + + @Override + public void callSessionUssdMessageReceived(IImsCallSession i, int mode, String message) + throws RemoteException { + mNewListener.callSessionUssdMessageReceived(mode, message); + } + + @Override + public void callSessionHandover(IImsCallSession i, int srcAccessTech, int targetAccessTech, + ImsReasonInfo reasonInfo) throws RemoteException { + mNewListener.callSessionHandover(srcAccessTech, targetAccessTech, reasonInfo); + } + + @Override + public void callSessionHandoverFailed(IImsCallSession i, int srcAccessTech, + int targetAccessTech, ImsReasonInfo reasonInfo) throws RemoteException { + mNewListener.callSessionHandoverFailed(srcAccessTech, targetAccessTech, reasonInfo); + } + + @Override + public void callSessionMayHandover(IImsCallSession i, int srcAccessTech, int targetAccessTech) + throws RemoteException { + mNewListener.callSessionMayHandover(srcAccessTech, targetAccessTech); + } + + @Override + public void callSessionTtyModeReceived(IImsCallSession iImsCallSession, int mode) + throws RemoteException { + mNewListener.callSessionTtyModeReceived(mode); + } + + @Override + public void callSessionMultipartyStateChanged(IImsCallSession i, boolean isMultiparty) + throws RemoteException { + mNewListener.callSessionMultipartyStateChanged(isMultiparty); + } + + @Override + public void callSessionSuppServiceReceived(IImsCallSession i, + ImsSuppServiceNotification imsSuppServiceNotification) throws RemoteException { + mNewListener.callSessionSuppServiceReceived(imsSuppServiceNotification); + } + + @Override + public void callSessionRttModifyRequestReceived(IImsCallSession i, + ImsCallProfile imsCallProfile) throws RemoteException { + mNewListener.callSessionRttModifyRequestReceived(imsCallProfile); + } + + @Override + public void callSessionRttModifyResponseReceived(int status) throws RemoteException { + mNewListener.callSessionRttModifyResponseReceived(status); + } + + @Override + public void callSessionRttMessageReceived(String rttMessage) throws RemoteException { + mNewListener.callSessionRttMessageReceived(rttMessage); + } + } +} diff --git a/telephony/java/android/telephony/ims/compat/stub/ImsConfigImplBase.java b/telephony/java/android/telephony/ims/compat/stub/ImsConfigImplBase.java new file mode 100644 index 000000000000..2c325ba8e134 --- /dev/null +++ b/telephony/java/android/telephony/ims/compat/stub/ImsConfigImplBase.java @@ -0,0 +1,391 @@ +/* + * 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.telephony.ims.compat.stub; + +import android.content.Context; +import android.content.Intent; +import android.os.RemoteException; +import android.util.Log; + +import com.android.ims.ImsConfig; +import com.android.ims.ImsConfigListener; +import com.android.ims.internal.IImsConfig; +import com.android.internal.annotations.VisibleForTesting; + +import java.lang.ref.WeakReference; +import java.util.HashMap; + + +/** + * Base implementation of ImsConfig. + * Override the methods that your implementation of ImsConfig supports. + * + * DO NOT remove or change the existing APIs, only add new ones to this Base implementation or you + * will break other implementations of ImsConfig maintained by other ImsServices. + * + * Provides APIs to get/set the IMS service feature/capability/parameters. + * The config items include: + * 1) Items provisioned by the operator. + * 2) Items configured by user. Mainly service feature class. + * + * The inner class {@link ImsConfigStub} implements methods of IImsConfig AIDL interface. + * The IImsConfig AIDL interface is called by ImsConfig, which may exist in many other processes. + * ImsConfigImpl access to the configuration parameters may be arbitrarily slow, especially in + * during initialization, or times when a lot of configuration parameters are being set/get + * (such as during boot up or SIM card change). By providing a cache in ImsConfigStub, we can speed + * up access to these configuration parameters, so a query to the ImsConfigImpl does not have to be + * performed every time. + * @hide + */ + +public class ImsConfigImplBase { + + static final private String TAG = "ImsConfigImplBase"; + + ImsConfigStub mImsConfigStub; + + public ImsConfigImplBase(Context context) { + mImsConfigStub = new ImsConfigStub(this, context); + } + + /** + * Gets the value for ims service/capabilities parameters from the provisioned + * value storage. Synchronous blocking call. + * + * @param item, as defined in com.android.ims.ImsConfig#ConfigConstants. + * @return value in Integer format. + */ + public int getProvisionedValue(int item) throws RemoteException { + return -1; + } + + /** + * Gets the value for ims service/capabilities parameters from the provisioned + * value storage. Synchronous blocking call. + * + * @param item, as defined in com.android.ims.ImsConfig#ConfigConstants. + * @return value in String format. + */ + public String getProvisionedStringValue(int item) throws RemoteException { + return null; + } + + /** + * Sets the value for IMS service/capabilities parameters by the operator device + * management entity. It sets the config item value in the provisioned storage + * from which the master value is derived. Synchronous blocking call. + * + * @param item, as defined in com.android.ims.ImsConfig#ConfigConstants. + * @param value in Integer format. + * @return as defined in com.android.ims.ImsConfig#OperationStatusConstants. + */ + public int setProvisionedValue(int item, int value) throws RemoteException { + return ImsConfig.OperationStatusConstants.FAILED; + } + + /** + * Sets the value for IMS service/capabilities parameters by the operator device + * management entity. It sets the config item value in the provisioned storage + * from which the master value is derived. Synchronous blocking call. + * + * @param item as defined in com.android.ims.ImsConfig#ConfigConstants. + * @param value in String format. + * @return as defined in com.android.ims.ImsConfig#OperationStatusConstants. + */ + public int setProvisionedStringValue(int item, String value) throws RemoteException { + return ImsConfig.OperationStatusConstants.FAILED; + } + + /** + * Gets the value of the specified IMS feature item for specified network type. + * This operation gets the feature config value from the master storage (i.e. final + * value). Asynchronous non-blocking call. + * + * @param feature as defined in com.android.ims.ImsConfig#FeatureConstants. + * @param network as defined in android.telephony.TelephonyManager#NETWORK_TYPE_XXX. + * @param listener feature value returned asynchronously through listener. + */ + public void getFeatureValue(int feature, int network, ImsConfigListener listener) + throws RemoteException { + } + + /** + * Sets the value for IMS feature item for specified network type. + * This operation stores the user setting in setting db from which master db + * is derived. + * + * @param feature as defined in com.android.ims.ImsConfig#FeatureConstants. + * @param network as defined in android.telephony.TelephonyManager#NETWORK_TYPE_XXX. + * @param value as defined in com.android.ims.ImsConfig#FeatureValueConstants. + * @param listener, provided if caller needs to be notified for set result. + */ + public void setFeatureValue(int feature, int network, int value, ImsConfigListener listener) + throws RemoteException { + } + + /** + * Gets the value for IMS VoLTE provisioned. + * This should be the same as the operator provisioned value if applies. + */ + public boolean getVolteProvisioned() throws RemoteException { + return false; + } + + /** + * Gets the value for IMS feature item video quality. + * + * @param listener Video quality value returned asynchronously through listener. + */ + public void getVideoQuality(ImsConfigListener listener) throws RemoteException { + } + + /** + * Sets the value for IMS feature item video quality. + * + * @param quality, defines the value of video quality. + * @param listener, provided if caller needs to be notified for set result. + */ + public void setVideoQuality(int quality, ImsConfigListener listener) throws RemoteException { + } + + public IImsConfig getIImsConfig() { return mImsConfigStub; } + + /** + * Updates provisioning value and notifies the framework of the change. + * Doesn't call #setProvisionedValue and assumes the result succeeded. + * This should only be used by modem when they implicitly changed provisioned values. + * + * @param item, as defined in com.android.ims.ImsConfig#ConfigConstants. + * @param value in Integer format. + */ + public final void notifyProvisionedValueChanged(int item, int value) { + mImsConfigStub.updateCachedValue(item, value, true); + } + + /** + * Updates provisioning value and notifies the framework of the change. + * Doesn't call #setProvisionedValue and assumes the result succeeded. + * This should only be used by modem when they implicitly changed provisioned values. + * + * @param item, as defined in com.android.ims.ImsConfig#ConfigConstants. + * @param value in String format. + */ + public final void notifyProvisionedValueChanged(int item, String value) { + mImsConfigStub.updateCachedValue(item, value, true); + } + + /** + * Implements the IImsConfig AIDL interface, which is called by potentially many processes + * in order to get/set configuration parameters. + * + * It holds an object of ImsConfigImplBase class which is usually extended by ImsConfigImpl + * with actual implementations from vendors. This class caches provisioned values from + * ImsConfigImpl layer because queries through ImsConfigImpl can be slow. When query goes in, + * it first checks cache layer. If missed, it will call the vendor implementation of + * ImsConfigImplBase API. + * and cache the return value if the set succeeds. + * + * Provides APIs to get/set the IMS service feature/capability/parameters. + * The config items include: + * 1) Items provisioned by the operator. + * 2) Items configured by user. Mainly service feature class. + * + * @hide + */ + @VisibleForTesting + static public class ImsConfigStub extends IImsConfig.Stub { + Context mContext; + WeakReference<ImsConfigImplBase> mImsConfigImplBaseWeakReference; + private HashMap<Integer, Integer> mProvisionedIntValue = new HashMap<>(); + private HashMap<Integer, String> mProvisionedStringValue = new HashMap<>(); + + @VisibleForTesting + public ImsConfigStub(ImsConfigImplBase imsConfigImplBase, Context context) { + mContext = context; + mImsConfigImplBaseWeakReference = + new WeakReference<ImsConfigImplBase>(imsConfigImplBase); + } + + /** + * Gets the value for ims service/capabilities parameters. It first checks its local cache, + * if missed, it will call ImsConfigImplBase.getProvisionedValue. + * Synchronous blocking call. + * + * @param item, as defined in com.android.ims.ImsConfig#ConfigConstants. + * @return value in Integer format. + */ + @Override + public synchronized int getProvisionedValue(int item) throws RemoteException { + if (mProvisionedIntValue.containsKey(item)) { + return mProvisionedIntValue.get(item); + } else { + int retVal = getImsConfigImpl().getProvisionedValue(item); + if (retVal != ImsConfig.OperationStatusConstants.UNKNOWN) { + updateCachedValue(item, retVal, false); + } + return retVal; + } + } + + /** + * Gets the value for ims service/capabilities parameters. It first checks its local cache, + * if missed, it will call #ImsConfigImplBase.getProvisionedValue. + * Synchronous blocking call. + * + * @param item, as defined in com.android.ims.ImsConfig#ConfigConstants. + * @return value in String format. + */ + @Override + public synchronized String getProvisionedStringValue(int item) throws RemoteException { + if (mProvisionedIntValue.containsKey(item)) { + return mProvisionedStringValue.get(item); + } else { + String retVal = getImsConfigImpl().getProvisionedStringValue(item); + if (retVal != null) { + updateCachedValue(item, retVal, false); + } + return retVal; + } + } + + /** + * Sets the value for IMS service/capabilities parameters by the operator device + * management entity. It sets the config item value in the provisioned storage + * from which the master value is derived, and write it into local cache. + * Synchronous blocking call. + * + * @param item, as defined in com.android.ims.ImsConfig#ConfigConstants. + * @param value in Integer format. + * @return as defined in com.android.ims.ImsConfig#OperationStatusConstants. + */ + @Override + public synchronized int setProvisionedValue(int item, int value) throws RemoteException { + mProvisionedIntValue.remove(item); + int retVal = getImsConfigImpl().setProvisionedValue(item, value); + if (retVal == ImsConfig.OperationStatusConstants.SUCCESS) { + updateCachedValue(item, value, true); + } else { + Log.d(TAG, "Set provision value of " + item + + " to " + value + " failed with error code " + retVal); + } + + return retVal; + } + + /** + * Sets the value for IMS service/capabilities parameters by the operator device + * management entity. It sets the config item value in the provisioned storage + * from which the master value is derived, and write it into local cache. + * Synchronous blocking call. + * + * @param item as defined in com.android.ims.ImsConfig#ConfigConstants. + * @param value in String format. + * @return as defined in com.android.ims.ImsConfig#OperationStatusConstants. + */ + @Override + public synchronized int setProvisionedStringValue(int item, String value) + throws RemoteException { + mProvisionedStringValue.remove(item); + int retVal = getImsConfigImpl().setProvisionedStringValue(item, value); + if (retVal == ImsConfig.OperationStatusConstants.SUCCESS) { + updateCachedValue(item, value, true); + } + + return retVal; + } + + /** + * Wrapper function to call ImsConfigImplBase.getFeatureValue. + */ + @Override + public void getFeatureValue(int feature, int network, ImsConfigListener listener) + throws RemoteException { + getImsConfigImpl().getFeatureValue(feature, network, listener); + } + + /** + * Wrapper function to call ImsConfigImplBase.setFeatureValue. + */ + @Override + public void setFeatureValue(int feature, int network, int value, ImsConfigListener listener) + throws RemoteException { + getImsConfigImpl().setFeatureValue(feature, network, value, listener); + } + + /** + * Wrapper function to call ImsConfigImplBase.getVolteProvisioned. + */ + @Override + public boolean getVolteProvisioned() throws RemoteException { + return getImsConfigImpl().getVolteProvisioned(); + } + + /** + * Wrapper function to call ImsConfigImplBase.getVideoQuality. + */ + @Override + public void getVideoQuality(ImsConfigListener listener) throws RemoteException { + getImsConfigImpl().getVideoQuality(listener); + } + + /** + * Wrapper function to call ImsConfigImplBase.setVideoQuality. + */ + @Override + public void setVideoQuality(int quality, ImsConfigListener listener) + throws RemoteException { + getImsConfigImpl().setVideoQuality(quality, listener); + } + + private ImsConfigImplBase getImsConfigImpl() throws RemoteException { + ImsConfigImplBase ref = mImsConfigImplBaseWeakReference.get(); + if (ref == null) { + throw new RemoteException("Fail to get ImsConfigImpl"); + } else { + return ref; + } + } + + private void sendImsConfigChangedIntent(int item, int value) { + sendImsConfigChangedIntent(item, Integer.toString(value)); + } + + private void sendImsConfigChangedIntent(int item, String value) { + Intent configChangedIntent = new Intent(ImsConfig.ACTION_IMS_CONFIG_CHANGED); + configChangedIntent.putExtra(ImsConfig.EXTRA_CHANGED_ITEM, item); + configChangedIntent.putExtra(ImsConfig.EXTRA_NEW_VALUE, value); + if (mContext != null) { + mContext.sendBroadcast(configChangedIntent); + } + } + + protected synchronized void updateCachedValue(int item, int value, boolean notifyChange) { + mProvisionedIntValue.put(item, value); + if (notifyChange) { + sendImsConfigChangedIntent(item, value); + } + } + + protected synchronized void updateCachedValue( + int item, String value, boolean notifyChange) { + mProvisionedStringValue.put(item, value); + if (notifyChange) { + sendImsConfigChangedIntent(item, value); + } + } + } +}
\ No newline at end of file diff --git a/telephony/java/android/telephony/ims/stub/ImsUtListenerImplBase.java b/telephony/java/android/telephony/ims/compat/stub/ImsUtListenerImplBase.java index daa74c8f6f88..b2aa08015d10 100644 --- a/telephony/java/android/telephony/ims/stub/ImsUtListenerImplBase.java +++ b/telephony/java/android/telephony/ims/compat/stub/ImsUtListenerImplBase.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2017 The Android Open Source Project + * Copyright (C) 2018 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,15 +14,15 @@ * limitations under the License */ -package android.telephony.ims.stub; +package android.telephony.ims.compat.stub; import android.os.Bundle; import android.os.RemoteException; -import com.android.ims.ImsCallForwardInfo; -import com.android.ims.ImsReasonInfo; -import com.android.ims.ImsSsData; -import com.android.ims.ImsSsInfo; +import android.telephony.ims.ImsCallForwardInfo; +import android.telephony.ims.ImsReasonInfo; +import android.telephony.ims.ImsSsData; +import android.telephony.ims.ImsSsInfo; import com.android.ims.internal.IImsUt; import com.android.ims.internal.IImsUtListener; diff --git a/telephony/java/android/telephony/ims/internal/feature/CapabilityChangeRequest.aidl b/telephony/java/android/telephony/ims/feature/CapabilityChangeRequest.aidl index f4ec0eb38f34..e789bd5ac940 100644 --- a/telephony/java/android/telephony/ims/internal/feature/CapabilityChangeRequest.aidl +++ b/telephony/java/android/telephony/ims/feature/CapabilityChangeRequest.aidl @@ -14,6 +14,6 @@ * limitations under the License */ -package android.telephony.ims.internal.feature; +package android.telephony.ims.feature; parcelable CapabilityChangeRequest; diff --git a/telephony/java/android/telephony/ims/internal/feature/CapabilityChangeRequest.java b/telephony/java/android/telephony/ims/feature/CapabilityChangeRequest.java index 5dbf077ee7c5..7c793a5c18ac 100644 --- a/telephony/java/android/telephony/ims/internal/feature/CapabilityChangeRequest.java +++ b/telephony/java/android/telephony/ims/feature/CapabilityChangeRequest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2017 The Android Open Source Project + * Copyright (C) 2018 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,8 +14,9 @@ * limitations under the License */ -package android.telephony.ims.internal.feature; +package android.telephony.ims.feature; +import android.annotation.SystemApi; import android.os.Parcel; import android.os.Parcelable; import android.telephony.ims.stub.ImsRegistrationImplBase; @@ -30,17 +31,32 @@ import java.util.Set; * the request. * {@hide} */ -public class CapabilityChangeRequest implements Parcelable { +@SystemApi +public final class CapabilityChangeRequest implements Parcelable { + /** + * Contains a feature capability, defined as + * {@link MmTelFeature.MmTelCapabilities#CAPABILITY_TYPE_VOICE}, + * {@link MmTelFeature.MmTelCapabilities#CAPABILITY_TYPE_VIDEO}, + * {@link MmTelFeature.MmTelCapabilities#CAPABILITY_TYPE_UT}, or + * {@link MmTelFeature.MmTelCapabilities#CAPABILITY_TYPE_SMS}, + * along with an associated technology, defined as + * {@link ImsRegistrationImplBase#REGISTRATION_TECH_LTE} or + * {@link ImsRegistrationImplBase#REGISTRATION_TECH_IWLAN} + */ public static class CapabilityPair { private final int mCapability; private final int radioTech; - public CapabilityPair(int capability, int radioTech) { + public CapabilityPair(@MmTelFeature.MmTelCapabilities.MmTelCapability int capability, + @ImsRegistrationImplBase.ImsRegistrationTech int radioTech) { this.mCapability = capability; this.radioTech = radioTech; } + /** + * @hide + */ @Override public boolean equals(Object o) { if (this == o) return true; @@ -52,6 +68,9 @@ public class CapabilityChangeRequest implements Parcelable { return getRadioTech() == that.getRadioTech(); } + /** + * @hide + */ @Override public int hashCode() { int result = getCapability(); @@ -59,10 +78,22 @@ public class CapabilityChangeRequest implements Parcelable { return result; } + /** + * @return The stored capability, defined as + * {@link MmTelFeature.MmTelCapabilities#CAPABILITY_TYPE_VOICE}, + * {@link MmTelFeature.MmTelCapabilities#CAPABILITY_TYPE_VIDEO}, + * {@link MmTelFeature.MmTelCapabilities#CAPABILITY_TYPE_UT}, or + * {@link MmTelFeature.MmTelCapabilities#CAPABILITY_TYPE_SMS} + */ public @MmTelFeature.MmTelCapabilities.MmTelCapability int getCapability() { return mCapability; } + /** + * @return the stored radio technology, defined as + * {@link ImsRegistrationImplBase#REGISTRATION_TECH_LTE} or + * {@link ImsRegistrationImplBase#REGISTRATION_TECH_IWLAN} + */ public @ImsRegistrationImplBase.ImsRegistrationTech int getRadioTech() { return radioTech; } @@ -73,6 +104,7 @@ public class CapabilityChangeRequest implements Parcelable { // Pair contains <radio tech, mCapability> private final Set<CapabilityPair> mCapabilitiesToDisable; + /** @hide */ public CapabilityChangeRequest() { mCapabilitiesToEnable = new ArraySet<>(); mCapabilitiesToDisable = new ArraySet<>(); @@ -130,6 +162,9 @@ public class CapabilityChangeRequest implements Parcelable { } } + /** + * @hide + */ protected CapabilityChangeRequest(Parcel in) { int enableSize = in.readInt(); mCapabilitiesToEnable = new ArraySet<>(enableSize); @@ -177,17 +212,24 @@ public class CapabilityChangeRequest implements Parcelable { } } + /** + * @hide + */ @Override public boolean equals(Object o) { if (this == o) return true; if (!(o instanceof CapabilityChangeRequest)) return false; - CapabilityChangeRequest that = (CapabilityChangeRequest) o; + CapabilityChangeRequest + that = (CapabilityChangeRequest) o; if (!mCapabilitiesToEnable.equals(that.mCapabilitiesToEnable)) return false; return mCapabilitiesToDisable.equals(that.mCapabilitiesToDisable); } + /** + * @hide + */ @Override public int hashCode() { int result = mCapabilitiesToEnable.hashCode(); diff --git a/telephony/java/android/telephony/ims/feature/ImsFeature.java b/telephony/java/android/telephony/ims/feature/ImsFeature.java index d47cea3097f3..bfdd4533275b 100644 --- a/telephony/java/android/telephony/ims/feature/ImsFeature.java +++ b/telephony/java/android/telephony/ims/feature/ImsFeature.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2017 The Android Open Source Project + * Copyright (C) 2018 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,28 +17,35 @@ package android.telephony.ims.feature; import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.SystemApi; import android.content.Context; import android.content.Intent; import android.os.IInterface; +import android.os.RemoteCallbackList; import android.os.RemoteException; import android.telephony.SubscriptionManager; +import android.telephony.ims.aidl.IImsCapabilityCallback; +import android.telephony.ims.stub.ImsRegistrationImplBase; import android.util.Log; import com.android.ims.internal.IImsFeatureStatusCallback; +import com.android.internal.annotations.VisibleForTesting; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; -import java.util.ArrayList; import java.util.Collections; import java.util.Iterator; -import java.util.List; import java.util.Set; import java.util.WeakHashMap; /** - * Base class for all IMS features that are supported by the framework. + * Base class for all IMS features that are supported by the framework. Use a concrete subclass + * of {@link ImsFeature}, such as {@link MmTelFeature} or {@link RcsFeature}. + * * @hide */ +@SystemApi public abstract class ImsFeature { private static final String LOG_TAG = "ImsFeature"; @@ -46,7 +53,8 @@ public abstract class ImsFeature { /** * Action to broadcast when ImsService is up. * Internal use only. - * Only defined here separately compatibility purposes with the old ImsService. + * Only defined here separately for compatibility purposes with the old ImsService. + * * @hide */ public static final String ACTION_IMS_SERVICE_UP = @@ -56,6 +64,7 @@ public abstract class ImsFeature { * Action to broadcast when ImsService is down. * Internal use only. * Only defined here separately for compatibility purposes with the old ImsService. + * * @hide */ public static final String ACTION_IMS_SERVICE_DOWN = @@ -65,67 +74,329 @@ public abstract class ImsFeature { * Part of the ACTION_IMS_SERVICE_UP or _DOWN intents. * A long value; the phone ID corresponding to the IMS service coming up or down. * Only defined here separately for compatibility purposes with the old ImsService. + * * @hide */ public static final String EXTRA_PHONE_ID = "android:phone_id"; - // Invalid feature value - public static final int INVALID = -1; + /** + * Invalid feature value\ + * @hide + */ + public static final int FEATURE_INVALID = -1; // ImsFeatures that are defined in the Manifests. Ensure that these values match the previously // defined values in ImsServiceClass for compatibility purposes. - public static final int EMERGENCY_MMTEL = 0; - public static final int MMTEL = 1; - public static final int RCS = 2; - // Total number of features defined - public static final int MAX = 3; + /** + * This feature supports emergency calling over MMTEL. + */ + public static final int FEATURE_EMERGENCY_MMTEL = 0; + /** + * This feature supports the MMTEL feature. + */ + public static final int FEATURE_MMTEL = 1; + /** + * This feature supports the RCS feature. + */ + public static final int FEATURE_RCS = 2; + /** + * Total number of features defined + * @hide + */ + public static final int FEATURE_MAX = 3; - // Integer values defining the state of the ImsFeature at any time. + /** + * Integer values defining IMS features that are supported in ImsFeature. + * @hide + */ @IntDef(flag = true, value = { - STATE_NOT_AVAILABLE, + FEATURE_EMERGENCY_MMTEL, + FEATURE_MMTEL, + FEATURE_RCS + }) + @Retention(RetentionPolicy.SOURCE) + public @interface FeatureType {} + + /** + * Integer values defining the state of the ImsFeature at any time. + * @hide + */ + @IntDef(flag = true, + value = { + STATE_UNAVAILABLE, STATE_INITIALIZING, STATE_READY, }) @Retention(RetentionPolicy.SOURCE) public @interface ImsState {} - public static final int STATE_NOT_AVAILABLE = 0; + + /** + * This {@link ImsFeature}'s state is unavailable and should not be communicated with. + */ + public static final int STATE_UNAVAILABLE = 0; + /** + * This {@link ImsFeature} state is initializing and should not be communicated with. + */ public static final int STATE_INITIALIZING = 1; + /** + * This {@link ImsFeature} is ready for communication. + */ public static final int STATE_READY = 2; + /** + * Integer values defining the result codes that should be returned from + * {@link #changeEnabledCapabilities} when the framework tries to set a feature's capability. + * @hide + */ + @IntDef(flag = true, + value = { + CAPABILITY_ERROR_GENERIC, + CAPABILITY_SUCCESS + }) + @Retention(RetentionPolicy.SOURCE) + public @interface ImsCapabilityError {} + + /** + * The capability was unable to be changed. + */ + public static final int CAPABILITY_ERROR_GENERIC = -1; + /** + * The capability was able to be changed. + */ + public static final int CAPABILITY_SUCCESS = 0; + + + /** + * The framework implements this callback in order to register for Feature Capability status + * updates, via {@link #onCapabilitiesStatusChanged(Capabilities)}, query Capability + * configurations, via {@link #onQueryCapabilityConfiguration}, as well as to receive error + * callbacks when the ImsService can not change the capability as requested, via + * {@link #onChangeCapabilityConfigurationError}. + * + * @hide + */ + public static class CapabilityCallback extends IImsCapabilityCallback.Stub { + + @Override + public final void onCapabilitiesStatusChanged(int config) throws RemoteException { + onCapabilitiesStatusChanged(new Capabilities(config)); + } + + /** + * Returns the result of a query for the capability configuration of a requested capability. + * + * @param capability The capability that was requested. + * @param radioTech The IMS radio technology associated with the capability. + * @param isEnabled true if the capability is enabled, false otherwise. + */ + @Override + public void onQueryCapabilityConfiguration(int capability, int radioTech, + boolean isEnabled) { + + } + + /** + * Called when a change to the capability configuration has returned an error. + * + * @param capability The capability that was requested to be changed. + * @param radioTech The IMS radio technology associated with the capability. + * @param reason error associated with the failure to change configuration. + */ + @Override + public void onChangeCapabilityConfigurationError(int capability, int radioTech, + @ImsCapabilityError int reason) { + } + + /** + * The status of the feature's capabilities has changed to either available or unavailable. + * If unavailable, the feature is not able to support the unavailable capability at this + * time. + * + * @param config The new availability of the capabilities. + */ + public void onCapabilitiesStatusChanged(Capabilities config) { + } + } + + /** + * Used by the ImsFeature to call back to the CapabilityCallback that the framework has + * provided. + */ + protected static class CapabilityCallbackProxy { + private final IImsCapabilityCallback mCallback; + + /** @hide */ + public CapabilityCallbackProxy(IImsCapabilityCallback c) { + mCallback = c; + } + + /** + * This method notifies the provided framework callback that the request to change the + * indicated capability has failed and has not changed. + * + * @param capability The Capability that will be notified to the framework, defined as + * {@link MmTelFeature.MmTelCapabilities#CAPABILITY_TYPE_VOICE}, + * {@link MmTelFeature.MmTelCapabilities#CAPABILITY_TYPE_VIDEO}, + * {@link MmTelFeature.MmTelCapabilities#CAPABILITY_TYPE_UT}, or + * {@link MmTelFeature.MmTelCapabilities#CAPABILITY_TYPE_SMS}. + * @param radioTech The radio tech that this capability failed for, defined as + * {@link ImsRegistrationImplBase#REGISTRATION_TECH_LTE} or + * {@link ImsRegistrationImplBase#REGISTRATION_TECH_IWLAN}. + * @param reason The reason this capability was unable to be changed, defined as + * {@link #CAPABILITY_ERROR_GENERIC} or {@link #CAPABILITY_SUCCESS}. + */ + public void onChangeCapabilityConfigurationError(int capability, int radioTech, + @ImsCapabilityError int reason) { + if (mCallback == null) { + return; + } + try { + mCallback.onChangeCapabilityConfigurationError(capability, radioTech, reason); + } catch (RemoteException e) { + Log.e(LOG_TAG, "onChangeCapabilityConfigurationError called on dead binder."); + } + } + } + + /** + * Contains the capabilities defined and supported by an ImsFeature in the form of a bit mask. + * @hide + */ + public static class Capabilities { + protected int mCapabilities = 0; + + public Capabilities() { + } + + protected Capabilities(int capabilities) { + mCapabilities = capabilities; + } + + /** + * @param capabilities Capabilities to be added to the configuration in the form of a + * bit mask. + */ + public void addCapabilities(int capabilities) { + mCapabilities |= capabilities; + } + + /** + * @param capabilities Capabilities to be removed to the configuration in the form of a + * bit mask. + */ + public void removeCapabilities(int capabilities) { + mCapabilities &= ~capabilities; + } + + /** + * @return true if all of the capabilities specified are capable. + */ + public boolean isCapable(int capabilities) { + return (mCapabilities & capabilities) == capabilities; + } + + /** + * @return a deep copy of the Capabilites. + */ + public Capabilities copy() { + return new Capabilities(mCapabilities); + } + + /** + * @return a bitmask containing the capability flags directly. + */ + public int getMask() { + return mCapabilities; + } + + /** + * @hide + */ + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof Capabilities)) return false; + + Capabilities that = (Capabilities) o; + + return mCapabilities == that.mCapabilities; + } + + /** + * @hide + */ + @Override + public int hashCode() { + return mCapabilities; + } + + /** + * @hide + */ + @Override + public String toString() { + return "Capabilities: " + Integer.toBinaryString(mCapabilities); + } + } + private final Set<IImsFeatureStatusCallback> mStatusCallbacks = Collections.newSetFromMap( new WeakHashMap<IImsFeatureStatusCallback, Boolean>()); - private @ImsState int mState = STATE_NOT_AVAILABLE; + private @ImsState int mState = STATE_UNAVAILABLE; private int mSlotId = SubscriptionManager.INVALID_SIM_SLOT_INDEX; + /** + * @hide + */ protected Context mContext; + private final Object mLock = new Object(); + private final RemoteCallbackList<IImsCapabilityCallback> mCapabilityCallbacks + = new RemoteCallbackList<>(); + private Capabilities mCapabilityStatus = new Capabilities(); - public void setContext(Context context) { + /** + * @hide + */ + public final void initialize(Context context, int slotId) { mContext = context; - } - - public void setSlotId(int slotId) { mSlotId = slotId; } + /** + * @return The current state of the feature, defined as {@link #STATE_UNAVAILABLE}, + * {@link #STATE_INITIALIZING}, or {@link #STATE_READY}. + * @hide + */ public int getFeatureState() { - return mState; + synchronized (mLock) { + return mState; + } } - protected final void setFeatureState(@ImsState int state) { - if (mState != state) { - mState = state; - notifyFeatureState(state); + /** + * Set the state of the ImsFeature. The state is used as a signal to the framework to start or + * stop communication, depending on the state sent. + * @param state The ImsFeature's state, defined as {@link #STATE_UNAVAILABLE}, + * {@link #STATE_INITIALIZING}, or {@link #STATE_READY}. + */ + public final void setFeatureState(@ImsState int state) { + synchronized (mLock) { + if (mState != state) { + mState = state; + notifyFeatureState(state); + } } } - public void addImsFeatureStatusCallback(IImsFeatureStatusCallback c) { - if (c == null) { - return; - } + /** + * Not final for testing, but shouldn't be extended! + * @hide + */ + @VisibleForTesting + public void addImsFeatureStatusCallback(@NonNull IImsFeatureStatusCallback c) { try { // If we have just connected, send queued status. - c.notifyImsFeatureStatus(mState); + c.notifyImsFeatureStatus(getFeatureState()); // Add the callback if the callback completes successfully without a RemoteException. - synchronized (mStatusCallbacks) { + synchronized (mLock) { mStatusCallbacks.add(c); } } catch (RemoteException e) { @@ -133,23 +404,24 @@ public abstract class ImsFeature { } } - public void removeImsFeatureStatusCallback(IImsFeatureStatusCallback c) { - if (c == null) { - return; - } - synchronized (mStatusCallbacks) { + /** + * Not final for testing, but shouldn't be extended! + * @hide + */ + @VisibleForTesting + public void removeImsFeatureStatusCallback(@NonNull IImsFeatureStatusCallback c) { + synchronized (mLock) { mStatusCallbacks.remove(c); } } /** * Internal method called by ImsFeature when setFeatureState has changed. - * @param state */ private void notifyFeatureState(@ImsState int state) { - synchronized (mStatusCallbacks) { + synchronized (mLock) { for (Iterator<IImsFeatureStatusCallback> iter = mStatusCallbacks.iterator(); - iter.hasNext(); ) { + iter.hasNext(); ) { IImsFeatureStatusCallback callback = iter.next(); try { Log.i(LOG_TAG, "notifying ImsFeatureState=" + state); @@ -168,12 +440,12 @@ public abstract class ImsFeature { * Provide backwards compatibility using deprecated service UP/DOWN intents. */ private void sendImsServiceIntent(@ImsState int state) { - if(mContext == null || mSlotId == SubscriptionManager.INVALID_SIM_SLOT_INDEX) { + if (mContext == null || mSlotId == SubscriptionManager.INVALID_SIM_SLOT_INDEX) { return; } Intent intent; switch (state) { - case ImsFeature.STATE_NOT_AVAILABLE: + case ImsFeature.STATE_UNAVAILABLE: case ImsFeature.STATE_INITIALIZING: intent = new Intent(ACTION_IMS_SERVICE_DOWN); break; @@ -188,17 +460,104 @@ public abstract class ImsFeature { } /** - * Called when the feature is ready to use. + * @hide */ - public abstract void onFeatureReady(); + public final void addCapabilityCallback(IImsCapabilityCallback c) { + mCapabilityCallbacks.register(c); + } + + /** + * @hide + */ + public final void removeCapabilityCallback(IImsCapabilityCallback c) { + mCapabilityCallbacks.unregister(c); + } + + /** + * @return the cached capabilities status for this feature. + * @hide + */ + @VisibleForTesting + public Capabilities queryCapabilityStatus() { + synchronized (mLock) { + return mCapabilityStatus.copy(); + } + } + + /** + * Called internally to request the change of enabled capabilities. + * @hide + */ + @VisibleForTesting + public final void requestChangeEnabledCapabilities(CapabilityChangeRequest request, + IImsCapabilityCallback c) { + if (request == null) { + throw new IllegalArgumentException( + "ImsFeature#requestChangeEnabledCapabilities called with invalid params."); + } + changeEnabledCapabilities(request, new CapabilityCallbackProxy(c)); + } /** - * Called when the feature is being removed and must be cleaned up. + * Called by the ImsFeature when the capabilities status has changed. + * + * @param c A {@link Capabilities} containing the new Capabilities status. + * + * @hide + */ + protected final void notifyCapabilitiesStatusChanged(Capabilities c) { + synchronized (mLock) { + mCapabilityStatus = c.copy(); + } + int count = mCapabilityCallbacks.beginBroadcast(); + try { + for (int i = 0; i < count; i++) { + try { + mCapabilityCallbacks.getBroadcastItem(i).onCapabilitiesStatusChanged( + c.mCapabilities); + } catch (RemoteException e) { + Log.w(LOG_TAG, e + " " + "notifyCapabilitiesStatusChanged() - Skipping " + + "callback."); + } + } + } finally { + mCapabilityCallbacks.finishBroadcast(); + } + } + + /** + * Features should override this method to receive Capability preference change requests from + * the framework using the provided {@link CapabilityChangeRequest}. If any of the capabilities + * in the {@link CapabilityChangeRequest} are not able to be completed due to an error, + * {@link CapabilityCallbackProxy#onChangeCapabilityConfigurationError} should be called for + * each failed capability. + * + * @param request A {@link CapabilityChangeRequest} containing requested capabilities to + * enable/disable. + * @param c A {@link CapabilityCallbackProxy}, which will be used to call back to the framework + * setting a subset of these capabilities fail, using + * {@link CapabilityCallbackProxy#onChangeCapabilityConfigurationError}. + */ + public abstract void changeEnabledCapabilities(CapabilityChangeRequest request, + CapabilityCallbackProxy c); + + /** + * Called when the framework is removing this feature and it needs to be cleaned up. */ public abstract void onFeatureRemoved(); /** - * @return Binder instance + * Called when the feature has been initialized and communication with the framework is set up. + * Any attempt by this feature to access the framework before this method is called will return + * with an {@link IllegalStateException}. + * The IMS provider should use this method to trigger registration for this feature on the IMS + * network, if needed. + */ + public abstract void onFeatureReady(); + + /** + * @return Binder instance that the framework will use to communicate with this feature. + * @hide */ - public abstract IInterface getBinder(); + protected abstract IInterface getBinder(); } diff --git a/telephony/java/android/telephony/ims/internal/feature/MmTelFeature.java b/telephony/java/android/telephony/ims/feature/MmTelFeature.java index 9b576c72fa96..09267fc2554c 100644 --- a/telephony/java/android/telephony/ims/internal/feature/MmTelFeature.java +++ b/telephony/java/android/telephony/ims/feature/MmTelFeature.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2017 The Android Open Source Project + * Copyright (C) 2018 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,29 +14,33 @@ * limitations under the License */ -package android.telephony.ims.internal.feature; +package android.telephony.ims.feature; import android.annotation.IntDef; +import android.annotation.SystemApi; +import android.net.Uri; +import android.os.Bundle; import android.os.Message; import android.os.RemoteException; import android.telecom.TelecomManager; -import android.telephony.ims.internal.ImsCallSessionListener; -import android.telephony.ims.internal.aidl.IImsCallSessionListener; -import android.telephony.ims.internal.aidl.IImsCapabilityCallback; -import android.telephony.ims.internal.aidl.IImsMmTelFeature; -import android.telephony.ims.internal.aidl.IImsMmTelListener; import android.telephony.ims.stub.ImsRegistrationImplBase; +import android.telephony.ims.stub.ImsCallSessionImplBase; +import android.telephony.ims.stub.ImsSmsImplBase; +import android.telephony.ims.aidl.IImsCapabilityCallback; +import android.telephony.ims.aidl.IImsMmTelFeature; +import android.telephony.ims.aidl.IImsMmTelListener; +import android.telephony.ims.aidl.IImsSmsListener; import android.telephony.ims.stub.ImsEcbmImplBase; import android.telephony.ims.stub.ImsMultiEndpointImplBase; import android.telephony.ims.stub.ImsUtImplBase; import android.util.Log; -import com.android.ims.ImsCallProfile; +import android.telephony.ims.ImsCallProfile; import com.android.ims.internal.IImsCallSession; import com.android.ims.internal.IImsEcbm; import com.android.ims.internal.IImsMultiEndpoint; import com.android.ims.internal.IImsUt; -import com.android.ims.internal.ImsCallSession; +import android.telephony.ims.ImsCallSession; import com.android.internal.annotations.VisibleForTesting; import java.lang.annotation.Retention; @@ -49,7 +53,7 @@ import java.lang.annotation.RetentionPolicy; * service supports. * @hide */ - +@SystemApi public class MmTelFeature extends ImsFeature { private static final String LOG_TAG = "MmTelFeature"; @@ -66,7 +70,11 @@ public class MmTelFeature extends ImsFeature { @Override public int getFeatureState() throws RemoteException { synchronized (mLock) { - return MmTelFeature.this.getFeatureState(); + try { + return MmTelFeature.this.getFeatureState(); + } catch (Exception e) { + throw new RemoteException(e.getMessage()); + } } } @@ -75,45 +83,57 @@ public class MmTelFeature extends ImsFeature { public ImsCallProfile createCallProfile(int callSessionType, int callType) throws RemoteException { synchronized (mLock) { - return MmTelFeature.this.createCallProfile(callSessionType, callType); + try { + return MmTelFeature.this.createCallProfile(callSessionType, callType); + } catch (Exception e) { + throw new RemoteException(e.getMessage()); + } + } + } + + @Override + public IImsCallSession createCallSession(ImsCallProfile profile) throws RemoteException { + synchronized (mLock) { + return createCallSessionInterface(profile); } } @Override - public IImsCallSession createCallSession(ImsCallProfile profile, - IImsCallSessionListener listener) throws RemoteException { + public int shouldProcessCall(String[] numbers) { synchronized (mLock) { - ImsCallSession s = MmTelFeature.this.createCallSession(profile, - new ImsCallSessionListener(listener)); - return s != null ? s.getSession() : null; + return MmTelFeature.this.shouldProcessCall(numbers); } } @Override public IImsUt getUtInterface() throws RemoteException { synchronized (mLock) { - return MmTelFeature.this.getUt(); + return MmTelFeature.this.getUtInterface(); } } @Override public IImsEcbm getEcbmInterface() throws RemoteException { synchronized (mLock) { - return MmTelFeature.this.getEcbm(); + return MmTelFeature.this.getEcbmInterface(); } } @Override public void setUiTtyMode(int uiTtyMode, Message onCompleteMessage) throws RemoteException { synchronized (mLock) { - MmTelFeature.this.setUiTtyMode(uiTtyMode, onCompleteMessage); + try { + MmTelFeature.this.setUiTtyMode(uiTtyMode, onCompleteMessage); + } catch (Exception e) { + throw new RemoteException(e.getMessage()); + } } } @Override public IImsMultiEndpoint getMultiEndpointInterface() throws RemoteException { synchronized (mLock) { - return MmTelFeature.this.getMultiEndpoint(); + return MmTelFeature.this.getMultiEndpointInterface(); } } @@ -143,11 +163,56 @@ public class MmTelFeature extends ImsFeature { IImsCapabilityCallback c) { queryCapabilityConfigurationInternal(capability, radioTech, c); } + + @Override + public void setSmsListener(IImsSmsListener l) throws RemoteException { + MmTelFeature.this.setSmsListener(l); + } + + @Override + public void sendSms(int token, int messageRef, String format, String smsc, boolean retry, + byte[] pdu) { + synchronized (mLock) { + MmTelFeature.this.sendSms(token, messageRef, format, smsc, retry, pdu); + } + } + + @Override + public void acknowledgeSms(int token, int messageRef, int result) { + synchronized (mLock) { + MmTelFeature.this.acknowledgeSms(token, messageRef, result); + } + } + + @Override + public void acknowledgeSmsReport(int token, int messageRef, int result) { + synchronized (mLock) { + MmTelFeature.this.acknowledgeSmsReport(token, messageRef, result); + } + } + + @Override + public String getSmsFormat() { + synchronized (mLock) { + return MmTelFeature.this.getSmsFormat(); + } + } + + @Override + public void onSmsReady() { + synchronized (mLock) { + MmTelFeature.this.onSmsReady(); + } + } }; /** * Contains the capabilities defined and supported by a MmTelFeature in the form of a Bitmask. - * The capabilities that are used in MmTelFeature are defined by {@link MmTelCapability}. + * The capabilities that are used in MmTelFeature are defined as + * {@link MmTelCapabilities#CAPABILITY_TYPE_VOICE}, + * {@link MmTelCapabilities#CAPABILITY_TYPE_VIDEO}, + * {@link MmTelCapabilities#CAPABILITY_TYPE_UT}, and + * {@link MmTelCapabilities#CAPABILITY_TYPE_SMS}. * * The capabilities of this MmTelFeature will be set by the framework and can be queried with * {@link #queryCapabilityStatus()}. @@ -158,6 +223,9 @@ public class MmTelFeature extends ImsFeature { */ public static class MmTelCapabilities extends Capabilities { + /** + * @hide + */ @VisibleForTesting public MmTelCapabilities() { super(); @@ -167,6 +235,10 @@ public class MmTelFeature extends ImsFeature { mCapabilities = c.mCapabilities; } + public MmTelCapabilities(int capabilities) { + mCapabilities = capabilities; + } + @IntDef(flag = true, value = { CAPABILITY_TYPE_VOICE, @@ -211,16 +283,36 @@ public class MmTelFeature extends ImsFeature { public final boolean isCapable(@MmTelCapability int capabilities) { return super.isCapable(capabilities); } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder("MmTel Capabilities - ["); + builder.append("Voice: "); + builder.append(isCapable(CAPABILITY_TYPE_VOICE)); + builder.append(" Video: "); + builder.append(isCapable(CAPABILITY_TYPE_VIDEO)); + builder.append(" UT: "); + builder.append(isCapable(CAPABILITY_TYPE_UT)); + builder.append(" SMS: "); + builder.append(isCapable(CAPABILITY_TYPE_SMS)); + builder.append("]"); + return builder.toString(); + } } /** * Listener that the framework implements for communication from the MmTelFeature. + * @hide */ public static class Listener extends IImsMmTelListener.Stub { + /** + * Called when the IMS provider receives an incoming call. + * @param c The {@link ImsCallSession} associated with the new call. + */ @Override - public final void onIncomingCall(IImsCallSession c) { - onIncomingCall(new ImsCallSession(c)); + public void onIncomingCall(IImsCallSession c, Bundle extras) { + } /** @@ -231,15 +323,34 @@ public class MmTelFeature extends ImsFeature { public void onVoiceMessageCountUpdate(int count) { } - - /** - * Called when the IMS provider receives an incoming call. - * @param c The {@link ImsCallSession} associated with the new call. - */ - public void onIncomingCall(ImsCallSession c) { - } } + /** + * To be returned by {@link #shouldProcessCall(String[])} when the ImsService should process the + * outgoing call as IMS. + */ + public static final int PROCESS_CALL_IMS = 0; + /** + * To be returned by {@link #shouldProcessCall(String[])} when the telephony framework should + * not process the outgoing NON_EMERGENCY call as IMS and should instead use circuit switch. + */ + public static final int PROCESS_CALL_CSFB = 1; + /** + * To be returned by {@link #shouldProcessCall(String[])} when the telephony framework should + * not process the outgoing EMERGENCY call as IMS and should instead use circuit switch. + */ + public static final int PROCESS_CALL_EMERGENCY_CSFB = 2; + + @IntDef(flag = true, + value = { + PROCESS_CALL_IMS, + PROCESS_CALL_CSFB, + PROCESS_CALL_EMERGENCY_CSFB + }) + @Retention(RetentionPolicy.SOURCE) + public @interface ProcessCallResult {} + + // Lock for feature synchronization private final Object mLock = new Object(); private IImsMmTelListener mListener; @@ -252,6 +363,9 @@ public class MmTelFeature extends ImsFeature { synchronized (mLock) { mListener = listener; } + if (mListener != null) { + onFeatureReady(); + } } private void queryCapabilityConfigurationInternal(int capability, int radioTech, @@ -289,23 +403,58 @@ public class MmTelFeature extends ImsFeature { * support the capability that is enabled. A capability that is disabled by the framework (via * {@link #changeEnabledCapabilities}) should also show the status as disabled. */ - protected final void notifyCapabilitiesStatusChanged(MmTelCapabilities c) { + public final void notifyCapabilitiesStatusChanged(MmTelCapabilities c) { super.notifyCapabilitiesStatusChanged(c); } /** * Notify the framework of an incoming call. - * @param c The {@link ImsCallSession} of the new incoming call. + * @param c The {@link ImsCallSessionImplBase} of the new incoming call. + */ + public final void notifyIncomingCall(ImsCallSessionImplBase c, Bundle extras) { + synchronized (mLock) { + if (mListener == null) { + throw new IllegalStateException("Session is not available."); + } + try { + mListener.onIncomingCall(c.getServiceImpl(), extras); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + } + } + + /** * - * @throws RemoteException if the connection to the framework is not available. If this happens, - * the call should be no longer considered active and should be cleaned up. - * */ - protected final void notifyIncomingCall(ImsCallSession c) throws RemoteException { + * @hide + */ + public final void notifyIncomingCallSession(IImsCallSession c, Bundle extras) { + synchronized (mLock) { + if (mListener == null) { + throw new IllegalStateException("Session is not available."); + } + try { + mListener.onIncomingCall(c, extras); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + } + } + + /** + * Notify the framework of a change in the Voice Message count. + * @link count the new Voice Message count. + */ + public final void notifyVoiceMessageCountUpdate(int count) { synchronized (mLock) { if (mListener == null) { throw new IllegalStateException("Session is not available."); } - mListener.onIncomingCall(c.getSession()); + try { + mListener.onVoiceMessageCountUpdate(count); + } catch (RemoteException e) { + throw new RuntimeException(e); + } } } @@ -365,21 +514,67 @@ public class MmTelFeature extends ImsFeature { } /** + * @hide + */ + public IImsCallSession createCallSessionInterface(ImsCallProfile profile) + throws RemoteException { + ImsCallSessionImplBase s = MmTelFeature.this.createCallSession(profile); + return s != null ? s.getServiceImpl() : null; + } + + /** * Creates an {@link ImsCallSession} with the specified call profile. * Use other methods, if applicable, instead of interacting with * {@link ImsCallSession} directly. * * @param profile a call profile to make the call - * @param listener An implementation of IImsCallSessionListener. */ - public ImsCallSession createCallSession(ImsCallProfile profile, - ImsCallSessionListener listener) { + public ImsCallSessionImplBase createCallSession(ImsCallProfile profile) { // Base Implementation - Should be overridden return null; } /** - * @return The Ut interface for the supplementary service configuration. + * Called by the framework to determine if the outgoing call, designated by the outgoing + * {@link Uri}s, should be processed as an IMS call or CSFB call. + * @param numbers An array of {@link String}s that will be used for placing the call. There can + * be multiple {@link String}s listed in the case when we want to place an outgoing + * call as a conference. + * @return a {@link ProcessCallResult} to the framework, which will be used to determine if the + * call wil lbe placed over IMS or via CSFB. + */ + public @ProcessCallResult int shouldProcessCall(String[] numbers) { + return PROCESS_CALL_IMS; + } + + /** + * + * @hide + */ + protected IImsUt getUtInterface() throws RemoteException { + ImsUtImplBase utImpl = getUt(); + return utImpl != null ? utImpl.getInterface() : null; + } + + /** + * @hide + */ + protected IImsEcbm getEcbmInterface() throws RemoteException { + ImsEcbmImplBase ecbmImpl = getEcbm(); + return ecbmImpl != null ? ecbmImpl.getImsEcbm() : null; + } + + /** + * @hide + */ + public IImsMultiEndpoint getMultiEndpointInterface() throws RemoteException { + ImsMultiEndpointImplBase multiendpointImpl = getMultiEndpoint(); + return multiendpointImpl != null ? multiendpointImpl.getIImsMultiEndpoint() : null; + } + + /** + * @return The {@link ImsUtImplBase} Ut interface implementation for the supplementary service + * configuration. */ public ImsUtImplBase getUt() { // Base Implementation - Should be overridden @@ -387,7 +582,8 @@ public class MmTelFeature extends ImsFeature { } /** - * @return The Emergency call-back mode interface for emergency VoLTE calls that support it. + * @return The {@link ImsEcbmImplBase} Emergency call-back mode interface for emergency VoLTE + * calls that support it. */ public ImsEcbmImplBase getEcbm() { // Base Implementation - Should be overridden @@ -395,7 +591,8 @@ public class MmTelFeature extends ImsFeature { } /** - * @return The Emergency call-back mode interface for emergency VoLTE calls that support it. + * @return The {@link ImsMultiEndpointImplBase} implementation for implementing Dialog event + * package processing for multi-endpoint. */ public ImsMultiEndpointImplBase getMultiEndpoint() { // Base Implementation - Should be overridden @@ -411,10 +608,48 @@ public class MmTelFeature extends ImsFeature { * {@link TelecomManager#TTY_MODE_VCO} * @param onCompleteMessage A {@link Message} to be used when the mode has been set. */ - void setUiTtyMode(int mode, Message onCompleteMessage) { + public void setUiTtyMode(int mode, Message onCompleteMessage) { // Base Implementation - Should be overridden } + private void setSmsListener(IImsSmsListener listener) { + getSmsImplementation().registerSmsListener(listener); + } + + private void sendSms(int token, int messageRef, String format, String smsc, boolean isRetry, + byte[] pdu) { + getSmsImplementation().sendSms(token, messageRef, format, smsc, isRetry, pdu); + } + + private void acknowledgeSms(int token, int messageRef, + @ImsSmsImplBase.DeliverStatusResult int result) { + getSmsImplementation().acknowledgeSms(token, messageRef, result); + } + + private void acknowledgeSmsReport(int token, int messageRef, + @ImsSmsImplBase.StatusReportResult int result) { + getSmsImplementation().acknowledgeSmsReport(token, messageRef, result); + } + + private void onSmsReady() { + getSmsImplementation().onReady(); + } + + /** + * Must be overridden by IMS Provider to be able to support SMS over IMS. Otherwise a default + * non-functional implementation is returned. + * + * @return an instance of {@link ImsSmsImplBase} which should be implemented by the IMS + * Provider. + */ + public ImsSmsImplBase getSmsImplementation() { + return new ImsSmsImplBase(); + } + + private String getSmsFormat() { + return getSmsImplementation().getSmsFormat(); + } + /**{@inheritDoc}*/ @Override public void onFeatureRemoved() { diff --git a/telephony/java/android/telephony/ims/feature/RcsFeature.java b/telephony/java/android/telephony/ims/feature/RcsFeature.java index 40c5181d6bca..a637e16d0a48 100644 --- a/telephony/java/android/telephony/ims/feature/RcsFeature.java +++ b/telephony/java/android/telephony/ims/feature/RcsFeature.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2017 The Android Open Source Project + * Copyright (C) 2018 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,16 +16,18 @@ package android.telephony.ims.feature; -import com.android.ims.internal.IImsRcsFeature; +import android.annotation.SystemApi; +import android.telephony.ims.aidl.IImsRcsFeature; /** * Base implementation of the RcsFeature APIs. Any ImsService wishing to support RCS should extend * this class and provide implementations of the RcsFeature methods that they support. * @hide */ - +@SystemApi public class RcsFeature extends ImsFeature { + /**{@inheritDoc}*/ private final IImsRcsFeature mImsRcsBinder = new IImsRcsFeature.Stub() { // Empty Default Implementation. }; @@ -35,16 +37,30 @@ public class RcsFeature extends ImsFeature { super(); } + /** + * {@inheritDoc} + */ @Override - public void onFeatureReady() { - + public void changeEnabledCapabilities(CapabilityChangeRequest request, + CapabilityCallbackProxy c) { + // Do nothing for base implementation. } + /**{@inheritDoc}*/ @Override public void onFeatureRemoved() { } + /**{@inheritDoc}*/ + @Override + public void onFeatureReady() { + + } + + /** + * @hide + */ @Override public final IImsRcsFeature getBinder() { return mImsRcsBinder; diff --git a/telephony/java/android/telephony/ims/internal/ImsCallSessionListener.java b/telephony/java/android/telephony/ims/internal/ImsCallSessionListener.java deleted file mode 100644 index 5d16dd5b30ee..000000000000 --- a/telephony/java/android/telephony/ims/internal/ImsCallSessionListener.java +++ /dev/null @@ -1,364 +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.telephony.ims.internal; - -import android.os.RemoteException; -import android.telephony.ims.internal.aidl.IImsCallSessionListener; - -import com.android.ims.ImsCallProfile; -import com.android.ims.ImsConferenceState; -import com.android.ims.ImsReasonInfo; -import com.android.ims.ImsStreamMediaProfile; -import com.android.ims.ImsSuppServiceNotification; -import com.android.ims.internal.ImsCallSession; - -/** - * Proxy class for interfacing with the framework's Call session for an ongoing IMS call. - * - * DO NOT remove or change the existing APIs, only add new ones to this Base implementation or you - * will break other implementations of ImsCallSessionListener maintained by other ImsServices. - * - * @hide - */ -public class ImsCallSessionListener { - - private final IImsCallSessionListener mListener; - - public ImsCallSessionListener(IImsCallSessionListener l) { - mListener = l; - } - - /** - * Called when a request is sent out to initiate a new session - * and 1xx response is received from the network. - */ - public void callSessionProgressing(ImsStreamMediaProfile profile) - throws RemoteException { - mListener.callSessionProgressing(profile); - } - - /** - * Called when the session is initiated. - * - * @param profile the associated {@link ImsCallSession}. - */ - public void callSessionInitiated(ImsCallProfile profile) throws RemoteException { - mListener.callSessionInitiated(profile); - } - - /** - * Called when the session establishment has failed. - * - * @param reasonInfo detailed reason of the session establishment failure - */ - public void callSessionInitiatedFailed(ImsReasonInfo reasonInfo) throws RemoteException { - mListener.callSessionInitiatedFailed(reasonInfo); - } - - /** - * Called when the session is terminated. - * - * @param reasonInfo detailed reason of the session termination - */ - public void callSessionTerminated(ImsReasonInfo reasonInfo) throws RemoteException { - mListener.callSessionTerminated(reasonInfo); - } - - /** - * Called when the session is on hold. - */ - public void callSessionHeld(ImsCallProfile profile) throws RemoteException { - mListener.callSessionHeld(profile); - } - - /** - * Called when the session hold has failed. - * - * @param reasonInfo detailed reason of the session hold failure - */ - public void callSessionHoldFailed(ImsReasonInfo reasonInfo) throws RemoteException { - mListener.callSessionHoldFailed(reasonInfo); - } - - /** - * Called when the session hold is received from the remote user. - */ - public void callSessionHoldReceived(ImsCallProfile profile) throws RemoteException { - mListener.callSessionHoldReceived(profile); - } - - /** - * Called when the session resume is done. - */ - public void callSessionResumed(ImsCallProfile profile) throws RemoteException { - mListener.callSessionResumed(profile); - } - - /** - * Called when the session resume has failed. - * - * @param reasonInfo detailed reason of the session resume failure - */ - public void callSessionResumeFailed(ImsReasonInfo reasonInfo) throws RemoteException { - mListener.callSessionResumeFailed(reasonInfo); - } - - /** - * Called when the session resume is received from the remote user. - */ - public void callSessionResumeReceived(ImsCallProfile profile) throws RemoteException { - mListener.callSessionResumeReceived(profile); - } - - /** - * Called when the session merge has been started. At this point, the {@code newSession} - * represents the session which has been initiated to the IMS conference server for the - * new merged conference. - * - * @param newSession the session object that is merged with an active & hold session - */ - public void callSessionMergeStarted(ImsCallSession newSession, ImsCallProfile profile) - throws RemoteException { - mListener.callSessionMergeStarted(newSession != null ? newSession.getSession() : null, - profile); - } - - /** - * Called when the session merge is successful and the merged session is active. - * - * @param newSession the new session object that is used for the conference - */ - public void callSessionMergeComplete(ImsCallSession newSession) throws RemoteException { - mListener.callSessionMergeComplete(newSession != null ? newSession.getSession() : null); - } - - /** - * Called when the session merge has failed. - * - * @param reasonInfo detailed reason of the call merge failure - */ - public void callSessionMergeFailed(ImsReasonInfo reasonInfo) throws RemoteException { - mListener.callSessionMergeFailed(reasonInfo); - } - - /** - * Called when the session is updated (except for hold/unhold). - */ - public void callSessionUpdated(ImsCallProfile profile) throws RemoteException { - mListener.callSessionUpdated(profile); - } - - /** - * Called when the session update has failed. - * - * @param reasonInfo detailed reason of the session update failure - */ - public void callSessionUpdateFailed(ImsReasonInfo reasonInfo) throws RemoteException { - mListener.callSessionUpdateFailed(reasonInfo); - } - - /** - * Called when the session update is received from the remote user. - */ - public void callSessionUpdateReceived(ImsCallProfile profile) throws RemoteException { - mListener.callSessionUpdateReceived(profile); - } - - /** - * Called when the session has been extended to a conference session. - * - * @param newSession the session object that is extended to the conference - * from the active session - */ - public void callSessionConferenceExtended(ImsCallSession newSession, ImsCallProfile profile) - throws RemoteException { - mListener.callSessionConferenceExtended(newSession != null ? newSession.getSession() : null, - profile); - } - - /** - * Called when the conference extension has failed. - * - * @param reasonInfo detailed reason of the conference extension failure - */ - public void callSessionConferenceExtendFailed(ImsReasonInfo reasonInfo) throws RemoteException { - mListener.callSessionConferenceExtendFailed(reasonInfo); - } - - /** - * Called when the conference extension is received from the remote user. - */ - public void callSessionConferenceExtendReceived(ImsCallSession newSession, - ImsCallProfile profile) throws RemoteException { - mListener.callSessionConferenceExtendReceived(newSession != null - ? newSession.getSession() : null, profile); - } - - /** - * Called when the invitation request of the participants is delivered to the conference - * server. - */ - public void callSessionInviteParticipantsRequestDelivered() throws RemoteException { - mListener.callSessionInviteParticipantsRequestDelivered(); - } - - /** - * Called when the invitation request of the participants has failed. - * - * @param reasonInfo detailed reason of the conference invitation failure - */ - public void callSessionInviteParticipantsRequestFailed(ImsReasonInfo reasonInfo) - throws RemoteException { - mListener.callSessionInviteParticipantsRequestFailed(reasonInfo); - } - - /** - * Called when the removal request of the participants is delivered to the conference - * server. - */ - public void callSessionRemoveParticipantsRequestDelivered() throws RemoteException { - mListener.callSessionRemoveParticipantsRequestDelivered(); - } - - /** - * Called when the removal request of the participants has failed. - * - * @param reasonInfo detailed reason of the conference removal failure - */ - public void callSessionRemoveParticipantsRequestFailed(ImsReasonInfo reasonInfo) - throws RemoteException { - mListener.callSessionInviteParticipantsRequestFailed(reasonInfo); - } - - /** - * Notifies the framework of the updated Call session conference state. - * - * @param state the new {@link ImsConferenceState} associated with the conference. - */ - public void callSessionConferenceStateUpdated(ImsConferenceState state) throws RemoteException { - mListener.callSessionConferenceStateUpdated(state); - } - - /** - * Notifies the incoming USSD message. - */ - public void callSessionUssdMessageReceived(int mode, String ussdMessage) - throws RemoteException { - mListener.callSessionUssdMessageReceived(mode, ussdMessage); - } - - /** - * Notifies of a case where a {@link com.android.ims.internal.ImsCallSession} may potentially - * handover from one radio technology to another. - * - * @param srcAccessTech The source radio access technology; one of the access technology - * constants defined in {@link android.telephony.ServiceState}. For - * example - * {@link android.telephony.ServiceState#RIL_RADIO_TECHNOLOGY_LTE}. - * @param targetAccessTech The target radio access technology; one of the access technology - * constants defined in {@link android.telephony.ServiceState}. For - * example - * {@link android.telephony.ServiceState#RIL_RADIO_TECHNOLOGY_LTE}. - */ - public void callSessionMayHandover(int srcAccessTech, int targetAccessTech) - throws RemoteException { - mListener.callSessionMayHandover(srcAccessTech, targetAccessTech); - } - - /** - * Called when session access technology changes. - * - * @param srcAccessTech original access technology - * @param targetAccessTech new access technology - * @param reasonInfo - */ - public void callSessionHandover(int srcAccessTech, int targetAccessTech, - ImsReasonInfo reasonInfo) throws RemoteException { - mListener.callSessionHandover(srcAccessTech, targetAccessTech, reasonInfo); - } - - /** - * Called when session access technology change fails. - * - * @param srcAccessTech original access technology - * @param targetAccessTech new access technology - * @param reasonInfo handover failure reason - */ - public void callSessionHandoverFailed(int srcAccessTech, int targetAccessTech, - ImsReasonInfo reasonInfo) throws RemoteException { - mListener.callSessionHandoverFailed(srcAccessTech, targetAccessTech, reasonInfo); - } - - /** - * Called when the TTY mode is changed by the remote party. - * - * @param mode one of the following: - - * {@link com.android.internal.telephony.Phone#TTY_MODE_OFF} - - * {@link com.android.internal.telephony.Phone#TTY_MODE_FULL} - - * {@link com.android.internal.telephony.Phone#TTY_MODE_HCO} - - * {@link com.android.internal.telephony.Phone#TTY_MODE_VCO} - */ - public void callSessionTtyModeReceived(int mode) throws RemoteException { - mListener.callSessionTtyModeReceived(mode); - } - - /** - * Called when the multiparty state is changed for this {@code ImsCallSession}. - * - * @param isMultiParty {@code true} if the session became multiparty, - * {@code false} otherwise. - */ - - public void callSessionMultipartyStateChanged(boolean isMultiParty) throws RemoteException { - mListener.callSessionMultipartyStateChanged(isMultiParty); - } - - /** - * Called when the supplementary service information is received for the current session. - */ - public void callSessionSuppServiceReceived(ImsSuppServiceNotification suppSrvNotification) - throws RemoteException { - mListener.callSessionSuppServiceReceived(suppSrvNotification); - } - - /** - * Received RTT modify request from the remote party. - * - * @param callProfile ImsCallProfile with updated attributes - */ - public void callSessionRttModifyRequestReceived(ImsCallProfile callProfile) - throws RemoteException { - mListener.callSessionRttModifyRequestReceived(callProfile); - } - - /** - * @param status the received response for RTT modify request. - */ - public void callSessionRttModifyResponseReceived(int status) throws RemoteException { - mListener.callSessionRttModifyResponseReceived(status); - } - - /** - * Device received RTT message from Remote UE. - * - * @param rttMessage RTT message received - */ - public void callSessionRttMessageReceived(String rttMessage) throws RemoteException { - mListener.callSessionRttMessageReceived(rttMessage); - } -} - diff --git a/telephony/java/android/telephony/ims/internal/ImsService.java b/telephony/java/android/telephony/ims/internal/ImsService.java deleted file mode 100644 index afaf33294d8a..000000000000 --- a/telephony/java/android/telephony/ims/internal/ImsService.java +++ /dev/null @@ -1,339 +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.telephony.ims.internal; - -import android.app.Service; -import android.content.Intent; -import android.os.IBinder; -import android.os.RemoteException; -import android.telephony.CarrierConfigManager; -import android.telephony.ims.internal.aidl.IImsConfig; -import android.telephony.ims.internal.aidl.IImsMmTelFeature; -import android.telephony.ims.internal.aidl.IImsRcsFeature; -import android.telephony.ims.internal.aidl.IImsServiceController; -import android.telephony.ims.internal.aidl.IImsServiceControllerListener; -import android.telephony.ims.internal.feature.ImsFeature; -import android.telephony.ims.internal.feature.MmTelFeature; -import android.telephony.ims.internal.feature.RcsFeature; -import android.telephony.ims.internal.stub.ImsConfigImplBase; -import android.telephony.ims.internal.stub.ImsFeatureConfiguration; -import android.telephony.ims.stub.ImsRegistrationImplBase; -import android.util.Log; -import android.util.SparseArray; - -import com.android.ims.internal.IImsFeatureStatusCallback; -import com.android.ims.internal.IImsRegistration; -import com.android.internal.annotations.VisibleForTesting; - -/** - * Main ImsService implementation, which binds via the Telephony ImsResolver. Services that extend - * ImsService must register the service in their AndroidManifest to be detected by the framework. - * First, the application must declare that they use the "android.permission.BIND_IMS_SERVICE" - * permission. Then, the ImsService definition in the manifest must follow the following format: - * - * ... - * <service android:name=".EgImsService" - * android:permission="android.permission.BIND_IMS_SERVICE" > - * <!-- Apps must declare which features they support as metadata. The different categories are - * defined below. In this example, the RCS_FEATURE feature is supported. --> - * <meta-data android:name="android.telephony.ims.RCS_FEATURE" android:value="true" /> - * <intent-filter> - * <action android:name="android.telephony.ims.ImsService" /> - * </intent-filter> - * </service> - * ... - * - * The telephony framework will then bind to the ImsService you have defined in your manifest - * if you are either: - * 1) Defined as the default ImsService for the device in the device overlay using - * "config_ims_package". - * 2) Defined as a Carrier Provided ImsService in the Carrier Configuration using - * {@link CarrierConfigManager#KEY_CONFIG_IMS_PACKAGE_OVERRIDE_STRING}. - * - * The features that are currently supported in an ImsService are: - * - RCS_FEATURE: This ImsService implements the RcsFeature class. - * - MMTEL_FEATURE: This ImsService implements the MmTelFeature class. - * @hide - */ -public class ImsService extends Service { - - private static final String LOG_TAG = "ImsService"; - - /** - * The intent that must be defined as an intent-filter in the AndroidManifest of the ImsService. - * @hide - */ - public static final String SERVICE_INTERFACE = "android.telephony.ims.ImsService"; - - // A map of slot Id -> map of features (indexed by ImsFeature feature id) corresponding to that - // slot. - // We keep track of this to facilitate cleanup of the IImsFeatureStatusCallback and - // call ImsFeature#onFeatureRemoved. - private final SparseArray<SparseArray<ImsFeature>> mFeaturesBySlot = new SparseArray<>(); - - private IImsServiceControllerListener mListener; - - - /** - * Listener that notifies the framework of ImsService changes. - */ - public static class Listener extends IImsServiceControllerListener.Stub { - /** - * The IMS features that this ImsService supports has changed. - * @param c a new {@link ImsFeatureConfiguration} containing {@link ImsFeature.FeatureType}s - * that this ImsService supports. This may trigger the addition/removal of feature - * in this service. - */ - public void onUpdateSupportedImsFeatures(ImsFeatureConfiguration c) { - } - } - - /** - * @hide - */ - protected final IBinder mImsServiceController = new IImsServiceController.Stub() { - @Override - public void setListener(IImsServiceControllerListener l) { - mListener = l; - } - - @Override - public IImsMmTelFeature createMmTelFeature(int slotId, IImsFeatureStatusCallback c) { - return createMmTelFeatureInternal(slotId, c); - } - - @Override - public IImsRcsFeature createRcsFeature(int slotId, IImsFeatureStatusCallback c) { - return createRcsFeatureInternal(slotId, c); - } - - @Override - public void removeImsFeature(int slotId, int featureType, IImsFeatureStatusCallback c) - throws RemoteException { - ImsService.this.removeImsFeature(slotId, featureType, c); - } - - @Override - public ImsFeatureConfiguration querySupportedImsFeatures() { - return ImsService.this.querySupportedImsFeatures(); - } - - @Override - public void notifyImsServiceReadyForFeatureCreation() { - ImsService.this.readyForFeatureCreation(); - } - - @Override - public void notifyImsFeatureReady(int slotId, int featureType) - throws RemoteException { - ImsService.this.notifyImsFeatureReady(slotId, featureType); - } - - @Override - public IImsConfig getConfig(int slotId) throws RemoteException { - ImsConfigImplBase c = ImsService.this.getConfig(slotId); - return c != null ? c.getBinder() : null; - } - - @Override - public IImsRegistration getRegistration(int slotId) throws RemoteException { - ImsRegistrationImplBase r = ImsService.this.getRegistration(slotId); - return r != null ? r.getBinder() : null; - } - }; - - /** - * @hide - */ - @Override - public IBinder onBind(Intent intent) { - if(SERVICE_INTERFACE.equals(intent.getAction())) { - Log.i(LOG_TAG, "ImsService Bound."); - return mImsServiceController; - } - return null; - } - - /** - * @hide - */ - @VisibleForTesting - public SparseArray<ImsFeature> getFeatures(int slotId) { - return mFeaturesBySlot.get(slotId); - } - - private IImsMmTelFeature createMmTelFeatureInternal(int slotId, - IImsFeatureStatusCallback c) { - MmTelFeature f = createMmTelFeature(slotId); - if (f != null) { - setupFeature(f, slotId, ImsFeature.FEATURE_MMTEL, c); - return f.getBinder(); - } else { - Log.e(LOG_TAG, "createMmTelFeatureInternal: null feature returned."); - return null; - } - } - - private IImsRcsFeature createRcsFeatureInternal(int slotId, - IImsFeatureStatusCallback c) { - RcsFeature f = createRcsFeature(slotId); - if (f != null) { - setupFeature(f, slotId, ImsFeature.FEATURE_RCS, c); - return f.getBinder(); - } else { - Log.e(LOG_TAG, "createRcsFeatureInternal: null feature returned."); - return null; - } - } - - private void setupFeature(ImsFeature f, int slotId, int featureType, - IImsFeatureStatusCallback c) { - f.addImsFeatureStatusCallback(c); - f.initialize(this, slotId); - addImsFeature(slotId, featureType, f); - } - - private void addImsFeature(int slotId, int featureType, ImsFeature f) { - synchronized (mFeaturesBySlot) { - // Get SparseArray for Features, by querying slot Id - SparseArray<ImsFeature> features = mFeaturesBySlot.get(slotId); - if (features == null) { - // Populate new SparseArray of features if it doesn't exist for this slot yet. - features = new SparseArray<>(); - mFeaturesBySlot.put(slotId, features); - } - features.put(featureType, f); - } - } - - private void removeImsFeature(int slotId, int featureType, - IImsFeatureStatusCallback c) { - synchronized (mFeaturesBySlot) { - // get ImsFeature associated with the slot/feature - SparseArray<ImsFeature> features = mFeaturesBySlot.get(slotId); - if (features == null) { - Log.w(LOG_TAG, "Can not remove ImsFeature. No ImsFeatures exist on slot " - + slotId); - return; - } - ImsFeature f = features.get(featureType); - if (f == null) { - Log.w(LOG_TAG, "Can not remove ImsFeature. No feature with type " - + featureType + " exists on slot " + slotId); - return; - } - f.removeImsFeatureStatusCallback(c); - f.onFeatureRemoved(); - features.remove(featureType); - } - } - - private void notifyImsFeatureReady(int slotId, int featureType) { - synchronized (mFeaturesBySlot) { - // get ImsFeature associated with the slot/feature - SparseArray<ImsFeature> features = mFeaturesBySlot.get(slotId); - if (features == null) { - Log.w(LOG_TAG, "Can not notify ImsFeature ready. No ImsFeatures exist on " + - "slot " + slotId); - return; - } - ImsFeature f = features.get(featureType); - if (f == null) { - Log.w(LOG_TAG, "Can not notify ImsFeature ready. No feature with type " - + featureType + " exists on slot " + slotId); - return; - } - f.onFeatureReady(); - } - } - - /** - * When called, provide the {@link ImsFeatureConfiguration} that this ImsService currently - * supports. This will trigger the framework to set up the {@link ImsFeature}s that correspond - * to the {@link ImsFeature.FeatureType}s configured here. - * @return an {@link ImsFeatureConfiguration} containing Features this ImsService supports, - * defined in {@link ImsFeature.FeatureType}. - */ - public ImsFeatureConfiguration querySupportedImsFeatures() { - // Return empty for base implementation - return new ImsFeatureConfiguration(); - } - - /** - * Updates the framework with a new {@link ImsFeatureConfiguration} containing the updated - * features, defined in {@link ImsFeature.FeatureType} that this ImsService supports. This may - * trigger the framework to add/remove new ImsFeatures, depending on the configuration. - */ - public final void onUpdateSupportedImsFeatures(ImsFeatureConfiguration c) - throws RemoteException { - if (mListener == null) { - throw new IllegalStateException("Framework is not ready"); - } - mListener.onUpdateSupportedImsFeatures(c); - } - - /** - * The ImsService has been bound and is ready for ImsFeature creation based on the Features that - * the ImsService has registered for with the framework, either in the manifest or via - * The ImsService should use this signal instead of onCreate/onBind or similar to perform - * feature initialization because the framework may bind to this service multiple times to - * query the ImsService's {@link ImsFeatureConfiguration} via - * {@link #querySupportedImsFeatures()}before creating features. - */ - public void readyForFeatureCreation() { - } - - /** - * When called, the framework is requesting that a new MmTelFeature is created for the specified - * slot. - * - * @param slotId The slot ID that the MMTel Feature is being created for. - * @return The newly created MmTelFeature associated with the slot or null if the feature is not - * supported. - */ - public MmTelFeature createMmTelFeature(int slotId) { - return null; - } - - /** - * When called, the framework is requesting that a new RcsFeature is created for the specified - * slot - * - * @param slotId The slot ID that the RCS Feature is being created for. - * @return The newly created RcsFeature associated with the slot or null if the feature is not - * supported. - */ - public RcsFeature createRcsFeature(int slotId) { - return null; - } - - /** - * @param slotId The slot that the IMS configuration is associated with. - * @return ImsConfig implementation that is associated with the specified slot. - */ - public ImsConfigImplBase getConfig(int slotId) { - return new ImsConfigImplBase(); - } - - /** - * @param slotId The slot that is associated with the IMS Registration. - * @return the ImsRegistration implementation associated with the slot. - */ - public ImsRegistrationImplBase getRegistration(int slotId) { - return new ImsRegistrationImplBase(); - } -} diff --git a/telephony/java/android/telephony/ims/internal/feature/ImsFeature.java b/telephony/java/android/telephony/ims/internal/feature/ImsFeature.java deleted file mode 100644 index 9f82ad241eaf..000000000000 --- a/telephony/java/android/telephony/ims/internal/feature/ImsFeature.java +++ /dev/null @@ -1,462 +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.telephony.ims.internal.feature; - -import android.annotation.IntDef; -import android.annotation.NonNull; -import android.content.Context; -import android.content.Intent; -import android.os.IInterface; -import android.os.RemoteCallbackList; -import android.os.RemoteException; -import android.telephony.SubscriptionManager; -import android.telephony.ims.internal.aidl.IImsCapabilityCallback; -import android.util.Log; - -import com.android.ims.internal.IImsFeatureStatusCallback; -import com.android.internal.annotations.VisibleForTesting; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.util.Collections; -import java.util.Iterator; -import java.util.Set; -import java.util.WeakHashMap; - -/** - * Base class for all IMS features that are supported by the framework. - * - * @hide - */ -public abstract class ImsFeature { - - private static final String LOG_TAG = "ImsFeature"; - - /** - * Action to broadcast when ImsService is up. - * Internal use only. - * Only defined here separately for compatibility purposes with the old ImsService. - * - * @hide - */ - public static final String ACTION_IMS_SERVICE_UP = - "com.android.ims.IMS_SERVICE_UP"; - - /** - * Action to broadcast when ImsService is down. - * Internal use only. - * Only defined here separately for compatibility purposes with the old ImsService. - * - * @hide - */ - public static final String ACTION_IMS_SERVICE_DOWN = - "com.android.ims.IMS_SERVICE_DOWN"; - - /** - * Part of the ACTION_IMS_SERVICE_UP or _DOWN intents. - * A long value; the phone ID corresponding to the IMS service coming up or down. - * Only defined here separately for compatibility purposes with the old ImsService. - * - * @hide - */ - public static final String EXTRA_PHONE_ID = "android:phone_id"; - - // Invalid feature value - public static final int FEATURE_INVALID = -1; - // ImsFeatures that are defined in the Manifests. Ensure that these values match the previously - // defined values in ImsServiceClass for compatibility purposes. - public static final int FEATURE_EMERGENCY_MMTEL = 0; - public static final int FEATURE_MMTEL = 1; - public static final int FEATURE_RCS = 2; - // Total number of features defined - public static final int FEATURE_MAX = 3; - - // Integer values defining IMS features that are supported in ImsFeature. - @IntDef(flag = true, - value = { - FEATURE_EMERGENCY_MMTEL, - FEATURE_MMTEL, - FEATURE_RCS - }) - @Retention(RetentionPolicy.SOURCE) - public @interface FeatureType {} - - // Integer values defining the state of the ImsFeature at any time. - @IntDef(flag = true, - value = { - STATE_UNAVAILABLE, - STATE_INITIALIZING, - STATE_READY, - }) - @Retention(RetentionPolicy.SOURCE) - public @interface ImsState {} - - public static final int STATE_UNAVAILABLE = 0; - public static final int STATE_INITIALIZING = 1; - public static final int STATE_READY = 2; - - // Integer values defining the result codes that should be returned from - // {@link changeEnabledCapabilities} when the framework tries to set a feature's capability. - @IntDef(flag = true, - value = { - CAPABILITY_ERROR_GENERIC, - CAPABILITY_SUCCESS - }) - @Retention(RetentionPolicy.SOURCE) - public @interface ImsCapabilityError {} - - public static final int CAPABILITY_ERROR_GENERIC = -1; - public static final int CAPABILITY_SUCCESS = 0; - - - /** - * The framework implements this callback in order to register for Feature Capability status - * updates, via {@link #onCapabilitiesStatusChanged(Capabilities)}, query Capability - * configurations, via {@link #onQueryCapabilityConfiguration}, as well as to receive error - * callbacks when the ImsService can not change the capability as requested, via - * {@link #onChangeCapabilityConfigurationError}. - */ - public static class CapabilityCallback extends IImsCapabilityCallback.Stub { - - @Override - public final void onCapabilitiesStatusChanged(int config) throws RemoteException { - onCapabilitiesStatusChanged(new Capabilities(config)); - } - - /** - * Returns the result of a query for the capability configuration of a requested capability. - * - * @param capability The capability that was requested. - * @param radioTech The IMS radio technology associated with the capability. - * @param isEnabled true if the capability is enabled, false otherwise. - */ - @Override - public void onQueryCapabilityConfiguration(int capability, int radioTech, - boolean isEnabled) { - - } - - /** - * Called when a change to the capability configuration has returned an error. - * - * @param capability The capability that was requested to be changed. - * @param radioTech The IMS radio technology associated with the capability. - * @param reason error associated with the failure to change configuration. - */ - @Override - public void onChangeCapabilityConfigurationError(int capability, int radioTech, - int reason) { - } - - /** - * The status of the feature's capabilities has changed to either available or unavailable. - * If unavailable, the feature is not able to support the unavailable capability at this - * time. - * - * @param config The new availability of the capabilities. - */ - public void onCapabilitiesStatusChanged(Capabilities config) { - } - } - - /** - * Used by the ImsFeature to call back to the CapabilityCallback that the framework has - * provided. - */ - protected static class CapabilityCallbackProxy { - private final IImsCapabilityCallback mCallback; - - public CapabilityCallbackProxy(IImsCapabilityCallback c) { - mCallback = c; - } - - /** - * This method notifies the provided framework callback that the request to change the - * indicated capability has failed and has not changed. - * - * @param capability The Capability that will be notified to the framework. - * @param radioTech The radio tech that this capability failed for. - * @param reason The reason this capability was unable to be changed. - */ - public void onChangeCapabilityConfigurationError(int capability, int radioTech, - @ImsCapabilityError int reason) { - try { - mCallback.onChangeCapabilityConfigurationError(capability, radioTech, reason); - } catch (RemoteException e) { - Log.e(LOG_TAG, "onChangeCapabilityConfigurationError called on dead binder."); - } - } - - public void onQueryCapabilityConfiguration(int capability, int radioTech, - boolean isEnabled) { - try { - mCallback.onQueryCapabilityConfiguration(capability, radioTech, isEnabled); - } catch (RemoteException e) { - Log.e(LOG_TAG, "onQueryCapabilityConfiguration called on dead binder."); - } - } - } - - /** - * Contains the capabilities defined and supported by an ImsFeature in the form of a bit mask. - */ - public static class Capabilities { - protected int mCapabilities = 0; - - public Capabilities() { - } - - protected Capabilities(int capabilities) { - mCapabilities = capabilities; - } - - /** - * @param capabilities Capabilities to be added to the configuration in the form of a - * bit mask. - */ - public void addCapabilities(int capabilities) { - mCapabilities |= capabilities; - } - - /** - * @param capabilities Capabilities to be removed to the configuration in the form of a - * bit mask. - */ - public void removeCapabilities(int capabilities) { - mCapabilities &= ~capabilities; - } - - /** - * @return true if all of the capabilities specified are capable. - */ - public boolean isCapable(int capabilities) { - return (mCapabilities & capabilities) == capabilities; - } - - public Capabilities copy() { - return new Capabilities(mCapabilities); - } - - /** - * @return a bitmask containing the capability flags directly. - */ - public int getMask() { - return mCapabilities; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (!(o instanceof Capabilities)) return false; - - Capabilities that = (Capabilities) o; - - return mCapabilities == that.mCapabilities; - } - - @Override - public int hashCode() { - return mCapabilities; - } - } - - private final Set<IImsFeatureStatusCallback> mStatusCallbacks = Collections.newSetFromMap( - new WeakHashMap<IImsFeatureStatusCallback, Boolean>()); - private @ImsState int mState = STATE_UNAVAILABLE; - private int mSlotId = SubscriptionManager.INVALID_SIM_SLOT_INDEX; - private Context mContext; - private final Object mLock = new Object(); - private final RemoteCallbackList<IImsCapabilityCallback> mCapabilityCallbacks - = new RemoteCallbackList<>(); - private Capabilities mCapabilityStatus = new Capabilities(); - - public final void initialize(Context context, int slotId) { - mContext = context; - mSlotId = slotId; - } - - public final int getFeatureState() { - synchronized (mLock) { - return mState; - } - } - - protected final void setFeatureState(@ImsState int state) { - synchronized (mLock) { - if (mState != state) { - mState = state; - notifyFeatureState(state); - } - } - } - - // Not final for testing, but shouldn't be extended! - @VisibleForTesting - public void addImsFeatureStatusCallback(@NonNull IImsFeatureStatusCallback c) { - try { - // If we have just connected, send queued status. - c.notifyImsFeatureStatus(getFeatureState()); - // Add the callback if the callback completes successfully without a RemoteException. - synchronized (mLock) { - mStatusCallbacks.add(c); - } - } catch (RemoteException e) { - Log.w(LOG_TAG, "Couldn't notify feature state: " + e.getMessage()); - } - } - - @VisibleForTesting - // Not final for testing, but should not be extended! - public void removeImsFeatureStatusCallback(@NonNull IImsFeatureStatusCallback c) { - synchronized (mLock) { - mStatusCallbacks.remove(c); - } - } - - /** - * Internal method called by ImsFeature when setFeatureState has changed. - */ - private void notifyFeatureState(@ImsState int state) { - synchronized (mLock) { - for (Iterator<IImsFeatureStatusCallback> iter = mStatusCallbacks.iterator(); - iter.hasNext(); ) { - IImsFeatureStatusCallback callback = iter.next(); - try { - Log.i(LOG_TAG, "notifying ImsFeatureState=" + state); - callback.notifyImsFeatureStatus(state); - } catch (RemoteException e) { - // remove if the callback is no longer alive. - iter.remove(); - Log.w(LOG_TAG, "Couldn't notify feature state: " + e.getMessage()); - } - } - } - sendImsServiceIntent(state); - } - - /** - * Provide backwards compatibility using deprecated service UP/DOWN intents. - */ - private void sendImsServiceIntent(@ImsState int state) { - if (mContext == null || mSlotId == SubscriptionManager.INVALID_SIM_SLOT_INDEX) { - return; - } - Intent intent; - switch (state) { - case ImsFeature.STATE_UNAVAILABLE: - case ImsFeature.STATE_INITIALIZING: - intent = new Intent(ACTION_IMS_SERVICE_DOWN); - break; - case ImsFeature.STATE_READY: - intent = new Intent(ACTION_IMS_SERVICE_UP); - break; - default: - intent = new Intent(ACTION_IMS_SERVICE_DOWN); - } - intent.putExtra(EXTRA_PHONE_ID, mSlotId); - mContext.sendBroadcast(intent); - } - - public final void addCapabilityCallback(IImsCapabilityCallback c) { - mCapabilityCallbacks.register(c); - } - - public final void removeCapabilityCallback(IImsCapabilityCallback c) { - mCapabilityCallbacks.unregister(c); - } - - /** - * @return the cached capabilities status for this feature. - */ - @VisibleForTesting - public Capabilities queryCapabilityStatus() { - synchronized (mLock) { - return mCapabilityStatus.copy(); - } - } - - // Called internally to request the change of enabled capabilities. - @VisibleForTesting - public final void requestChangeEnabledCapabilities(CapabilityChangeRequest request, - IImsCapabilityCallback c) throws RemoteException { - if (request == null) { - throw new IllegalArgumentException( - "ImsFeature#requestChangeEnabledCapabilities called with invalid params."); - } - changeEnabledCapabilities(request, new CapabilityCallbackProxy(c)); - } - - /** - * Called by the ImsFeature when the capabilities status has changed. - * - * @param c A {@link Capabilities} containing the new Capabilities status. - */ - protected final void notifyCapabilitiesStatusChanged(Capabilities c) { - synchronized (mLock) { - mCapabilityStatus = c.copy(); - } - int count = mCapabilityCallbacks.beginBroadcast(); - try { - for (int i = 0; i < count; i++) { - try { - mCapabilityCallbacks.getBroadcastItem(i).onCapabilitiesStatusChanged( - c.mCapabilities); - } catch (RemoteException e) { - Log.w(LOG_TAG, e + " " + "notifyCapabilitiesStatusChanged() - Skipping " + - "callback."); - } - } - } finally { - mCapabilityCallbacks.finishBroadcast(); - } - } - - /** - * Features should override this method to receive Capability preference change requests from - * the framework using the provided {@link CapabilityChangeRequest}. If any of the capabilities - * in the {@link CapabilityChangeRequest} are not able to be completed due to an error, - * {@link CapabilityCallbackProxy#onChangeCapabilityConfigurationError} should be called for - * each failed capability. - * - * @param request A {@link CapabilityChangeRequest} containing requested capabilities to - * enable/disable. - * @param c A {@link CapabilityCallbackProxy}, which will be used to call back to the framework - * setting a subset of these capabilities fail, using - * {@link CapabilityCallbackProxy#onChangeCapabilityConfigurationError}. - */ - public abstract void changeEnabledCapabilities(CapabilityChangeRequest request, - CapabilityCallbackProxy c); - - /** - * Called when the framework is removing this feature and it needs to be cleaned up. - */ - public abstract void onFeatureRemoved(); - - /** - * Called when the feature has been initialized and communication with the framework is set up. - * Any attempt by this feature to access the framework before this method is called will return - * with an {@link IllegalStateException}. - * The IMS provider should use this method to trigger registration for this feature on the IMS - * network, if needed. - */ - public abstract void onFeatureReady(); - - /** - * @return Binder instance that the framework will use to communicate with this feature. - */ - protected abstract IInterface getBinder(); -} diff --git a/telephony/java/android/telephony/ims/internal/stub/ImsConfigImplBase.java b/telephony/java/android/telephony/ims/internal/stub/ImsConfigImplBase.java deleted file mode 100644 index 33aec5dfb8af..000000000000 --- a/telephony/java/android/telephony/ims/internal/stub/ImsConfigImplBase.java +++ /dev/null @@ -1,173 +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.telephony.ims.internal.stub; - -import android.os.RemoteCallbackList; -import android.os.RemoteException; -import android.telephony.ims.internal.aidl.IImsConfig; -import android.telephony.ims.internal.aidl.IImsConfigCallback; - -import com.android.ims.ImsConfig; - -/** - * Controls the modification of IMS specific configurations. For more information on the supported - * IMS configuration constants, see {@link ImsConfig}. - * - * @hide - */ - -public class ImsConfigImplBase { - - //TODO: Implement the Binder logic to call base APIs. Need to finish other ImsService Config - // work first. - private final IImsConfig mBinder = new IImsConfig.Stub() { - - @Override - public void addImsConfigCallback(IImsConfigCallback c) throws RemoteException { - ImsConfigImplBase.this.addImsConfigCallback(c); - } - - @Override - public void removeImsConfigCallback(IImsConfigCallback c) throws RemoteException { - ImsConfigImplBase.this.removeImsConfigCallback(c); - } - - @Override - public int getConfigInt(int item) throws RemoteException { - return Integer.MIN_VALUE; - } - - @Override - public String getConfigString(int item) throws RemoteException { - return null; - } - - @Override - public int setConfigInt(int item, int value) throws RemoteException { - return Integer.MIN_VALUE; - } - - @Override - public int setConfigString(int item, String value) throws RemoteException { - return Integer.MIN_VALUE; - } - }; - - public class Callback extends IImsConfigCallback.Stub { - - @Override - public final void onIntConfigChanged(int item, int value) throws RemoteException { - onConfigChanged(item, value); - } - - @Override - public final void onStringConfigChanged(int item, String value) throws RemoteException { - onConfigChanged(item, value); - } - - /** - * Called when the IMS configuration has changed. - * @param item the IMS configuration key constant, as defined in ImsConfig. - * @param value the new integer value of the IMS configuration constant. - */ - public void onConfigChanged(int item, int value) { - // Base Implementation - } - - /** - * Called when the IMS configuration has changed. - * @param item the IMS configuration key constant, as defined in ImsConfig. - * @param value the new String value of the IMS configuration constant. - */ - public void onConfigChanged(int item, String value) { - // Base Implementation - } - } - - private final RemoteCallbackList<IImsConfigCallback> mCallbacks = new RemoteCallbackList<>(); - - /** - * Adds a {@link Callback} to the list of callbacks notified when a value in the configuration - * changes. - * @param c callback to add. - */ - private void addImsConfigCallback(IImsConfigCallback c) { - mCallbacks.register(c); - } - /** - * Removes a {@link Callback} to the list of callbacks notified when a value in the - * configuration changes. - * - * @param c callback to remove. - */ - private void removeImsConfigCallback(IImsConfigCallback c) { - mCallbacks.unregister(c); - } - - public final IImsConfig getBinder() { - return mBinder; - } - - /** - * Sets the value for IMS service/capabilities parameters by the operator device - * management entity. It sets the config item value in the provisioned storage - * from which the master value is derived. - * - * @param item as defined in com.android.ims.ImsConfig#ConfigConstants. - * @param value in Integer format. - * @return as defined in com.android.ims.ImsConfig#OperationStatusConstants. - */ - public int setConfig(int item, int value) { - // Base Implementation - To be overridden. - return ImsConfig.OperationStatusConstants.FAILED; - } - - /** - * Sets the value for IMS service/capabilities parameters by the operator device - * management entity. It sets the config item value in the provisioned storage - * from which the master value is derived. - * - * @param item as defined in com.android.ims.ImsConfig#ConfigConstants. - * @param value in String format. - * @return as defined in com.android.ims.ImsConfig#OperationStatusConstants. - */ - public int setConfig(int item, String value) { - return ImsConfig.OperationStatusConstants.FAILED; - } - - /** - * Gets the value for ims service/capabilities parameters from the provisioned - * value storage. - * - * @param item as defined in com.android.ims.ImsConfig#ConfigConstants. - * @return value in Integer format. - */ - public int getConfigInt(int item) { - return ImsConfig.OperationStatusConstants.FAILED; - } - - /** - * Gets the value for ims service/capabilities parameters from the provisioned - * value storage. - * - * @param item as defined in com.android.ims.ImsConfig#ConfigConstants. - * @return value in String format. - */ - public String getConfigString(int item) { - return null; - } -} diff --git a/telephony/java/android/telephony/ims/stub/ImsCallSessionImplBase.java b/telephony/java/android/telephony/ims/stub/ImsCallSessionImplBase.java index 80b2f789e340..c6ca6fdb0fd8 100644 --- a/telephony/java/android/telephony/ims/stub/ImsCallSessionImplBase.java +++ b/telephony/java/android/telephony/ims/stub/ImsCallSessionImplBase.java @@ -16,131 +16,347 @@ package android.telephony.ims.stub; +import android.annotation.SystemApi; import android.os.Message; import android.os.RemoteException; +import android.telephony.ims.ImsCallSessionListener; +import android.telephony.ims.aidl.IImsCallSessionListener; -import com.android.ims.ImsCallProfile; -import com.android.ims.ImsStreamMediaProfile; -import com.android.ims.internal.ImsCallSession; +import android.telephony.ims.ImsCallProfile; +import android.telephony.ims.ImsReasonInfo; +import android.telephony.ims.ImsStreamMediaProfile; +import android.telephony.ims.ImsCallSession; import com.android.ims.internal.IImsCallSession; -import com.android.ims.internal.IImsCallSessionListener; import com.android.ims.internal.IImsVideoCallProvider; +import android.telephony.ims.ImsVideoCallProvider; + +import dalvik.system.CloseGuard; /** - * Base implementation of IImsCallSession, which implements stub versions of the methods in the - * IImsCallSession AIDL. Override the methods that your implementation of ImsCallSession supports. + * Base implementation of IImsCallSession, which implements stub versions of the methods available. * - * DO NOT remove or change the existing APIs, only add new ones to this Base implementation or you - * will break other implementations of ImsCallSession maintained by other ImsServices. + * Override the methods that your implementation of ImsCallSession supports. * * @hide */ - -public class ImsCallSessionImplBase extends IImsCallSession.Stub { +@SystemApi +// DO NOT remove or change the existing APIs, only add new ones to this Base implementation or you +// will break other implementations of ImsCallSession maintained by other ImsServices. +public class ImsCallSessionImplBase implements AutoCloseable { + /** + * Notify USSD Mode. + */ + public static final int USSD_MODE_NOTIFY = 0; + /** + * Request USSD Mode + */ + public static final int USSD_MODE_REQUEST = 1; /** - * Closes the object. This object is not usable after being closed. + * Defines IMS call session state. */ - @Override - public void close() throws RemoteException { + public static class State { + public static final int IDLE = 0; + public static final int INITIATED = 1; + public static final int NEGOTIATING = 2; + public static final int ESTABLISHING = 3; + public static final int ESTABLISHED = 4; + + public static final int RENEGOTIATING = 5; + public static final int REESTABLISHING = 6; + + public static final int TERMINATING = 7; + public static final int TERMINATED = 8; + + public static final int INVALID = (-1); + + /** + * Converts the state to string. + */ + public static String toString(int state) { + switch (state) { + case IDLE: + return "IDLE"; + case INITIATED: + return "INITIATED"; + case NEGOTIATING: + return "NEGOTIATING"; + case ESTABLISHING: + return "ESTABLISHING"; + case ESTABLISHED: + return "ESTABLISHED"; + case RENEGOTIATING: + return "RENEGOTIATING"; + case REESTABLISHING: + return "REESTABLISHING"; + case TERMINATING: + return "TERMINATING"; + case TERMINATED: + return "TERMINATED"; + default: + return "UNKNOWN"; + } + } + + /** + * @hide + */ + private State() { + } + } + + // Non-final for injection by tests + private IImsCallSession mServiceImpl = new IImsCallSession.Stub() { + @Override + public void close() { + ImsCallSessionImplBase.this.close(); + } + + @Override + public String getCallId() { + return ImsCallSessionImplBase.this.getCallId(); + } + + @Override + public ImsCallProfile getCallProfile() { + return ImsCallSessionImplBase.this.getCallProfile(); + } + + @Override + public ImsCallProfile getLocalCallProfile() { + return ImsCallSessionImplBase.this.getLocalCallProfile(); + } + + @Override + public ImsCallProfile getRemoteCallProfile() { + return ImsCallSessionImplBase.this.getRemoteCallProfile(); + } + + @Override + public String getProperty(String name) { + return ImsCallSessionImplBase.this.getProperty(name); + } + + @Override + public int getState() { + return ImsCallSessionImplBase.this.getState(); + } + + @Override + public boolean isInCall() { + return ImsCallSessionImplBase.this.isInCall(); + } + + @Override + public void setListener(IImsCallSessionListener listener) { + ImsCallSessionImplBase.this.setListener(new ImsCallSessionListener(listener)); + } + + @Override + public void setMute(boolean muted) { + ImsCallSessionImplBase.this.setMute(muted); + } + + @Override + public void start(String callee, ImsCallProfile profile) { + ImsCallSessionImplBase.this.start(callee, profile); + } + + @Override + public void startConference(String[] participants, ImsCallProfile profile) throws + RemoteException { + ImsCallSessionImplBase.this.startConference(participants, profile); + } + + @Override + public void accept(int callType, ImsStreamMediaProfile profile) { + ImsCallSessionImplBase.this.accept(callType, profile); + } + + @Override + public void reject(int reason) { + ImsCallSessionImplBase.this.reject(reason); + } + + @Override + public void terminate(int reason) { + ImsCallSessionImplBase.this.terminate(reason); + } + + @Override + public void hold(ImsStreamMediaProfile profile) { + ImsCallSessionImplBase.this.hold(profile); + } + + @Override + public void resume(ImsStreamMediaProfile profile) { + ImsCallSessionImplBase.this.resume(profile); + } + + @Override + public void merge() { + ImsCallSessionImplBase.this.merge(); + } + + @Override + public void update(int callType, ImsStreamMediaProfile profile) { + ImsCallSessionImplBase.this.update(callType, profile); + } + + @Override + public void extendToConference(String[] participants) { + ImsCallSessionImplBase.this.extendToConference(participants); + } + + @Override + public void inviteParticipants(String[] participants) { + ImsCallSessionImplBase.this.inviteParticipants(participants); + } + + @Override + public void removeParticipants(String[] participants) { + ImsCallSessionImplBase.this.removeParticipants(participants); + } + + @Override + public void sendDtmf(char c, Message result) { + ImsCallSessionImplBase.this.sendDtmf(c, result); + } + + @Override + public void startDtmf(char c) { + ImsCallSessionImplBase.this.startDtmf(c); + } + + @Override + public void stopDtmf() { + ImsCallSessionImplBase.this.stopDtmf(); + } + + @Override + public void sendUssd(String ussdMessage) { + ImsCallSessionImplBase.this.sendUssd(ussdMessage); + } + + @Override + public IImsVideoCallProvider getVideoCallProvider() { + return ImsCallSessionImplBase.this.getVideoCallProvider(); + } + + @Override + public boolean isMultiparty() { + return ImsCallSessionImplBase.this.isMultiparty(); + } + + @Override + public void sendRttModifyRequest(ImsCallProfile toProfile) { + ImsCallSessionImplBase.this.sendRttModifyRequest(toProfile); + } + + @Override + public void sendRttModifyResponse(boolean status) { + ImsCallSessionImplBase.this.sendRttModifyResponse(status); + } + + @Override + public void sendRttMessage(String rttMessage) { + ImsCallSessionImplBase.this.sendRttMessage(rttMessage); + } + }; + /** + * @hide + */ + public final void setListener(IImsCallSessionListener listener) throws RemoteException { + setListener(new ImsCallSessionListener(listener)); } /** - * Gets the call ID of the session. + * Sets the listener to listen to the session events. An {@link ImsCallSession} + * can only hold one listener at a time. Subsequent calls to this method + * override the previous listener. * - * @return the call ID + * @param listener {@link ImsCallSessionListener} used to notify the framework of updates + * to the ImsCallSession */ - @Override - public String getCallId() throws RemoteException { - return null; + public void setListener(ImsCallSessionListener listener) { } /** - * Gets the call profile that this session is associated with - * - * @return the {@link ImsCallProfile} that this session is associated with + * Closes the object. This {@link ImsCallSessionImplBase} is not usable after being closed. */ @Override - public ImsCallProfile getCallProfile() throws RemoteException { + public void close() { + + } + + /** + * @return A String containing the unique call ID of this {@link ImsCallSessionImplBase}. + */ + public String getCallId() { return null; } /** - * Gets the local call profile that this session is associated with - * - * @return the local {@link ImsCallProfile} that this session is associated with + * @return The {@link ImsCallProfile} that this {@link ImsCallSessionImplBase} is associated + * with. */ - @Override - public ImsCallProfile getLocalCallProfile() throws RemoteException { + public ImsCallProfile getCallProfile() { return null; } /** - * Gets the remote call profile that this session is associated with - * - * @return the remote {@link ImsCallProfile} that this session is associated with + * @return The local {@link ImsCallProfile} that this {@link ImsCallSessionImplBase} is + * associated with. */ - @Override - public ImsCallProfile getRemoteCallProfile() throws RemoteException { + public ImsCallProfile getLocalCallProfile() { return null; } /** - * Gets the value associated with the specified property of this session. - * - * @return the string value associated with the specified property + * @return The remote {@link ImsCallProfile} that this {@link ImsCallSessionImplBase} is + * associated with. */ - @Override - public String getProperty(String name) throws RemoteException { + public ImsCallProfile getRemoteCallProfile() { return null; } /** - * Gets the session state. - * The value returned must be one of the states in {@link ImsCallSession.State}. - * - * @return the session state + * @param name The String extra key. + * @return The string extra value associated with the specified property. */ - @Override - public int getState() throws RemoteException { - return ImsCallSession.State.INVALID; + public String getProperty(String name) { + return null; } /** - * Checks if the session is in call. - * - * @return true if the session is in call, false otherwise + * @return The {@link ImsCallSessionImplBase} state, defined in + * {@link ImsCallSessionImplBase.State}. */ - @Override - public boolean isInCall() throws RemoteException { - return false; + public int getState() { + return ImsCallSessionImplBase.State.INVALID; } /** - * Sets the listener to listen to the session events. An {@link ImsCallSession} - * can only hold one listener at a time. Subsequent calls to this method - * override the previous listener. - * - * @param listener to listen to the session events of this object + * @return true if the {@link ImsCallSessionImplBase} is in a call, false otherwise. */ - @Override - public void setListener(IImsCallSessionListener listener) throws RemoteException { + public boolean isInCall() { + return false; } /** * Mutes or unmutes the mic for the active call. * - * @param muted true if the call is muted, false otherwise + * @param muted true if the call should be muted, false otherwise. */ - @Override - public void setMute(boolean muted) throws RemoteException { + public void setMute(boolean muted) { } /** - * Initiates an IMS call with the specified target and call profile. - * The session listener set in {@link #setListener} is called back upon defined session events. - * The method is only valid to call when the session state is in + * Initiates an IMS call with the specified number and call profile. + * The session listener set in {@link #setListener(ImsCallSessionListener)} is called back upon + * defined session events. + * Only valid to call when the session state is in * {@link ImsCallSession.State#IDLE}. * * @param callee dialed string to make the call to @@ -149,13 +365,13 @@ public class ImsCallSessionImplBase extends IImsCallSession.Stub { * @see {@link ImsCallSession.Listener#callSessionStarted}, * {@link ImsCallSession.Listener#callSessionStartFailed} */ - @Override - public void start(String callee, ImsCallProfile profile) throws RemoteException { + public void start(String callee, ImsCallProfile profile) { } /** * Initiates an IMS call with the specified participants and call profile. - * The session listener set in {@link #setListener} is called back upon defined session events. + * The session listener set in {@link #setListener(ImsCallSessionListener)} is called back upon + * defined session events. * The method is only valid to call when the session state is in * {@link ImsCallSession.State#IDLE}. * @@ -165,9 +381,7 @@ public class ImsCallSessionImplBase extends IImsCallSession.Stub { * @see {@link ImsCallSession.Listener#callSessionStarted}, * {@link ImsCallSession.Listener#callSessionStartFailed} */ - @Override - public void startConference(String[] participants, ImsCallProfile profile) - throws RemoteException { + public void startConference(String[] participants, ImsCallProfile profile) { } /** @@ -177,31 +391,26 @@ public class ImsCallSessionImplBase extends IImsCallSession.Stub { * @param profile stream media profile {@link ImsStreamMediaProfile} to be answered * @see {@link ImsCallSession.Listener#callSessionStarted} */ - @Override - public void accept(int callType, ImsStreamMediaProfile profile) throws RemoteException { + public void accept(int callType, ImsStreamMediaProfile profile) { } /** * Rejects an incoming call or session update. * - * @param reason reason code to reject an incoming call, defined in - * com.android.ims.ImsReasonInfo + * @param reason reason code to reject an incoming call, defined in {@link ImsReasonInfo}. * {@link ImsCallSession.Listener#callSessionStartFailed} */ - @Override - public void reject(int reason) throws RemoteException { + public void reject(int reason) { } /** * Terminates a call. * - * @param reason reason code to terminate a call, defined in - * com.android.ims.ImsReasonInfo + * @param reason reason code to terminate a call, defined in {@link ImsReasonInfo}. * * @see {@link ImsCallSession.Listener#callSessionTerminated} */ - @Override - public void terminate(int reason) throws RemoteException { + public void terminate(int reason) { } /** @@ -212,8 +421,7 @@ public class ImsCallSessionImplBase extends IImsCallSession.Stub { * @see {@link ImsCallSession.Listener#callSessionHeld}, * {@link ImsCallSession.Listener#callSessionHoldFailed} */ - @Override - public void hold(ImsStreamMediaProfile profile) throws RemoteException { + public void hold(ImsStreamMediaProfile profile) { } /** @@ -224,12 +432,11 @@ public class ImsCallSessionImplBase extends IImsCallSession.Stub { * @see {@link ImsCallSession.Listener#callSessionResumed}, * {@link ImsCallSession.Listener#callSessionResumeFailed} */ - @Override - public void resume(ImsStreamMediaProfile profile) throws RemoteException { + public void resume(ImsStreamMediaProfile profile) { } /** - * Merges the active & hold call. When the merge starts, + * Merges the active and held call. When the merge starts, * {@link ImsCallSession.Listener#callSessionMergeStarted} is called. * {@link ImsCallSession.Listener#callSessionMergeComplete} is called if the merge is * successful, and {@link ImsCallSession.Listener#callSessionMergeFailed} is called if the merge @@ -239,8 +446,7 @@ public class ImsCallSessionImplBase extends IImsCallSession.Stub { * {@link ImsCallSession.Listener#callSessionMergeComplete}, * {@link ImsCallSession.Listener#callSessionMergeFailed} */ - @Override - public void merge() throws RemoteException { + public void merge() { } /** @@ -251,8 +457,7 @@ public class ImsCallSessionImplBase extends IImsCallSession.Stub { * @see {@link ImsCallSession.Listener#callSessionUpdated}, * {@link ImsCallSession.Listener#callSessionUpdateFailed} */ - @Override - public void update(int callType, ImsStreamMediaProfile profile) throws RemoteException { + public void update(int callType, ImsStreamMediaProfile profile) { } /** @@ -263,8 +468,7 @@ public class ImsCallSessionImplBase extends IImsCallSession.Stub { * @see {@link ImsCallSession.Listener#callSessionConferenceExtended}, * {@link ImsCallSession.Listener#callSessionConferenceExtendFailed} */ - @Override - public void extendToConference(String[] participants) throws RemoteException { + public void extendToConference(String[] participants) { } /** @@ -274,8 +478,7 @@ public class ImsCallSessionImplBase extends IImsCallSession.Stub { * @see {@link ImsCallSession.Listener#callSessionInviteParticipantsRequestDelivered}, * {@link ImsCallSession.Listener#callSessionInviteParticipantsRequestFailed} */ - @Override - public void inviteParticipants(String[] participants) throws RemoteException { + public void inviteParticipants(String[] participants) { } /** @@ -285,8 +488,7 @@ public class ImsCallSessionImplBase extends IImsCallSession.Stub { * @see {@link ImsCallSession.Listener#callSessionRemoveParticipantsRequestDelivered}, * {@link ImsCallSession.Listener#callSessionRemoveParticipantsRequestFailed} */ - @Override - public void removeParticipants(String[] participants) throws RemoteException { + public void removeParticipants(String[] participants) { } /** @@ -296,8 +498,7 @@ public class ImsCallSessionImplBase extends IImsCallSession.Stub { * * @param c the DTMF to send. '0' ~ '9', 'A' ~ 'D', '*', '#' are valid inputs. */ - @Override - public void sendDtmf(char c, Message result) throws RemoteException { + public void sendDtmf(char c, Message result) { } /** @@ -307,15 +508,13 @@ public class ImsCallSessionImplBase extends IImsCallSession.Stub { * * @param c the DTMF to send. '0' ~ '9', 'A' ~ 'D', '*', '#' are valid inputs. */ - @Override - public void startDtmf(char c) throws RemoteException { + public void startDtmf(char c) { } /** * Stop a DTMF code. */ - @Override - public void stopDtmf() throws RemoteException { + public void stopDtmf() { } /** @@ -323,17 +522,23 @@ public class ImsCallSessionImplBase extends IImsCallSession.Stub { * * @param ussdMessage USSD message to send */ - @Override - public void sendUssd(String ussdMessage) throws RemoteException { + public void sendUssd(String ussdMessage) { } /** - * Returns a binder for the video call provider implementation contained within the IMS service - * process. This binder is used by the VideoCallProvider subclass in Telephony which - * intermediates between the propriety implementation and Telecomm/InCall. + * See {@link #getImsVideoCallProvider()}, used directly in older ImsService implementations. + * @hide */ - @Override - public IImsVideoCallProvider getVideoCallProvider() throws RemoteException { + public IImsVideoCallProvider getVideoCallProvider() { + ImsVideoCallProvider provider = getImsVideoCallProvider(); + return provider != null ? provider.getInterface() : null; + } + + /** + * @return The {@link ImsVideoCallProvider} implementation contained within the IMS service + * process. + */ + public ImsVideoCallProvider getImsVideoCallProvider() { return null; } @@ -341,8 +546,7 @@ public class ImsCallSessionImplBase extends IImsCallSession.Stub { * Determines if the current session is multiparty. * @return {@code True} if the session is multiparty. */ - @Override - public boolean isMultiparty() throws RemoteException { + public boolean isMultiparty() { return false; } @@ -350,16 +554,13 @@ public class ImsCallSessionImplBase extends IImsCallSession.Stub { * Device issues RTT modify request * @param toProfile The profile with requested changes made */ - @Override public void sendRttModifyRequest(ImsCallProfile toProfile) { } /** * Device responds to Remote RTT modify request - * @param status true Accepted the request - * false Declined the request + * @param status true if the the request was accepted or false of the request is defined. */ - @Override public void sendRttModifyResponse(boolean status) { } @@ -367,7 +568,16 @@ public class ImsCallSessionImplBase extends IImsCallSession.Stub { * Device sends RTT message * @param rttMessage RTT message to be sent */ - @Override public void sendRttMessage(String rttMessage) { } + + /** @hide */ + public IImsCallSession getServiceImpl() { + return mServiceImpl; + } + + /** @hide */ + public void setServiceImpl(IImsCallSession serviceImpl) { + mServiceImpl = serviceImpl; + } } diff --git a/telephony/java/android/telephony/ims/stub/ImsCallSessionListenerImplBase.java b/telephony/java/android/telephony/ims/stub/ImsCallSessionListenerImplBase.java deleted file mode 100644 index 6c18935d16ef..000000000000 --- a/telephony/java/android/telephony/ims/stub/ImsCallSessionListenerImplBase.java +++ /dev/null @@ -1,298 +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.telephony.ims.stub; - -import com.android.ims.ImsCallProfile; -import com.android.ims.ImsConferenceState; -import com.android.ims.ImsReasonInfo; -import com.android.ims.ImsStreamMediaProfile; -import com.android.ims.ImsSuppServiceNotification; -import com.android.ims.internal.IImsCallSession; -import com.android.ims.internal.IImsCallSessionListener; - -/** - * Base implementation of ImsCallSessionListenerBase, which implements stub versions of the methods - * in the IImsCallSessionListener AIDL. Override the methods that your implementation of - * ImsCallSessionListener supports. - * - * DO NOT remove or change the existing APIs, only add new ones to this Base implementation or you - * will break other implementations of ImsCallSessionListener maintained by other ImsServices. - * - * @hide - */ -public class ImsCallSessionListenerImplBase extends IImsCallSessionListener.Stub { - /** - * Notifies the result of the basic session operation (setup / terminate). - */ - @Override - public void callSessionProgressing(IImsCallSession session, ImsStreamMediaProfile profile) { - // no-op - } - - @Override - public void callSessionStarted(IImsCallSession session, ImsCallProfile profile) { - // no-op - } - - @Override - public void callSessionStartFailed(IImsCallSession session, ImsReasonInfo reasonInfo) { - // no-op - } - - @Override - public void callSessionTerminated(IImsCallSession session, ImsReasonInfo reasonInfo) { - // no-op - } - - /** - * Notifies the result of the call hold/resume operation. - */ - @Override - public void callSessionHeld(IImsCallSession session, ImsCallProfile profile) { - // no-op - } - - @Override - public void callSessionHoldFailed(IImsCallSession session, ImsReasonInfo reasonInfo) { - // no-op - } - - @Override - public void callSessionHoldReceived(IImsCallSession session, ImsCallProfile profile) { - // no-op - } - - @Override - public void callSessionResumed(IImsCallSession session, ImsCallProfile profile) { - // no-op - } - - @Override - public void callSessionResumeFailed(IImsCallSession session, ImsReasonInfo reasonInfo) { - // no-op - } - - @Override - public void callSessionResumeReceived(IImsCallSession session, ImsCallProfile profile) { - // no-op - } - - /** - * Notifies the result of call merge operation. - */ - @Override - public void callSessionMergeStarted(IImsCallSession session, IImsCallSession newSession, - ImsCallProfile profile) { - // no-op - } - - @Override - public void callSessionMergeComplete(IImsCallSession session) { - // no-op - } - - @Override - public void callSessionMergeFailed(IImsCallSession session, ImsReasonInfo reasonInfo) { - // no-op - } - - /** - * Notifies the result of call upgrade / downgrade or any other call - * updates. - */ - @Override - public void callSessionUpdated(IImsCallSession session, ImsCallProfile profile) { - // no-op - } - - @Override - public void callSessionUpdateFailed(IImsCallSession session, ImsReasonInfo reasonInfo) { - // no-op - } - - @Override - public void callSessionUpdateReceived(IImsCallSession session, ImsCallProfile profile) { - // no-op - } - - /** - * Notifies the result of conference extension. - */ - @Override - public void callSessionConferenceExtended(IImsCallSession session, IImsCallSession newSession, - ImsCallProfile profile) { - // no-op - } - - @Override - public void callSessionConferenceExtendFailed(IImsCallSession session, - ImsReasonInfo reasonInfo) { - // no-op - } - - @Override - public void callSessionConferenceExtendReceived(IImsCallSession session, - IImsCallSession newSession, - ImsCallProfile profile) { - // no-op - } - - /** - * Notifies the result of the participant invitation / removal to/from the - * conference session. - */ - @Override - public void callSessionInviteParticipantsRequestDelivered(IImsCallSession session) { - // no-op - } - - @Override - public void callSessionInviteParticipantsRequestFailed(IImsCallSession session, - ImsReasonInfo reasonInfo) { - // no-op - } - - @Override - public void callSessionRemoveParticipantsRequestDelivered(IImsCallSession session) { - // no-op - } - - @Override - public void callSessionRemoveParticipantsRequestFailed(IImsCallSession session, - ImsReasonInfo reasonInfo) { - // no-op - } - - /** - * Notifies the changes of the conference info. the conference session. - */ - @Override - public void callSessionConferenceStateUpdated(IImsCallSession session, - ImsConferenceState state) { - // no-op - } - - /** - * Notifies the incoming USSD message. - */ - @Override - public void callSessionUssdMessageReceived(IImsCallSession session, int mode, - String ussdMessage) { - // no-op - } - - /** - * Notifies of a case where a {@link com.android.ims.internal.ImsCallSession} may potentially - * handover from one radio technology to another. - * @param session - * @param srcAccessTech The source radio access technology; one of the access technology - * constants defined in {@link android.telephony.ServiceState}. For - * example {@link android.telephony.ServiceState#RIL_RADIO_TECHNOLOGY_LTE}. - * @param targetAccessTech The target radio access technology; one of the access technology - * constants defined in {@link android.telephony.ServiceState}. For - * example {@link android.telephony.ServiceState#RIL_RADIO_TECHNOLOGY_LTE}. - */ - @Override - public void callSessionMayHandover(IImsCallSession session, int srcAccessTech, - int targetAccessTech) { - // no-op - } - - /** - * Notifies of handover information for this call - */ - @Override - public void callSessionHandover(IImsCallSession session, int srcAccessTech, - int targetAccessTech, - ImsReasonInfo reasonInfo) { - // no-op - } - - @Override - public void callSessionHandoverFailed(IImsCallSession session, int srcAccessTech, - int targetAccessTech, - ImsReasonInfo reasonInfo) { - // no-op - } - - /** - * Notifies the TTY mode change by remote party. - * - * @param mode one of the following: - - * {@link com.android.internal.telephony.Phone#TTY_MODE_OFF} - - * {@link com.android.internal.telephony.Phone#TTY_MODE_FULL} - - * {@link com.android.internal.telephony.Phone#TTY_MODE_HCO} - - * {@link com.android.internal.telephony.Phone#TTY_MODE_VCO} - */ - @Override - public void callSessionTtyModeReceived(IImsCallSession session, int mode) { - // no-op - } - - /** - * Notifies of a change to the multiparty state for this - * {@code ImsCallSession}. - * - * @param session The call session. - * @param isMultiParty {@code true} if the session became multiparty, - * {@code false} otherwise. - */ - @Override - public void callSessionMultipartyStateChanged(IImsCallSession session, boolean isMultiParty) { - // no-op - } - - /** - * Notifies the supplementary service information for the current session. - */ - @Override - public void callSessionSuppServiceReceived(IImsCallSession session, - ImsSuppServiceNotification suppSrvNotification) { - // no-op - } - - /** - * Received RTT modify request from Remote Party - * @param session The call session. - * @param callProfile ImsCallProfile with updated attribute - */ - @Override - public void callSessionRttModifyRequestReceived(IImsCallSession session, - ImsCallProfile callProfile) { - // no-op - } - - /** - * Received response for RTT modify request - * @param status true : Accepted the request - * false : Declined the request - */ - @Override - public void callSessionRttModifyResponseReceived(int status) { - // no -op - } - - /** - * Device received RTT message from Remote UE - * @param rttMessage RTT message received - */ - @Override - public void callSessionRttMessageReceived(String rttMessage) { - // no-op - } -} - diff --git a/telephony/java/android/telephony/ims/stub/ImsConfigImplBase.java b/telephony/java/android/telephony/ims/stub/ImsConfigImplBase.java index 1670e6b9ba5c..dcd7ea714f8c 100644 --- a/telephony/java/android/telephony/ims/stub/ImsConfigImplBase.java +++ b/telephony/java/android/telephony/ims/stub/ImsConfigImplBase.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2017 The Android Open Source Project + * Copyright (C) 2018 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,31 +16,24 @@ package android.telephony.ims.stub; +import android.annotation.SystemApi; import android.content.Context; import android.content.Intent; +import android.os.RemoteCallbackList; import android.os.RemoteException; +import android.telephony.ims.aidl.IImsConfig; +import android.telephony.ims.aidl.IImsConfigCallback; import android.util.Log; import com.android.ims.ImsConfig; -import com.android.ims.ImsConfigListener; -import com.android.ims.internal.IImsConfig; import com.android.internal.annotations.VisibleForTesting; import java.lang.ref.WeakReference; import java.util.HashMap; - /** - * Base implementation of ImsConfig. - * Override the methods that your implementation of ImsConfig supports. - * - * DO NOT remove or change the existing APIs, only add new ones to this Base implementation or you - * will break other implementations of ImsConfig maintained by other ImsServices. - * - * Provides APIs to get/set the IMS service feature/capability/parameters. - * The config items include: - * 1) Items provisioned by the operator. - * 2) Items configured by user. Mainly service feature class. + * Controls the modification of IMS specific configurations. For more information on the supported + * IMS configuration constants, see {@link ImsConfig}. * * The inner class {@link ImsConfigStub} implements methods of IImsConfig AIDL interface. * The IImsConfig AIDL interface is called by ImsConfig, which may exist in many other processes. @@ -51,142 +44,10 @@ import java.util.HashMap; * performed every time. * @hide */ - +@SystemApi public class ImsConfigImplBase { - static final private String TAG = "ImsConfigImplBase"; - - ImsConfigStub mImsConfigStub; - - public ImsConfigImplBase(Context context) { - mImsConfigStub = new ImsConfigStub(this, context); - } - - /** - * Gets the value for ims service/capabilities parameters from the provisioned - * value storage. Synchronous blocking call. - * - * @param item, as defined in com.android.ims.ImsConfig#ConfigConstants. - * @return value in Integer format. - */ - public int getProvisionedValue(int item) throws RemoteException { - return -1; - } - - /** - * Gets the value for ims service/capabilities parameters from the provisioned - * value storage. Synchronous blocking call. - * - * @param item, as defined in com.android.ims.ImsConfig#ConfigConstants. - * @return value in String format. - */ - public String getProvisionedStringValue(int item) throws RemoteException { - return null; - } - - /** - * Sets the value for IMS service/capabilities parameters by the operator device - * management entity. It sets the config item value in the provisioned storage - * from which the master value is derived. Synchronous blocking call. - * - * @param item, as defined in com.android.ims.ImsConfig#ConfigConstants. - * @param value in Integer format. - * @return as defined in com.android.ims.ImsConfig#OperationStatusConstants. - */ - public int setProvisionedValue(int item, int value) throws RemoteException { - return ImsConfig.OperationStatusConstants.FAILED; - } - - /** - * Sets the value for IMS service/capabilities parameters by the operator device - * management entity. It sets the config item value in the provisioned storage - * from which the master value is derived. Synchronous blocking call. - * - * @param item as defined in com.android.ims.ImsConfig#ConfigConstants. - * @param value in String format. - * @return as defined in com.android.ims.ImsConfig#OperationStatusConstants. - */ - public int setProvisionedStringValue(int item, String value) throws RemoteException { - return ImsConfig.OperationStatusConstants.FAILED; - } - - /** - * Gets the value of the specified IMS feature item for specified network type. - * This operation gets the feature config value from the master storage (i.e. final - * value). Asynchronous non-blocking call. - * - * @param feature as defined in com.android.ims.ImsConfig#FeatureConstants. - * @param network as defined in android.telephony.TelephonyManager#NETWORK_TYPE_XXX. - * @param listener feature value returned asynchronously through listener. - */ - public void getFeatureValue(int feature, int network, ImsConfigListener listener) - throws RemoteException { - } - - /** - * Sets the value for IMS feature item for specified network type. - * This operation stores the user setting in setting db from which master db - * is derived. - * - * @param feature as defined in com.android.ims.ImsConfig#FeatureConstants. - * @param network as defined in android.telephony.TelephonyManager#NETWORK_TYPE_XXX. - * @param value as defined in com.android.ims.ImsConfig#FeatureValueConstants. - * @param listener, provided if caller needs to be notified for set result. - */ - public void setFeatureValue(int feature, int network, int value, ImsConfigListener listener) - throws RemoteException { - } - - /** - * Gets the value for IMS VoLTE provisioned. - * This should be the same as the operator provisioned value if applies. - */ - public boolean getVolteProvisioned() throws RemoteException { - return false; - } - - /** - * Gets the value for IMS feature item video quality. - * - * @param listener Video quality value returned asynchronously through listener. - */ - public void getVideoQuality(ImsConfigListener listener) throws RemoteException { - } - - /** - * Sets the value for IMS feature item video quality. - * - * @param quality, defines the value of video quality. - * @param listener, provided if caller needs to be notified for set result. - */ - public void setVideoQuality(int quality, ImsConfigListener listener) throws RemoteException { - } - - public IImsConfig getIImsConfig() { return mImsConfigStub; } - - /** - * Updates provisioning value and notifies the framework of the change. - * Doesn't call #setProvisionedValue and assumes the result succeeded. - * This should only be used by modem when they implicitly changed provisioned values. - * - * @param item, as defined in com.android.ims.ImsConfig#ConfigConstants. - * @param value in Integer format. - */ - public final void notifyProvisionedValueChanged(int item, int value) { - mImsConfigStub.updateCachedValue(item, value, true); - } - - /** - * Updates provisioning value and notifies the framework of the change. - * Doesn't call #setProvisionedValue and assumes the result succeeded. - * This should only be used by modem when they implicitly changed provisioned values. - * - * @param item, as defined in com.android.ims.ImsConfig#ConfigConstants. - * @param value in String format. - */ - public final void notifyProvisionedValueChanged(int item, String value) { - mImsConfigStub.updateCachedValue(item, value, true); - } + private static final String TAG = "ImsConfigImplBase"; /** * Implements the IImsConfig AIDL interface, which is called by potentially many processes @@ -208,32 +69,41 @@ public class ImsConfigImplBase { */ @VisibleForTesting static public class ImsConfigStub extends IImsConfig.Stub { - Context mContext; WeakReference<ImsConfigImplBase> mImsConfigImplBaseWeakReference; private HashMap<Integer, Integer> mProvisionedIntValue = new HashMap<>(); private HashMap<Integer, String> mProvisionedStringValue = new HashMap<>(); @VisibleForTesting - public ImsConfigStub(ImsConfigImplBase imsConfigImplBase, Context context) { - mContext = context; + public ImsConfigStub(ImsConfigImplBase imsConfigImplBase) { mImsConfigImplBaseWeakReference = new WeakReference<ImsConfigImplBase>(imsConfigImplBase); } + @Override + public void addImsConfigCallback(IImsConfigCallback c) throws RemoteException { + getImsConfigImpl().addImsConfigCallback(c); + } + + @Override + public void removeImsConfigCallback(IImsConfigCallback c) throws RemoteException { + getImsConfigImpl().removeImsConfigCallback(c); + } + /** * Gets the value for ims service/capabilities parameters. It first checks its local cache, - * if missed, it will call ImsConfigImplBase.getProvisionedValue. + * if missed, it will call ImsConfigImplBase.getConfigInt. * Synchronous blocking call. * - * @param item, as defined in com.android.ims.ImsConfig#ConfigConstants. - * @return value in Integer format. + * @param item integer key + * @return value in Integer format or {@link #CONFIG_RESULT_UNKNOWN} if + * unavailable. */ @Override - public synchronized int getProvisionedValue(int item) throws RemoteException { + public synchronized int getConfigInt(int item) throws RemoteException { if (mProvisionedIntValue.containsKey(item)) { return mProvisionedIntValue.get(item); } else { - int retVal = getImsConfigImpl().getProvisionedValue(item); + int retVal = getImsConfigImpl().getConfigInt(item); if (retVal != ImsConfig.OperationStatusConstants.UNKNOWN) { updateCachedValue(item, retVal, false); } @@ -243,18 +113,18 @@ public class ImsConfigImplBase { /** * Gets the value for ims service/capabilities parameters. It first checks its local cache, - * if missed, it will call #ImsConfigImplBase.getProvisionedValue. + * if missed, it will call #ImsConfigImplBase.getConfigString. * Synchronous blocking call. * - * @param item, as defined in com.android.ims.ImsConfig#ConfigConstants. + * @param item integer key * @return value in String format. */ @Override - public synchronized String getProvisionedStringValue(int item) throws RemoteException { + public synchronized String getConfigString(int item) throws RemoteException { if (mProvisionedIntValue.containsKey(item)) { return mProvisionedStringValue.get(item); } else { - String retVal = getImsConfigImpl().getProvisionedStringValue(item); + String retVal = getImsConfigImpl().getConfigString(item); if (retVal != null) { updateCachedValue(item, retVal, false); } @@ -268,16 +138,17 @@ public class ImsConfigImplBase { * from which the master value is derived, and write it into local cache. * Synchronous blocking call. * - * @param item, as defined in com.android.ims.ImsConfig#ConfigConstants. + * @param item integer key * @param value in Integer format. - * @return as defined in com.android.ims.ImsConfig#OperationStatusConstants. + * @return the result of setting the configuration value, defined as either + * {@link #CONFIG_RESULT_FAILED} or {@link #CONFIG_RESULT_SUCCESS}. */ @Override - public synchronized int setProvisionedValue(int item, int value) throws RemoteException { + public synchronized int setConfigInt(int item, int value) throws RemoteException { mProvisionedIntValue.remove(item); - int retVal = getImsConfigImpl().setProvisionedValue(item, value); + int retVal = getImsConfigImpl().setConfig(item, value); if (retVal == ImsConfig.OperationStatusConstants.SUCCESS) { - updateCachedValue(item, retVal, true); + updateCachedValue(item, value, true); } else { Log.d(TAG, "Set provision value of " + item + " to " + value + " failed with error code " + retVal); @@ -294,98 +165,252 @@ public class ImsConfigImplBase { * * @param item as defined in com.android.ims.ImsConfig#ConfigConstants. * @param value in String format. - * @return as defined in com.android.ims.ImsConfig#OperationStatusConstants. + * @return the result of setting the configuration value, defined as either + * {@link #CONFIG_RESULT_FAILED} or {@link #CONFIG_RESULT_SUCCESS}. */ @Override - public synchronized int setProvisionedStringValue(int item, String value) + public synchronized int setConfigString(int item, String value) throws RemoteException { mProvisionedStringValue.remove(item); - int retVal = getImsConfigImpl().setProvisionedStringValue(item, value); + int retVal = getImsConfigImpl().setConfig(item, value); if (retVal == ImsConfig.OperationStatusConstants.SUCCESS) { - updateCachedValue(item, retVal, true); + updateCachedValue(item, value, true); } return retVal; } - /** - * Wrapper function to call ImsConfigImplBase.getFeatureValue. - */ - @Override - public void getFeatureValue(int feature, int network, ImsConfigListener listener) - throws RemoteException { - getImsConfigImpl().getFeatureValue(feature, network, listener); + private ImsConfigImplBase getImsConfigImpl() throws RemoteException { + ImsConfigImplBase ref = mImsConfigImplBaseWeakReference.get(); + if (ref == null) { + throw new RemoteException("Fail to get ImsConfigImpl"); + } else { + return ref; + } } - /** - * Wrapper function to call ImsConfigImplBase.setFeatureValue. - */ + private void notifyImsConfigChanged(int item, int value) throws RemoteException { + getImsConfigImpl().notifyConfigChanged(item, value); + } + + private void notifyImsConfigChanged(int item, String value) throws RemoteException { + getImsConfigImpl().notifyConfigChanged(item, value); + } + + protected synchronized void updateCachedValue(int item, int value, boolean notifyChange) + throws RemoteException { + mProvisionedIntValue.put(item, value); + if (notifyChange) { + notifyImsConfigChanged(item, value); + } + } + + protected synchronized void updateCachedValue(int item, String value, + boolean notifyChange) throws RemoteException { + mProvisionedStringValue.put(item, value); + if (notifyChange) { + notifyImsConfigChanged(item, value); + } + } + } + + /** + * Callback that the framework uses for receiving Configuration change updates. + * {@hide} + */ + public static class Callback extends IImsConfigCallback.Stub { + @Override - public void setFeatureValue(int feature, int network, int value, ImsConfigListener listener) - throws RemoteException { - getImsConfigImpl().setFeatureValue(feature, network, value, listener); + public final void onIntConfigChanged(int item, int value) throws RemoteException { + onConfigChanged(item, value); } - /** - * Wrapper function to call ImsConfigImplBase.getVolteProvisioned. - */ @Override - public boolean getVolteProvisioned() throws RemoteException { - return getImsConfigImpl().getVolteProvisioned(); + public final void onStringConfigChanged(int item, String value) throws RemoteException { + onConfigChanged(item, value); } /** - * Wrapper function to call ImsConfigImplBase.getVideoQuality. + * Called when the IMS configuration has changed. + * @param item the IMS configuration key constant, as defined in ImsConfig. + * @param value the new integer value of the IMS configuration constant. */ - @Override - public void getVideoQuality(ImsConfigListener listener) throws RemoteException { - getImsConfigImpl().getVideoQuality(listener); + public void onConfigChanged(int item, int value) { + // Base Implementation } /** - * Wrapper function to call ImsConfigImplBase.setVideoQuality. + * Called when the IMS configuration has changed. + * @param item the IMS configuration key constant, as defined in ImsConfig. + * @param value the new String value of the IMS configuration constant. */ - @Override - public void setVideoQuality(int quality, ImsConfigListener listener) - throws RemoteException { - getImsConfigImpl().setVideoQuality(quality, listener); + public void onConfigChanged(int item, String value) { + // Base Implementation } + } - private ImsConfigImplBase getImsConfigImpl() throws RemoteException { - ImsConfigImplBase ref = mImsConfigImplBaseWeakReference.get(); - if (ref == null) { - throw new RemoteException("Fail to get ImsConfigImpl"); - } else { - return ref; - } - } + /** + * The configuration requested resulted in an unknown result. This may happen if the + * IMS configurations are unavailable. + */ + public static final int CONFIG_RESULT_UNKNOWN = -1; + /** + * Setting the configuration value completed. + */ + public static final int CONFIG_RESULT_SUCCESS = 0; + /** + * Setting the configuration value failed. + */ + public static final int CONFIG_RESULT_FAILED = 1; - private void sendImsConfigChangedIntent(int item, int value) { - sendImsConfigChangedIntent(item, Integer.toString(value)); - } + private final RemoteCallbackList<IImsConfigCallback> mCallbacks = new RemoteCallbackList<>(); + ImsConfigStub mImsConfigStub; - private void sendImsConfigChangedIntent(int item, String value) { - Intent configChangedIntent = new Intent(ImsConfig.ACTION_IMS_CONFIG_CHANGED); - configChangedIntent.putExtra(ImsConfig.EXTRA_CHANGED_ITEM, item); - configChangedIntent.putExtra(ImsConfig.EXTRA_NEW_VALUE, value); - if (mContext != null) { - mContext.sendBroadcast(configChangedIntent); - } + /** + * Used for compatibility between older versions of the ImsService. + * @hide + */ + public ImsConfigImplBase(Context context) { + mImsConfigStub = new ImsConfigStub(this); + } + + public ImsConfigImplBase() { + mImsConfigStub = new ImsConfigStub(this); + } + + /** + * Adds a {@link Callback} to the list of callbacks notified when a value in the configuration + * changes. + * @param c callback to add. + */ + private void addImsConfigCallback(IImsConfigCallback c) { + mCallbacks.register(c); + } + /** + * Removes a {@link Callback} to the list of callbacks notified when a value in the + * configuration changes. + * + * @param c callback to remove. + */ + private void removeImsConfigCallback(IImsConfigCallback c) { + mCallbacks.unregister(c); + } + + /** + * @param item + * @param value + */ + private final void notifyConfigChanged(int item, int value) { + // can be null in testing + if (mCallbacks == null) { + return; } + mCallbacks.broadcast(c -> { + try { + c.onIntConfigChanged(item, value); + } catch (RemoteException e) { + Log.w(TAG, "notifyConfigChanged(int): dead binder in notify, skipping."); + } + }); + } - protected synchronized void updateCachedValue(int item, int value, boolean notifyChange) { - mProvisionedIntValue.put(item, value); - if (notifyChange) { - sendImsConfigChangedIntent(item, value); + private void notifyConfigChanged(int item, String value) { + // can be null in testing + if (mCallbacks == null) { + return; + } + mCallbacks.broadcast(c -> { + try { + c.onStringConfigChanged(item, value); + } catch (RemoteException e) { + Log.w(TAG, "notifyConfigChanged(string): dead binder in notify, skipping."); } + }); + } + + /** + * @hide + */ + public IImsConfig getIImsConfig() { return mImsConfigStub; } + + /** + * Updates provisioning value and notifies the framework of the change. + * Doesn't call {@link #setConfig(int,int)} and assumes the result succeeded. + * This should only be used when the IMS implementer implicitly changed provisioned values. + * + * @param item an integer key. + * @param value in Integer format. + */ + public final void notifyProvisionedValueChanged(int item, int value) { + try { + mImsConfigStub.updateCachedValue(item, value, true); + } catch (RemoteException e) { + Log.w(TAG, "notifyProvisionedValueChanged(int): Framework connection is dead."); } + } - protected synchronized void updateCachedValue( - int item, String value, boolean notifyChange) { - mProvisionedStringValue.put(item, value); - if (notifyChange) { - sendImsConfigChangedIntent(item, value); - } + /** + * Updates provisioning value and notifies the framework of the change. + * Doesn't call {@link #setConfig(int,String)} and assumes the result succeeded. + * This should only be used when the IMS implementer implicitly changed provisioned values. + * + * @param item an integer key. + * @param value in String format. + */ + public final void notifyProvisionedValueChanged(int item, String value) { + try { + mImsConfigStub.updateCachedValue(item, value, true); + } catch (RemoteException e) { + Log.w(TAG, "notifyProvisionedValueChanged(string): Framework connection is dead."); } } + + /** + * Sets the configuration value for this ImsService. + * + * @param item an integer key. + * @param value an integer containing the configuration value. + * @return the result of setting the configuration value, defined as either + * {@link #CONFIG_RESULT_FAILED} or {@link #CONFIG_RESULT_SUCCESS}. + */ + public int setConfig(int item, int value) { + // Base Implementation - To be overridden. + return CONFIG_RESULT_FAILED; + } + + /** + * Sets the configuration value for this ImsService. + * + * @param item an integer key. + * @param value a String containing the new configuration value. + * @return Result of setting the configuration value, defined as either + * {@link #CONFIG_RESULT_FAILED} or {@link #CONFIG_RESULT_SUCCESS}. + */ + public int setConfig(int item, String value) { + // Base Implementation - To be overridden. + return CONFIG_RESULT_FAILED; + } + + /** + * Gets the currently stored value configuration value from the ImsService for {@code item}. + * + * @param item an integer key. + * @return configuration value, stored in integer format or {@link #CONFIG_RESULT_UNKNOWN} if + * unavailable. + */ + public int getConfigInt(int item) { + // Base Implementation - To be overridden. + return CONFIG_RESULT_UNKNOWN; + } + + /** + * Gets the currently stored value configuration value from the ImsService for {@code item}. + * + * @param item an integer key. + * @return configuration value, stored in String format or {@code null} if unavailable. + */ + public String getConfigString(int item) { + // Base Implementation - To be overridden. + return null; + } } diff --git a/telephony/java/android/telephony/ims/stub/ImsEcbmImplBase.java b/telephony/java/android/telephony/ims/stub/ImsEcbmImplBase.java index 89f95ff0142d..06c35eaec6dd 100644 --- a/telephony/java/android/telephony/ims/stub/ImsEcbmImplBase.java +++ b/telephony/java/android/telephony/ims/stub/ImsEcbmImplBase.java @@ -16,7 +16,9 @@ package android.telephony.ims.stub; +import android.annotation.SystemApi; import android.os.RemoteException; +import android.util.Log; import com.android.ims.internal.IImsEcbm; import com.android.ims.internal.IImsEcbmListener; @@ -30,22 +32,65 @@ import com.android.ims.internal.IImsEcbmListener; * * @hide */ +@SystemApi +public class ImsEcbmImplBase { + private static final String TAG = "ImsEcbmImplBase"; -public class ImsEcbmImplBase extends IImsEcbm.Stub { + private IImsEcbmListener mListener; + private IImsEcbm mImsEcbm = new IImsEcbm.Stub() { + @Override + public void setListener(IImsEcbmListener listener) { + mListener = listener; + } + + @Override + public void exitEmergencyCallbackMode() { + ImsEcbmImplBase.this.exitEmergencyCallbackMode(); + } + }; + + /** @hide */ + public IImsEcbm getImsEcbm() { + return mImsEcbm; + } /** - * Sets the listener. + * This method should be implemented by the IMS provider. Framework will trigger this method to + * request to come out of ECBM mode */ - @Override - public void setListener(IImsEcbmListener listener) throws RemoteException { - + public void exitEmergencyCallbackMode() { + Log.d(TAG, "exitEmergencyCallbackMode() not implemented"); } /** - * Requests Modem to come out of ECBM mode + * Notifies the framework when the device enters Emergency Callback Mode. + * + * @throws RuntimeException if the connection to the framework is not available. */ - @Override - public void exitEmergencyCallbackMode() throws RemoteException { + public final void enteredEcbm() { + Log.d(TAG, "Entered ECBM."); + if (mListener != null) { + try { + mListener.enteredECBM(); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + } + } + /** + * Notifies the framework when the device exits Emergency Callback Mode. + * + * @throws RuntimeException if the connection to the framework is not available. + */ + public final void exitedEcbm() { + Log.d(TAG, "Exited ECBM."); + if (mListener != null) { + try { + mListener.exitedECBM(); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + } } } diff --git a/telephony/java/android/telephony/ims/internal/stub/ImsFeatureConfiguration.aidl b/telephony/java/android/telephony/ims/stub/ImsFeatureConfiguration.aidl index e890cf8756f3..e2ae0e8f6775 100644 --- a/telephony/java/android/telephony/ims/internal/stub/ImsFeatureConfiguration.aidl +++ b/telephony/java/android/telephony/ims/stub/ImsFeatureConfiguration.aidl @@ -14,6 +14,6 @@ * limitations under the License */ -package android.telephony.ims.internal.stub; +package android.telephony.ims.stub; parcelable ImsFeatureConfiguration; diff --git a/telephony/java/android/telephony/ims/internal/stub/ImsFeatureConfiguration.java b/telephony/java/android/telephony/ims/stub/ImsFeatureConfiguration.java index 244c9578f6b4..98b67c3d3727 100644 --- a/telephony/java/android/telephony/ims/internal/stub/ImsFeatureConfiguration.java +++ b/telephony/java/android/telephony/ims/stub/ImsFeatureConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2017 The Android Open Source Project + * Copyright (C) 2018 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,29 +14,34 @@ * limitations under the License */ -package android.telephony.ims.internal.stub; +package android.telephony.ims.stub; +import android.annotation.SystemApi; import android.os.Parcel; import android.os.Parcelable; -import android.telephony.ims.internal.feature.ImsFeature; +import android.telephony.ims.feature.ImsFeature; import android.util.ArraySet; -import java.util.Arrays; import java.util.Set; /** * Container class for IMS Feature configuration. This class contains the features that the - * ImsService supports, which are defined in {@link ImsFeature.FeatureType}. + * ImsService supports, which are defined in {@link ImsFeature} as + * {@link ImsFeature#FEATURE_EMERGENCY_MMTEL}, {@link ImsFeature#FEATURE_MMTEL}, and + * {@link ImsFeature#FEATURE_RCS}. + * * @hide */ -public class ImsFeatureConfiguration implements Parcelable { +@SystemApi +public final class ImsFeatureConfiguration implements Parcelable { /** * Features that this ImsService supports. */ private final Set<Integer> mFeatures; /** - * Creates an ImsFeatureConfiguration with the features + * Builder for {@link ImsFeatureConfiguration} that makes adding supported {@link ImsFeature}s + * easier. */ public static class Builder { ImsFeatureConfiguration mConfig; @@ -72,7 +77,10 @@ public class ImsFeatureConfiguration implements Parcelable { * Configuration of the ImsService, which describes which features the ImsService supports * (for registration). * @param features an array of feature integers defined in {@link ImsFeature} that describe - * which features this ImsService supports. + * which features this ImsService supports. Supported values are + * {@link ImsFeature#FEATURE_EMERGENCY_MMTEL}, {@link ImsFeature#FEATURE_MMTEL}, and + * {@link ImsFeature#FEATURE_RCS}. + * @hide */ public ImsFeatureConfiguration(int[] features) { mFeatures = new ArraySet<>(); @@ -85,7 +93,9 @@ public class ImsFeatureConfiguration implements Parcelable { } /** - * @return an int[] containing the features that this ImsService supports. + * @return an int[] containing the features that this ImsService supports. Supported values are + * {@link ImsFeature#FEATURE_EMERGENCY_MMTEL}, {@link ImsFeature#FEATURE_MMTEL}, and + * {@link ImsFeature#FEATURE_RCS}. */ public int[] getServiceFeatures() { return mFeatures.stream().mapToInt(i->i).toArray(); @@ -95,6 +105,7 @@ public class ImsFeatureConfiguration implements Parcelable { mFeatures.add(feature); } + /** @hide */ protected ImsFeatureConfiguration(Parcel in) { int[] features = in.createIntArray(); if (features != null) { @@ -130,16 +141,23 @@ public class ImsFeatureConfiguration implements Parcelable { dest.writeIntArray(mFeatures.stream().mapToInt(i->i).toArray()); } + /** + * @hide + */ @Override public boolean equals(Object o) { if (this == o) return true; if (!(o instanceof ImsFeatureConfiguration)) return false; - ImsFeatureConfiguration that = (ImsFeatureConfiguration) o; + ImsFeatureConfiguration + that = (ImsFeatureConfiguration) o; return mFeatures.equals(that.mFeatures); } + /** + * @hide + */ @Override public int hashCode() { return mFeatures.hashCode(); diff --git a/telephony/java/android/telephony/ims/stub/ImsMultiEndpointImplBase.java b/telephony/java/android/telephony/ims/stub/ImsMultiEndpointImplBase.java index 05da9da485a9..ce2d89a8d809 100644 --- a/telephony/java/android/telephony/ims/stub/ImsMultiEndpointImplBase.java +++ b/telephony/java/android/telephony/ims/stub/ImsMultiEndpointImplBase.java @@ -16,11 +16,16 @@ package android.telephony.ims.stub; +import android.annotation.SystemApi; import android.os.RemoteException; +import android.util.Log; +import android.telephony.ims.ImsExternalCallState; import com.android.ims.internal.IImsExternalCallStateListener; import com.android.ims.internal.IImsMultiEndpoint; +import java.util.List; + /** * Base implementation of ImsMultiEndpoint, which implements stub versions of the methods * in the IImsMultiEndpoint AIDL. Override the methods that your implementation of @@ -31,23 +36,49 @@ import com.android.ims.internal.IImsMultiEndpoint; * * @hide */ +@SystemApi +public class ImsMultiEndpointImplBase { + private static final String TAG = "MultiEndpointImplBase"; -public class ImsMultiEndpointImplBase extends IImsMultiEndpoint.Stub { + private IImsExternalCallStateListener mListener; + private IImsMultiEndpoint mImsMultiEndpoint = new IImsMultiEndpoint.Stub() { + @Override + public void setListener(IImsExternalCallStateListener listener) throws RemoteException { + mListener = listener; + } - /** - * Sets the listener. - */ - @Override - public void setListener(IImsExternalCallStateListener listener) throws RemoteException { + @Override + public void requestImsExternalCallStateInfo() throws RemoteException { + ImsMultiEndpointImplBase.this.requestImsExternalCallStateInfo(); + } + }; + /** @hide */ + public IImsMultiEndpoint getIImsMultiEndpoint() { + return mImsMultiEndpoint; } /** - * Query API to get the latest Dialog Event Package information - * Should be invoked only after setListener is done + * Notifies framework when Dialog Event Package update is received + * + * @throws RuntimeException if the connection to the framework is not available. */ - @Override - public void requestImsExternalCallStateInfo() throws RemoteException { + public final void onImsExternalCallStateUpdate(List<ImsExternalCallState> externalCallDialogs) { + Log.d(TAG, "ims external call state update triggered."); + if (mListener != null) { + try { + mListener.onImsExternalCallStateUpdate(externalCallDialogs); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + } + } + /** + * This method should be implemented by the IMS provider. Framework will trigger this to get the + * latest Dialog Event Package information. Should + */ + public void requestImsExternalCallStateInfo() { + Log.d(TAG, "requestImsExternalCallStateInfo() not implemented"); } } diff --git a/telephony/java/android/telephony/ims/stub/ImsRegistrationImplBase.java b/telephony/java/android/telephony/ims/stub/ImsRegistrationImplBase.java index 42af08365f61..4334d3aadab3 100644 --- a/telephony/java/android/telephony/ims/stub/ImsRegistrationImplBase.java +++ b/telephony/java/android/telephony/ims/stub/ImsRegistrationImplBase.java @@ -17,15 +17,16 @@ package android.telephony.ims.stub; import android.annotation.IntDef; +import android.annotation.SystemApi; import android.net.Uri; -import android.os.IBinder; import android.os.RemoteCallbackList; import android.os.RemoteException; +import android.telephony.ims.aidl.IImsRegistration; +import android.telephony.ims.aidl.IImsRegistrationCallback; import android.util.Log; -import com.android.ims.ImsReasonInfo; -import com.android.ims.internal.IImsRegistration; -import com.android.ims.internal.IImsRegistrationCallback; +import android.telephony.ims.ImsReasonInfo; + import com.android.internal.annotations.VisibleForTesting; import java.lang.annotation.Retention; @@ -36,11 +37,14 @@ import java.lang.annotation.RetentionPolicy; * registration for this ImsService has changed status. * @hide */ - +@SystemApi public class ImsRegistrationImplBase { private static final String LOG_TAG = "ImsRegistrationImplBase"; + /** + * @hide + */ // Defines the underlying radio technology type that we have registered for IMS over. @IntDef(flag = true, value = { @@ -155,6 +159,9 @@ public class ImsRegistrationImplBase { // Locked on mLock, create unspecified disconnect cause. private ImsReasonInfo mLastDisconnectCause = new ImsReasonInfo(); + /** + * @hide + */ public final IImsRegistration getBinder() { return mBinder; } @@ -171,8 +178,8 @@ public class ImsRegistrationImplBase { /** * Notify the framework that the device is connected to the IMS network. * - * @param imsRadioTech the radio access technology. Valid values are defined in - * {@link ImsRegistrationTech}. + * @param imsRadioTech the radio access technology. Valid values are defined as + * {@link #REGISTRATION_TECH_LTE} and {@link #REGISTRATION_TECH_IWLAN}. */ public final void onRegistered(@ImsRegistrationTech int imsRadioTech) { updateToState(imsRadioTech, REGISTRATION_STATE_REGISTERED); @@ -189,8 +196,8 @@ public class ImsRegistrationImplBase { /** * Notify the framework that the device is trying to connect the IMS network. * - * @param imsRadioTech the radio access technology. Valid values are defined in - * {@link ImsRegistrationTech}. + * @param imsRadioTech the radio access technology. Valid values are defined as + * {@link #REGISTRATION_TECH_LTE} and {@link #REGISTRATION_TECH_IWLAN}. */ public final void onRegistering(@ImsRegistrationTech int imsRadioTech) { updateToState(imsRadioTech, REGISTRATION_STATE_REGISTERING); @@ -221,6 +228,13 @@ public class ImsRegistrationImplBase { }); } + /** + * Notify the framework that the handover from the current radio technology to the technology + * defined in {@code imsRadioTech} has failed. + * @param imsRadioTech The technology that has failed to be changed. Valid values are + * {@link #REGISTRATION_TECH_LTE} and {@link #REGISTRATION_TECH_IWLAN}. + * @param info The {@link ImsReasonInfo} for the failure to change technology. + */ public final void onTechnologyChangeFailed(@ImsRegistrationTech int imsRadioTech, ImsReasonInfo info) { mCallbacks.broadcast((c) -> { @@ -233,6 +247,11 @@ public class ImsRegistrationImplBase { }); } + /** + * The this device's subscriber associated {@link Uri}s have changed, which are used to filter + * out this device's {@link Uri}s during conference calling. + * @param uris + */ public final void onSubscriberAssociatedUriChanged(Uri[] uris) { mCallbacks.broadcast((c) -> { try { @@ -264,6 +283,11 @@ public class ImsRegistrationImplBase { } } + /** + * @return the current registration connection type. Valid values are + * {@link #REGISTRATION_TECH_LTE} and {@link #REGISTRATION_TECH_IWLAN} + * @hide + */ @VisibleForTesting public final @ImsRegistrationTech int getConnectionType() { synchronized (mLock) { diff --git a/telephony/java/android/telephony/ims/internal/stub/SmsImplBase.java b/telephony/java/android/telephony/ims/stub/ImsSmsImplBase.java index 89acc80a9d3b..0673a3845383 100644 --- a/telephony/java/android/telephony/ims/internal/stub/SmsImplBase.java +++ b/telephony/java/android/telephony/ims/stub/ImsSmsImplBase.java @@ -14,17 +14,16 @@ * limitations under the License */ -package android.telephony.ims.internal.stub; +package android.telephony.ims.stub; import android.annotation.IntDef; import android.annotation.SystemApi; import android.os.RemoteException; import android.telephony.SmsManager; import android.telephony.SmsMessage; +import android.telephony.ims.aidl.IImsSmsListener; import android.util.Log; -import com.android.ims.internal.IImsSmsListener; - import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -37,7 +36,7 @@ import java.lang.annotation.RetentionPolicy; * @hide */ @SystemApi -public class SmsImplBase { +public class ImsSmsImplBase { private static final String LOG_TAG = "SmsImplBase"; /** @hide */ @@ -158,7 +157,7 @@ public class SmsImplBase { * @param token token provided in {@link #onSmsReceived(int, String, byte[])} * @param result result of delivering the message. Valid values are: * {@link #DELIVER_STATUS_OK}, - * {@link #DELIVER_STATUS_OK} + * {@link #DELIVER_STATUS_ERROR} * @param messageRef the message reference */ public void acknowledgeSms(int token, @DeliverStatusResult int messageRef, int result) { @@ -299,9 +298,9 @@ public class SmsImplBase { } /** - * Called when SmsImpl has been initialized and communication with the framework is set up. + * Called when ImsSmsImpl has been initialized and communication with the framework is set up. * Any attempt by this class to access the framework before this method is called will return - * with an {@link RuntimeException}. + * with a {@link RuntimeException}. */ public void onReady() { // Base Implementation - Should be overridden diff --git a/telephony/java/android/telephony/ims/stub/ImsUtImplBase.java b/telephony/java/android/telephony/ims/stub/ImsUtImplBase.java index 054a8b22d0f2..fcd7faf73bb8 100644 --- a/telephony/java/android/telephony/ims/stub/ImsUtImplBase.java +++ b/telephony/java/android/telephony/ims/stub/ImsUtImplBase.java @@ -16,177 +16,332 @@ package android.telephony.ims.stub; +import android.annotation.SystemApi; import android.os.Bundle; import android.os.RemoteException; +import android.telephony.ims.ImsUtListener; import com.android.ims.internal.IImsUt; import com.android.ims.internal.IImsUtListener; /** - * Base implementation of ImsUt, which implements stub versions of the methods - * in the IImsUt AIDL. Override the methods that your implementation of ImsUt supports. - * - * DO NOT remove or change the existing APIs, only add new ones to this Base implementation or you - * will break other implementations of ImsUt maintained by other ImsServices. - * - * Provides the Ut interface interworking to get/set the supplementary service configuration. + * Base implementation of IMS UT interface, which implements stubs. Override these methods to + * implement functionality. * * @hide */ +// DO NOT remove or change the existing APIs, only add new ones to this Base implementation or you +// will break other implementations of ImsUt maintained by other ImsServices. +@SystemApi +public class ImsUtImplBase { + + private IImsUt.Stub mServiceImpl = new IImsUt.Stub() { + @Override + public void close() throws RemoteException { + ImsUtImplBase.this.close(); + } + + @Override + public int queryCallBarring(int cbType) throws RemoteException { + return ImsUtImplBase.this.queryCallBarring(cbType); + } + + @Override + public int queryCallForward(int condition, String number) throws RemoteException { + return ImsUtImplBase.this.queryCallForward(condition, number); + } + + @Override + public int queryCallWaiting() throws RemoteException { + return ImsUtImplBase.this.queryCallWaiting(); + } + + @Override + public int queryCLIR() throws RemoteException { + return ImsUtImplBase.this.queryCLIR(); + } + + @Override + public int queryCLIP() throws RemoteException { + return ImsUtImplBase.this.queryCLIP(); + } + + @Override + public int queryCOLR() throws RemoteException { + return ImsUtImplBase.this.queryCOLR(); + } + + @Override + public int queryCOLP() throws RemoteException { + return ImsUtImplBase.this.queryCOLP(); + } + + @Override + public int transact(Bundle ssInfo) throws RemoteException { + return ImsUtImplBase.this.transact(ssInfo); + } + + @Override + public int updateCallBarring(int cbType, int action, String[] barrList) throws + RemoteException { + return ImsUtImplBase.this.updateCallBarring(cbType, action, barrList); + } + + @Override + public int updateCallForward(int action, int condition, String number, int serviceClass, + int timeSeconds) throws RemoteException { + return ImsUtImplBase.this.updateCallForward(action, condition, number, serviceClass, + timeSeconds); + } + + @Override + public int updateCallWaiting(boolean enable, int serviceClass) throws RemoteException { + return ImsUtImplBase.this.updateCallWaiting(enable, serviceClass); + } + + @Override + public int updateCLIR(int clirMode) throws RemoteException { + return ImsUtImplBase.this.updateCLIR(clirMode); + } + + @Override + public int updateCLIP(boolean enable) throws RemoteException { + return ImsUtImplBase.this.updateCLIP(enable); + } -public class ImsUtImplBase extends IImsUt.Stub { + @Override + public int updateCOLR(int presentation) throws RemoteException { + return ImsUtImplBase.this.updateCOLR(presentation); + } + + @Override + public int updateCOLP(boolean enable) throws RemoteException { + return ImsUtImplBase.this.updateCOLP(enable); + } + + @Override + public void setListener(IImsUtListener listener) throws RemoteException { + ImsUtImplBase.this.setListener(new ImsUtListener(listener)); + } + + @Override + public int queryCallBarringForServiceClass(int cbType, int serviceClass) + throws RemoteException { + return ImsUtImplBase.this.queryCallBarringForServiceClass(cbType, serviceClass); + } + + @Override + public int updateCallBarringForServiceClass(int cbType, int action, + String[] barrList, int serviceClass) throws RemoteException { + return ImsUtImplBase.this.updateCallBarringForServiceClass( + cbType, action, barrList, serviceClass); + } + }; /** - * Closes the object. This object is not usable after being closed. + * Called when the framework no longer needs to interact with the IMS UT implementation any + * longer. */ - @Override - public void close() throws RemoteException { + public void close() { } /** - * Retrieves the configuration of the call barring. + * Retrieves the call barring configuration. + * @param cbType */ - @Override - public int queryCallBarring(int cbType) throws RemoteException { + public int queryCallBarring(int cbType) { return -1; } /** * Retrieves the configuration of the call barring for specified service class. */ - @Override - public int queryCallBarringForServiceClass(int cbType, int serviceClass) - throws RemoteException { + public int queryCallBarringForServiceClass(int cbType, int serviceClass) { return -1; } /** * Retrieves the configuration of the call forward. */ - @Override - public int queryCallForward(int condition, String number) throws RemoteException { + public int queryCallForward(int condition, String number) { return -1; } /** * Retrieves the configuration of the call waiting. */ - @Override - public int queryCallWaiting() throws RemoteException { + public int queryCallWaiting() { return -1; } /** * Retrieves the default CLIR setting. + * @hide + */ + public int queryCLIR() { + return queryClir(); + } + + /** + * Retrieves the CLIP call setting. + * @hide + */ + public int queryCLIP() { + return queryClip(); + } + + /** + * Retrieves the COLR call setting. + * @hide + */ + public int queryCOLR() { + return queryColr(); + } + + /** + * Retrieves the COLP call setting. + * @hide + */ + public int queryCOLP() { + return queryColp(); + } + + /** + * Retrieves the default CLIR setting. */ - @Override - public int queryCLIR() throws RemoteException { + public int queryClir() { return -1; } /** * Retrieves the CLIP call setting. */ - @Override - public int queryCLIP() throws RemoteException { + public int queryClip() { return -1; } /** * Retrieves the COLR call setting. */ - @Override - public int queryCOLR() throws RemoteException { + public int queryColr() { return -1; } /** * Retrieves the COLP call setting. */ - @Override - public int queryCOLP() throws RemoteException { + public int queryColp() { return -1; } /** * Updates or retrieves the supplementary service configuration. */ - @Override - public int transact(Bundle ssInfo) throws RemoteException { + public int transact(Bundle ssInfo) { return -1; } /** * Updates the configuration of the call barring. */ - @Override - public int updateCallBarring(int cbType, int action, String[] barrList) throws RemoteException { + public int updateCallBarring(int cbType, int action, String[] barrList) { return -1; } /** * Updates the configuration of the call barring for specified service class. */ - @Override public int updateCallBarringForServiceClass(int cbType, int action, String[] barrList, - int serviceClass) throws RemoteException { + int serviceClass) { return -1; } /** * Updates the configuration of the call forward. */ - @Override public int updateCallForward(int action, int condition, String number, int serviceClass, - int timeSeconds) throws RemoteException { + int timeSeconds) { return 0; } /** * Updates the configuration of the call waiting. */ - @Override - public int updateCallWaiting(boolean enable, int serviceClass) throws RemoteException { + public int updateCallWaiting(boolean enable, int serviceClass) { return -1; } /** * Updates the configuration of the CLIR supplementary service. + * @hide + */ + public int updateCLIR(int clirMode) { + return updateClir(clirMode); + } + + /** + * Updates the configuration of the CLIP supplementary service. + * @hide + */ + public int updateCLIP(boolean enable) { + return updateClip(enable); + } + + /** + * Updates the configuration of the COLR supplementary service. + * @hide + */ + public int updateCOLR(int presentation) { + return updateColr(presentation); + } + + /** + * Updates the configuration of the COLP supplementary service. + * @hide + */ + public int updateCOLP(boolean enable) { + return updateColp(enable); + } + + /** + * Updates the configuration of the CLIR supplementary service. */ - @Override - public int updateCLIR(int clirMode) throws RemoteException { + public int updateClir(int clirMode) { return -1; } /** * Updates the configuration of the CLIP supplementary service. */ - @Override - public int updateCLIP(boolean enable) throws RemoteException { + public int updateClip(boolean enable) { return -1; } /** * Updates the configuration of the COLR supplementary service. */ - @Override - public int updateCOLR(int presentation) throws RemoteException { + public int updateColr(int presentation) { return -1; } /** * Updates the configuration of the COLP supplementary service. */ - @Override - public int updateCOLP(boolean enable) throws RemoteException { + public int updateColp(boolean enable) { return -1; } /** * Sets the listener. */ - @Override - public void setListener(IImsUtListener listener) throws RemoteException { + public void setListener(ImsUtListener listener) { + } + + /** + * @hide + */ + public IImsUt getInterface() { + return mServiceImpl; } } diff --git a/telephony/java/com/android/ims/ImsConfig.java b/telephony/java/com/android/ims/ImsConfig.java index cd0c4b115c0e..1dda6bf2b8bc 100644 --- a/telephony/java/com/android/ims/ImsConfig.java +++ b/telephony/java/com/android/ims/ImsConfig.java @@ -19,8 +19,9 @@ package com.android.ims; import android.content.Context; import android.os.RemoteException; import android.telephony.Rlog; - -import com.android.ims.internal.IImsConfig; +import android.telephony.ims.ImsReasonInfo; +import android.telephony.ims.aidl.IImsConfig; +import android.telephony.ims.stub.ImsConfigImplBase; /** * Provides APIs to get/set the IMS service feature/capability/parameters. @@ -46,7 +47,7 @@ public class ImsConfig { /** * Broadcast action: the configuration was changed - * + * @deprecated Use {@link ImsConfig#addConfigCallback(ImsConfigImplBase.Callback)} instead. * @hide */ public static final String ACTION_IMS_CONFIG_CHANGED = @@ -70,6 +71,8 @@ public class ImsConfig { /** * Defines IMS service/capability feature constants. + * @deprecated Use + * {@link android.telephony.ims.feature.MmTelFeature.MmTelCapabilities.MmTelCapability} instead. */ public static class FeatureConstants { public static final int FEATURE_TYPE_UNKNOWN = -1; @@ -539,164 +542,166 @@ public class ImsConfig { } public ImsConfig(IImsConfig iconfig, Context context) { - if (DBG) Rlog.d(TAG, "ImsConfig creates"); + if (DBG) Rlog.d(TAG, "ImsConfig created"); miConfig = iconfig; mContext = context; } /** - * Gets the provisioned value for IMS service/capabilities parameters used by IMS stack. - * This function should not be called from the mainthread as it could block the - * mainthread. + * @deprecated see {@link #getInt(int)} instead. + */ + public int getProvisionedValue(int item) throws ImsException { + return getConfigInt(item); + } + + /** + * Gets the configuration value for IMS service/capabilities parameters used by IMS stack. * * @param item, as defined in com.android.ims.ImsConfig#ConfigConstants. * @return the value in Integer format. - * - * @throws ImsException if calling the IMS service results in an error. + * @throws ImsException if the ImsService is unavailable. */ - public int getProvisionedValue(int item) throws ImsException { + public int getConfigInt(int item) throws ImsException { int ret = 0; try { - ret = miConfig.getProvisionedValue(item); + ret = miConfig.getConfigInt(item); } catch (RemoteException e) { - throw new ImsException("getValue()", e, + throw new ImsException("getInt()", e, ImsReasonInfo.CODE_LOCAL_SERVICE_UNAVAILABLE); } - if (DBG) Rlog.d(TAG, "getProvisionedValue(): item = " + item + ", ret =" + ret); + if (DBG) Rlog.d(TAG, "getInt(): item = " + item + ", ret =" + ret); return ret; } /** - * Gets the provisioned value for IMS service/capabilities parameters used by IMS stack. - * This function should not be called from the mainthread as it could block the - * mainthread. + * @deprecated see {@link #getConfigString(int)} instead + */ + public String getProvisionedStringValue(int item) throws ImsException { + return getConfigString(item); + } + + /** + * Gets the configuration value for IMS service/capabilities parameters used by IMS stack. * * @param item, as defined in com.android.ims.ImsConfig#ConfigConstants. * @return value in String format. * - * @throws ImsException if calling the IMS service results in an error. + * @throws ImsException if the ImsService is unavailable. */ - public String getProvisionedStringValue(int item) throws ImsException { + public String getConfigString(int item) throws ImsException { String ret = "Unknown"; try { - ret = miConfig.getProvisionedStringValue(item); + ret = miConfig.getConfigString(item); } catch (RemoteException e) { - throw new ImsException("getProvisionedStringValue()", e, + throw new ImsException("getConfigString()", e, ImsReasonInfo.CODE_LOCAL_SERVICE_UNAVAILABLE); } - if (DBG) Rlog.d(TAG, "getProvisionedStringValue(): item = " + item + ", ret =" + ret); + if (DBG) Rlog.d(TAG, "getConfigString(): item = " + item + ", ret =" + ret); return ret; } /** - * Sets the value for IMS service/capabilities parameters by - * the operator device management entity. - * This function should not be called from main thread as it could block - * mainthread. + * @deprecated see {@link #setConfig(int, int)} instead. + */ + public int setProvisionedValue(int item, int value) throws ImsException { + return setConfig(item, value); + } + + /** + * @deprecated see {@link #setConfig(int, String)} instead. + */ + public int setProvisionedStringValue(int item, String value) throws ImsException { + return setConfig(item, value); + } + + /** + * Sets the value for ImsService configuration item. * * @param item, as defined in com.android.ims.ImsConfig#ConfigConstants. * @param value in Integer format. * @return as defined in com.android.ims.ImsConfig#OperationStatusConstants * - * @throws ImsException if calling the IMS service results in an error. + * @throws ImsException if the ImsService is unavailable. */ - public int setProvisionedValue(int item, int value) - throws ImsException { + public int setConfig(int item, int value) throws ImsException { int ret = OperationStatusConstants.UNKNOWN; if (DBG) { - Rlog.d(TAG, "setProvisionedValue(): item = " + item + + Rlog.d(TAG, "setConfig(): item = " + item + "value = " + value); } try { - ret = miConfig.setProvisionedValue(item, value); + ret = miConfig.setConfigInt(item, value); } catch (RemoteException e) { - throw new ImsException("setProvisionedValue()", e, + throw new ImsException("setConfig()", e, ImsReasonInfo.CODE_LOCAL_SERVICE_UNAVAILABLE); } if (DBG) { - Rlog.d(TAG, "setProvisionedValue(): item = " + item + + Rlog.d(TAG, "setConfig(): item = " + item + " value = " + value + " ret = " + ret); } return ret; + } /** - * Sets the value for IMS service/capabilities parameters by - * the operator device management entity. - * This function should not be called from main thread as it could block - * mainthread. + * Sets the value for ImsService configuration item. * * @param item, as defined in com.android.ims.ImsConfig#ConfigConstants. - * @param value in String format. + * @param value in Integer format. * @return as defined in com.android.ims.ImsConfig#OperationStatusConstants * - * @throws ImsException if calling the IMS service results in an error. + * @throws ImsException if the ImsService is unavailable. */ - public int setProvisionedStringValue(int item, String value) - throws ImsException { + public int setConfig(int item, String value) throws ImsException { int ret = OperationStatusConstants.UNKNOWN; + if (DBG) { + Rlog.d(TAG, "setConfig(): item = " + item + + "value = " + value); + } try { - ret = miConfig.setProvisionedStringValue(item, value); + ret = miConfig.setConfigString(item, value); } catch (RemoteException e) { - throw new ImsException("setProvisionedStringValue()", e, + throw new ImsException("setConfig()", e, ImsReasonInfo.CODE_LOCAL_SERVICE_UNAVAILABLE); } if (DBG) { - Rlog.d(TAG, "setProvisionedStringValue(): item = " + item + - ", value =" + value); + Rlog.d(TAG, "setConfig(): item = " + item + + " value = " + value + " ret = " + ret); } return ret; } /** - * Gets the value for IMS feature item for specified network type. + * Adds a {@link ImsConfigImplBase.Callback} to the ImsService to notify when a Configuration + * item has changed. * - * @param feature, defined as in FeatureConstants. - * @param network, defined as in android.telephony.TelephonyManager#NETWORK_TYPE_XXX. - * @param listener, provided to be notified for the feature on/off status. - * @return void - * - * @throws ImsException if calling the IMS service results in an error. + * Make sure to call {@link #removeConfigCallback(ImsConfigImplBase.Callback)} when finished + * using this callback. */ - public void getFeatureValue(int feature, int network, - ImsConfigListener listener) throws ImsException { - if (DBG) { - Rlog.d(TAG, "getFeatureValue: feature = " + feature + ", network =" + network + - ", listener =" + listener); - } + public void addConfigCallback(ImsConfigImplBase.Callback callback) throws ImsException { + if (DBG) Rlog.d(TAG, "addConfigCallback: " + callback); try { - miConfig.getFeatureValue(feature, network, listener); - } catch (RemoteException e) { - throw new ImsException("getFeatureValue()", e, + miConfig.addImsConfigCallback(callback); + } catch (RemoteException e) { + throw new ImsException("addConfigCallback()", e, ImsReasonInfo.CODE_LOCAL_SERVICE_UNAVAILABLE); } } /** - * Sets the value for IMS feature item for specified network type. - * - * @param feature, as defined in FeatureConstants. - * @param network, as defined in android.telephony.TelephonyManager#NETWORK_TYPE_XXX. - * @param value, as defined in FeatureValueConstants. - * @param listener, provided if caller needs to be notified for set result. - * @return void - * - * @throws ImsException if calling the IMS service results in an error. + * Removes a {@link ImsConfigImplBase.Callback} from the ImsService that was previously added + * by {@link #addConfigCallback(ImsConfigImplBase.Callback)}. */ - public void setFeatureValue(int feature, int network, int value, - ImsConfigListener listener) throws ImsException { - if (DBG) { - Rlog.d(TAG, "setFeatureValue: feature = " + feature + ", network =" + network + - ", value =" + value + ", listener =" + listener); - } + public void removeConfigCallback(ImsConfigImplBase.Callback callback) throws ImsException { + if (DBG) Rlog.d(TAG, "removeConfigCallback: " + callback); try { - miConfig.setFeatureValue(feature, network, value, listener); - } catch (RemoteException e) { - throw new ImsException("setFeatureValue()", e, + miConfig.removeImsConfigCallback(callback); + } catch (RemoteException e) { + throw new ImsException("removeConfigCallback()", e, ImsReasonInfo.CODE_LOCAL_SERVICE_UNAVAILABLE); } } diff --git a/telephony/java/com/android/ims/ImsException.java b/telephony/java/com/android/ims/ImsException.java index 0e8bad79c59b..f35e88672a23 100644 --- a/telephony/java/com/android/ims/ImsException.java +++ b/telephony/java/com/android/ims/ImsException.java @@ -16,6 +16,8 @@ package com.android.ims; +import android.telephony.ims.ImsReasonInfo; + /** * This class defines a general IMS-related exception. * diff --git a/telephony/java/com/android/ims/ImsUtInterface.java b/telephony/java/com/android/ims/ImsUtInterface.java index 14c184a64061..c9d440551631 100644 --- a/telephony/java/com/android/ims/ImsUtInterface.java +++ b/telephony/java/com/android/ims/ImsUtInterface.java @@ -18,6 +18,8 @@ package com.android.ims; import android.os.Handler; import android.os.Message; +import android.telephony.ims.ImsCallForwardInfo; +import android.telephony.ims.ImsSsInfo; /** * Provides APIs for the supplementary service settings using IMS (Ut interface). diff --git a/telephony/java/com/android/ims/internal/IImsCallSession.aidl b/telephony/java/com/android/ims/internal/IImsCallSession.aidl index c6fc5e563bf4..203e6cf9c577 100644 --- a/telephony/java/com/android/ims/internal/IImsCallSession.aidl +++ b/telephony/java/com/android/ims/internal/IImsCallSession.aidl @@ -17,9 +17,10 @@ package com.android.ims.internal; import android.os.Message; -import com.android.ims.ImsCallProfile; -import com.android.ims.ImsStreamMediaProfile; -import com.android.ims.internal.IImsCallSessionListener; +import android.telephony.ims.aidl.IImsCallSessionListener; + +import android.telephony.ims.ImsCallProfile; +import android.telephony.ims.ImsStreamMediaProfile; import com.android.ims.internal.IImsVideoCallProvider; /** diff --git a/telephony/java/com/android/ims/internal/IImsCallSessionListener.aidl b/telephony/java/com/android/ims/internal/IImsCallSessionListener.aidl index 748092d2a3bd..a8e8b7dd03aa 100644 --- a/telephony/java/com/android/ims/internal/IImsCallSessionListener.aidl +++ b/telephony/java/com/android/ims/internal/IImsCallSessionListener.aidl @@ -16,12 +16,12 @@ package com.android.ims.internal; -import com.android.ims.ImsStreamMediaProfile; -import com.android.ims.ImsCallProfile; -import com.android.ims.ImsReasonInfo; -import com.android.ims.ImsConferenceState; +import android.telephony.ims.ImsStreamMediaProfile; +import android.telephony.ims.ImsCallProfile; +import android.telephony.ims.ImsReasonInfo; +import android.telephony.ims.ImsConferenceState; import com.android.ims.internal.IImsCallSession; -import com.android.ims.ImsSuppServiceNotification; +import android.telephony.ims.ImsSuppServiceNotification; /** * A listener type for receiving notification on IMS call session events. diff --git a/telephony/java/com/android/ims/internal/IImsExternalCallStateListener.aidl b/telephony/java/com/android/ims/internal/IImsExternalCallStateListener.aidl index 16219671cea5..b3d813960c55 100644 --- a/telephony/java/com/android/ims/internal/IImsExternalCallStateListener.aidl +++ b/telephony/java/com/android/ims/internal/IImsExternalCallStateListener.aidl @@ -16,7 +16,7 @@ package com.android.ims.internal; -import com.android.ims.ImsExternalCallState; +import android.telephony.ims.ImsExternalCallState; /** * A listener type for receiving notifications about DEP through IMS diff --git a/telephony/java/com/android/ims/internal/IImsFeatureStatusCallback.aidl b/telephony/java/com/android/ims/internal/IImsFeatureStatusCallback.aidl index 41b1042e9a86..b83b13060aff 100644 --- a/telephony/java/com/android/ims/internal/IImsFeatureStatusCallback.aidl +++ b/telephony/java/com/android/ims/internal/IImsFeatureStatusCallback.aidl @@ -17,9 +17,9 @@ package com.android.ims.internal; /** -* Interface from ImsFeature in the ImsService to ImsServiceController. + * Interface from ImsFeature in the ImsService to ImsServiceController. * {@hide} */ oneway interface IImsFeatureStatusCallback { void notifyImsFeatureStatus(int featureStatus); -}
\ No newline at end of file +} diff --git a/telephony/java/com/android/ims/internal/IImsMMTelFeature.aidl b/telephony/java/com/android/ims/internal/IImsMMTelFeature.aidl index 10c7f3e8a2d7..51511923402f 100644 --- a/telephony/java/com/android/ims/internal/IImsMMTelFeature.aidl +++ b/telephony/java/com/android/ims/internal/IImsMMTelFeature.aidl @@ -18,14 +18,12 @@ package com.android.ims.internal; import android.app.PendingIntent; -import com.android.ims.ImsCallProfile; +import android.telephony.ims.ImsCallProfile; import com.android.ims.internal.IImsCallSession; -import com.android.ims.internal.IImsCallSessionListener; import com.android.ims.internal.IImsConfig; import com.android.ims.internal.IImsEcbm; import com.android.ims.internal.IImsMultiEndpoint; import com.android.ims.internal.IImsRegistrationListener; -import com.android.ims.internal.IImsSmsListener; import com.android.ims.internal.IImsUt; import android.os.Message; @@ -44,8 +42,7 @@ interface IImsMMTelFeature { void addRegistrationListener(in IImsRegistrationListener listener); void removeRegistrationListener(in IImsRegistrationListener listener); ImsCallProfile createCallProfile(int sessionId, int callSessionType, int callType); - IImsCallSession createCallSession(int sessionId, in ImsCallProfile profile, - IImsCallSessionListener listener); + IImsCallSession createCallSession(int sessionId, in ImsCallProfile profile); IImsCallSession getPendingCallSession(int sessionId, String callId); IImsUt getUtInterface(); IImsConfig getConfigInterface(); @@ -54,12 +51,4 @@ interface IImsMMTelFeature { IImsEcbm getEcbmInterface(); void setUiTTYMode(int uiTtyMode, in Message onComplete); IImsMultiEndpoint getMultiEndpointInterface(); - // SMS APIs - void setSmsListener(IImsSmsListener l); - oneway void sendSms(in int token, int messageRef, String format, String smsc, boolean retry, - in byte[] pdu); - oneway void acknowledgeSms(int token, int messageRef, int result); - oneway void acknowledgeSmsReport(int token, int messageRef, int result); - String getSmsFormat(); - oneway void onSmsReady(); } diff --git a/telephony/java/com/android/ims/internal/IImsRegistrationListener.aidl b/telephony/java/com/android/ims/internal/IImsRegistrationListener.aidl index 15f872603bfb..2212109c8a67 100644 --- a/telephony/java/com/android/ims/internal/IImsRegistrationListener.aidl +++ b/telephony/java/com/android/ims/internal/IImsRegistrationListener.aidl @@ -16,7 +16,7 @@ package com.android.ims.internal; -import com.android.ims.ImsReasonInfo; +import android.telephony.ims.ImsReasonInfo; import android.net.Uri; diff --git a/telephony/java/com/android/ims/internal/IImsService.aidl b/telephony/java/com/android/ims/internal/IImsService.aidl index 406d22d9bd8e..c3cc6fb7a385 100644 --- a/telephony/java/com/android/ims/internal/IImsService.aidl +++ b/telephony/java/com/android/ims/internal/IImsService.aidl @@ -18,7 +18,7 @@ package com.android.ims.internal; import android.app.PendingIntent; -import com.android.ims.ImsCallProfile; +import android.telephony.ims.ImsCallProfile; import com.android.ims.internal.IImsCallSession; import com.android.ims.internal.IImsCallSessionListener; import com.android.ims.internal.IImsConfig; diff --git a/telephony/java/com/android/ims/internal/IImsServiceController.aidl b/telephony/java/com/android/ims/internal/IImsServiceController.aidl index 7ac25ac13fbe..857089fac33a 100644 --- a/telephony/java/com/android/ims/internal/IImsServiceController.aidl +++ b/telephony/java/com/android/ims/internal/IImsServiceController.aidl @@ -18,7 +18,6 @@ package com.android.ims.internal; import com.android.ims.internal.IImsFeatureStatusCallback; import com.android.ims.internal.IImsMMTelFeature; -import com.android.ims.internal.IImsRegistration; import com.android.ims.internal.IImsRcsFeature; /** @@ -30,5 +29,4 @@ interface IImsServiceController { IImsMMTelFeature createMMTelFeature(int slotId, in IImsFeatureStatusCallback c); IImsRcsFeature createRcsFeature(int slotId, in IImsFeatureStatusCallback c); void removeImsFeature(int slotId, int featureType, in IImsFeatureStatusCallback c); - IImsRegistration getRegistration(int slotId); } diff --git a/telephony/java/com/android/ims/internal/IImsUtListener.aidl b/telephony/java/com/android/ims/internal/IImsUtListener.aidl index 1bc036979356..a603cd34dfcd 100644 --- a/telephony/java/com/android/ims/internal/IImsUtListener.aidl +++ b/telephony/java/com/android/ims/internal/IImsUtListener.aidl @@ -18,11 +18,11 @@ package com.android.ims.internal; import android.os.Bundle; -import com.android.ims.ImsCallForwardInfo; -import com.android.ims.ImsSsData; -import com.android.ims.ImsSsInfo; +import android.telephony.ims.ImsCallForwardInfo; +import android.telephony.ims.ImsSsInfo; import com.android.ims.internal.IImsUt; -import com.android.ims.ImsReasonInfo; +import android.telephony.ims.ImsReasonInfo; +import android.telephony.ims.ImsSsData; /** * {@hide} diff --git a/telephony/java/com/android/internal/telephony/ExponentialBackoff.java b/telephony/java/com/android/internal/telephony/ExponentialBackoff.java index 80958c077d6a..f323a0cc6201 100644 --- a/telephony/java/com/android/internal/telephony/ExponentialBackoff.java +++ b/telephony/java/com/android/internal/telephony/ExponentialBackoff.java @@ -20,6 +20,8 @@ import android.annotation.NonNull; import android.os.Handler; import android.os.Looper; +import com.android.internal.annotations.VisibleForTesting; + /** The implementation of exponential backoff with jitter applied. */ public class ExponentialBackoff { private int mRetryCounter; @@ -27,8 +29,31 @@ public class ExponentialBackoff { private long mMaximumDelayMs; private long mCurrentDelayMs; private int mMultiplier; - private Runnable mRunnable; - private Handler mHandler; + private final Runnable mRunnable; + private final Handler mHandler; + + /** + * Implementation of Handler methods, Adapter for testing (can't spy on final methods). + */ + private HandlerAdapter mHandlerAdapter = new HandlerAdapter() { + @Override + public boolean postDelayed(Runnable runnable, long delayMillis) { + return mHandler.postDelayed(runnable, delayMillis); + } + + @Override + public void removeCallbacks(Runnable runnable) { + mHandler.removeCallbacks(runnable); + } + }; + + /** + * Need to spy final methods for testing. + */ + public interface HandlerAdapter { + boolean postDelayed(Runnable runnable, long delayMillis); + void removeCallbacks(Runnable runnable); + } public ExponentialBackoff( long initialDelayMs, @@ -57,14 +82,14 @@ public class ExponentialBackoff { public void start() { mRetryCounter = 0; mCurrentDelayMs = mStartDelayMs; - mHandler.removeCallbacks(mRunnable); - mHandler.postDelayed(mRunnable, mCurrentDelayMs); + mHandlerAdapter.removeCallbacks(mRunnable); + mHandlerAdapter.postDelayed(mRunnable, mCurrentDelayMs); } /** Stops the backoff, all pending messages will be removed from the message queue. */ public void stop() { mRetryCounter = 0; - mHandler.removeCallbacks(mRunnable); + mHandlerAdapter.removeCallbacks(mRunnable); } /** Should call when the retry action has failed and we want to retry after a longer delay. */ @@ -73,12 +98,17 @@ public class ExponentialBackoff { long temp = Math.min( mMaximumDelayMs, (long) (mStartDelayMs * Math.pow(mMultiplier, mRetryCounter))); mCurrentDelayMs = (long) (((1 + Math.random()) / 2) * temp); - mHandler.removeCallbacks(mRunnable); - mHandler.postDelayed(mRunnable, mCurrentDelayMs); + mHandlerAdapter.removeCallbacks(mRunnable); + mHandlerAdapter.postDelayed(mRunnable, mCurrentDelayMs); } /** Returns the delay for the most recently posted message. */ public long getCurrentDelay() { return mCurrentDelayMs; } + + @VisibleForTesting + public void setHandlerAdapter(HandlerAdapter a) { + mHandlerAdapter = a; + } } diff --git a/telephony/java/com/android/internal/telephony/IPhoneSubInfo.aidl b/telephony/java/com/android/internal/telephony/IPhoneSubInfo.aidl index f8a040da30ba..0ed0820c888f 100644 --- a/telephony/java/com/android/internal/telephony/IPhoneSubInfo.aidl +++ b/telephony/java/com/android/internal/telephony/IPhoneSubInfo.aidl @@ -202,14 +202,6 @@ interface IPhoneSubInfo { String[] getIsimPcscf(int subId); /** - * TODO: Deprecate and remove this interface. Superceded by getIccsimChallengeResponse. - * Returns the response of ISIM Authetification through RIL. - * @return the response of ISIM Authetification, or null if - * the Authentification hasn't been successed or isn't present iphonesubinfo. - */ - String getIsimChallengeResponse(String nonce); - - /** * Returns the response of the SIM application on the UICC to authentication * challenge/response algorithm. The data string and challenge response are * Base64 encoded Strings. diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl index dfb3c344cb93..2b4c059cf69f 100644 --- a/telephony/java/com/android/internal/telephony/ITelephony.aidl +++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl @@ -38,9 +38,10 @@ import android.telephony.ServiceState; import android.telephony.SignalStrength; import android.telephony.TelephonyHistogram; import android.telephony.VisualVoicemailSmsFilterSettings; -import com.android.ims.internal.IImsMMTelFeature; -import com.android.ims.internal.IImsRcsFeature; -import com.android.ims.internal.IImsRegistration; +import android.telephony.ims.aidl.IImsConfig; +import android.telephony.ims.aidl.IImsMmTelFeature; +import android.telephony.ims.aidl.IImsRcsFeature; +import android.telephony.ims.aidl.IImsRegistration; import com.android.ims.internal.IImsServiceFeatureCallback; import com.android.internal.telephony.CellNetworkScanResult; import com.android.internal.telephony.OperatorInfo; @@ -680,6 +681,7 @@ interface ITelephony { * Input parameters equivalent to TS 27.007 AT+CSIM command. * * @param subId The subscription to use. + * @param callingPackage the name of the package making the call. * @param cla Class of the APDU command. * @param instruction Instruction of the APDU command. * @param p1 P1 value of the APDU command. @@ -690,7 +692,7 @@ interface ITelephony { * @return The APDU response from the ICC card with the status appended at * the end. */ - String iccTransmitApduBasicChannel(int subId, int cla, int instruction, + String iccTransmitApduBasicChannel(int subId, String callingPackage, int cla, int instruction, int p1, int p2, int p3, String data); /** @@ -787,20 +789,21 @@ interface ITelephony { int getTetherApnRequired(); /** - * Get IImsMMTelFeature binder from ImsResolver that corresponds to the subId and MMTel feature - * as well as registering the MMTelFeature for callbacks using the IImsServiceFeatureCallback - * interface. - */ - IImsMMTelFeature getMMTelFeatureAndListen(int slotId, in IImsServiceFeatureCallback callback); + * Enables framework IMS and triggers IMS Registration. + */ + void enableIms(int slotId); + + /** + * Disables framework IMS and triggers IMS deregistration. + */ + void disableIms(int slotId); /** - * Get IImsMMTelFeature binder from ImsResolver that corresponds to the subId and MMTel feature - * as well as registering the MMTelFeature for callbacks using the IImsServiceFeatureCallback + * Get IImsMmTelFeature binder from ImsResolver that corresponds to the subId and MMTel feature + * as well as registering the MmTelFeature for callbacks using the IImsServiceFeatureCallback * interface. - * Used for emergency calling only. */ - IImsMMTelFeature getEmergencyMMTelFeatureAndListen(int slotId, - in IImsServiceFeatureCallback callback); + IImsMmTelFeature getMmTelFeatureAndListen(int slotId, in IImsServiceFeatureCallback callback); /** * Get IImsRcsFeature binder from ImsResolver that corresponds to the subId and RCS feature @@ -815,6 +818,11 @@ interface ITelephony { IImsRegistration getImsRegistration(int slotId, int feature); /** + * Returns the IImsConfig associated with the slot and feature specified. + */ + IImsConfig getImsConfig(int slotId, int feature); + + /** * Set the network selection mode to automatic. * * @param subId the id of the subscription to update. diff --git a/test-base/Android.bp b/test-base/Android.bp index ccf57b00a379..62fed61da276 100644 --- a/test-base/Android.bp +++ b/test-base/Android.bp @@ -83,28 +83,3 @@ java_library_static { "junit", ], } - -// Build the legacy-android-test library -// ===================================== -// This contains the android.test classes that were in Android API level 25, -// including those from android.test.runner. -// Also contains the com.android.internal.util.Predicate[s] classes. -java_library_static { - name: "legacy-android-test", - - srcs: [ - "src/android/**/*.java", - "src/com/**/*.java", - ], - - static_libs: [ - "android.test.runner-minus-junit", - "android.test.mock", - ], - - no_framework_libs: true, - libs: [ - "framework", - "junit", - ], -} diff --git a/test-legacy/Android.bp b/test-legacy/Android.bp new file mode 100644 index 000000000000..d2af8a9f1c82 --- /dev/null +++ b/test-legacy/Android.bp @@ -0,0 +1,36 @@ +// +// Copyright (C) 2018 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +// Build the legacy-android-test library +// ===================================== +// This contains the android.test classes that were in Android API level 25, +// including those from android.test.runner. +// Also contains the com.android.internal.util.Predicate[s] classes. +java_library_static { + name: "legacy-android-test", + + static_libs: [ + "android.test.base-minus-junit", + "android.test.runner-minus-junit", + "android.test.mock", + ], + + no_framework_libs: true, + libs: [ + "framework", + "junit", + ], +} diff --git a/test-legacy/Android.mk b/test-legacy/Android.mk new file mode 100644 index 000000000000..b8c53266b9f8 --- /dev/null +++ b/test-legacy/Android.mk @@ -0,0 +1,40 @@ +# +# Copyright (C) 2018 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +LOCAL_PATH:= $(call my-dir) + +# For unbundled build we'll use the prebuilt jar from prebuilts/sdk. +ifeq (,$(TARGET_BUILD_APPS)$(filter true,$(TARGET_BUILD_PDK))) + +# Build the android.test.legacy library +# ===================================== +include $(CLEAR_VARS) + +LOCAL_MODULE := android.test.legacy + +LOCAL_SDK_VERSION := current + +LOCAL_JAVA_LIBRARIES := junit +LOCAL_STATIC_JAVA_LIBRARIES := \ + android.test.base-minus-junit \ + android.test.runner-minus-junit \ + +include $(BUILD_STATIC_JAVA_LIBRARY) + +# Archive a copy of the classes.jar in SDK build. +$(call dist-for-goals,sdk win_sdk,$(full_classes_jar):android.test.legacy.jar) + +endif # not TARGET_BUILD_APPS not TARGET_BUILD_PDK=true diff --git a/test-runner/Android.mk b/test-runner/Android.mk index 229a6ac05bb1..706f6364ef8d 100644 --- a/test-runner/Android.mk +++ b/test-runner/Android.mk @@ -117,23 +117,5 @@ update-android-test-runner-api: $(ANDROID_TEST_RUNNER_OUTPUT_API_FILE) | $(ACP) endif # not TARGET_BUILD_APPS not TARGET_BUILD_PDK=true -# Build the android.test.legacy library -# ===================================== -include $(CLEAR_VARS) - -LOCAL_MODULE := android.test.legacy - -LOCAL_SRC_FILES := $(call all-java-files-under, src/android) - -LOCAL_SDK_VERSION := current - -LOCAL_JAVA_LIBRARIES := android.test.mock.stubs junit -LOCAL_STATIC_JAVA_LIBRARIES := android.test.base-minus-junit - -include $(BUILD_STATIC_JAVA_LIBRARY) - -# Archive a copy of the classes.jar in SDK build. -$(call dist-for-goals,sdk win_sdk,$(full_classes_jar):android.test.legacy.jar) - # additionally, build unit tests in a separate .apk include $(call all-makefiles-under,$(LOCAL_PATH)) diff --git a/tests/net/Android.mk b/tests/net/Android.mk index 994f3ccd7560..9130e7db80a1 100644 --- a/tests/net/Android.mk +++ b/tests/net/Android.mk @@ -39,6 +39,7 @@ LOCAL_JNI_SHARED_LIBRARIES := \ libc++ \ libcrypto \ libcutils \ + libdexfile \ libframeworksnettestsjni \ libhidl-gen-utils \ libhidlbase \ diff --git a/tests/net/java/com/android/server/NetworkManagementServiceTest.java b/tests/net/java/com/android/server/NetworkManagementServiceTest.java index 2ac73dbd4f52..56a075be4722 100644 --- a/tests/net/java/com/android/server/NetworkManagementServiceTest.java +++ b/tests/net/java/com/android/server/NetworkManagementServiceTest.java @@ -99,8 +99,12 @@ public class NetworkManagementServiceTest { @After public void tearDown() throws Exception { - if (mSocket != null) mSocket.close(); - if (mServerSocket != null) mServerSocket.close(); + mNMService.shutdown(); + // Once NetworkManagementService#shutdown() actually does something and shutdowns + // the underlying NativeDaemonConnector, the block below should be uncommented. + // if (mOutputStream != null) mOutputStream.close(); + // if (mSocket != null) mSocket.close(); + // if (mServerSocket != null) mServerSocket.close(); } /** diff --git a/tests/net/java/com/android/server/connectivity/IpConnectivityMetricsTest.java b/tests/net/java/com/android/server/connectivity/IpConnectivityMetricsTest.java index 9f2cb921ea8e..8359fe2a8fb4 100644 --- a/tests/net/java/com/android/server/connectivity/IpConnectivityMetricsTest.java +++ b/tests/net/java/com/android/server/connectivity/IpConnectivityMetricsTest.java @@ -66,7 +66,6 @@ import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.junit.Before; -import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; @@ -175,7 +174,6 @@ public class IpConnectivityMetricsTest { } @Test - @Ignore public void testDefaultNetworkEvents() throws Exception { final long cell = BitUtils.packBits(new int[]{NetworkCapabilities.TRANSPORT_CELLULAR}); final long wifi = BitUtils.packBits(new int[]{NetworkCapabilities.TRANSPORT_WIFI}); @@ -294,7 +292,6 @@ public class IpConnectivityMetricsTest { } @Test - @Ignore public void testEndToEndLogging() throws Exception { // TODO: instead of comparing textpb to textpb, parse textpb and compare proto to proto. IpConnectivityLog logger = new IpConnectivityLog(mService.impl); @@ -635,6 +632,7 @@ public class IpConnectivityMetricsTest { when(nai.getCurrentScore()).thenReturn(score); nai.linkProperties = new LinkProperties(); nai.networkCapabilities = new NetworkCapabilities(); + nai.lastValidated = true; for (int t : BitUtils.unpackBits(transports)) { nai.networkCapabilities.addTransportType(t); } diff --git a/tools/aapt2/Resources.proto b/tools/aapt2/Resources.proto index 069360e3d6f3..df483b2bdb1b 100644 --- a/tools/aapt2/Resources.proto +++ b/tools/aapt2/Resources.proto @@ -318,7 +318,7 @@ message Primitive { float dimension_value = 4; float fraction_value = 5; int32 int_decimal_value = 6; - uint32 int_hexidecimal_value = 7; + uint32 int_hexadecimal_value = 7; bool boolean_value = 8; uint32 color_argb8_value = 9; uint32 color_rgb8_value = 10; diff --git a/tools/aapt2/format/proto/ProtoDeserialize.cpp b/tools/aapt2/format/proto/ProtoDeserialize.cpp index 81bc2c88939e..f1eb95289053 100644 --- a/tools/aapt2/format/proto/ProtoDeserialize.cpp +++ b/tools/aapt2/format/proto/ProtoDeserialize.cpp @@ -792,9 +792,9 @@ std::unique_ptr<Item> DeserializeItemFromPb(const pb::Item& pb_item, val.dataType = android::Res_value::TYPE_INT_DEC; val.data = static_cast<uint32_t>(pb_prim.int_decimal_value()); } break; - case pb::Primitive::kIntHexidecimalValue: { + case pb::Primitive::kIntHexadecimalValue: { val.dataType = android::Res_value::TYPE_INT_HEX; - val.data = pb_prim.int_hexidecimal_value(); + val.data = pb_prim.int_hexadecimal_value(); } break; case pb::Primitive::kBooleanValue: { val.dataType = android::Res_value::TYPE_INT_BOOLEAN; diff --git a/tools/aapt2/format/proto/ProtoSerialize.cpp b/tools/aapt2/format/proto/ProtoSerialize.cpp index e9622f54f6ae..1d00852eab73 100644 --- a/tools/aapt2/format/proto/ProtoSerialize.cpp +++ b/tools/aapt2/format/proto/ProtoSerialize.cpp @@ -460,7 +460,7 @@ class ValueSerializer : public ConstValueVisitor { pb_prim->set_int_decimal_value(static_cast<int32_t>(val.data)); } break; case android::Res_value::TYPE_INT_HEX: { - pb_prim->set_int_hexidecimal_value(val.data); + pb_prim->set_int_hexadecimal_value(val.data); } break; case android::Res_value::TYPE_INT_BOOLEAN: { pb_prim->set_boolean_value(static_cast<bool>(val.data)); diff --git a/wifi/java/android/net/wifi/IWifiManager.aidl b/wifi/java/android/net/wifi/IWifiManager.aidl index 2a2ff0cf3a6f..309bc80b8864 100644 --- a/wifi/java/android/net/wifi/IWifiManager.aidl +++ b/wifi/java/android/net/wifi/IWifiManager.aidl @@ -158,9 +158,6 @@ interface IWifiManager int getVerboseLoggingLevel(); - void enableAggressiveHandover(int enabled); - int getAggressiveHandover(); - void enableWifiConnectivityManager(boolean enabled); void disableEphemeralNetwork(String SSID, String packageName); diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java index 13b7c1ad6dae..897b1eaa2a64 100644 --- a/wifi/java/android/net/wifi/WifiManager.java +++ b/wifi/java/android/net/wifi/WifiManager.java @@ -3532,31 +3532,6 @@ public class WifiManager { } /** - * Set wifi Aggressive Handover. Called from developer settings. - * @hide - */ - public void enableAggressiveHandover(int enabled) { - try { - mService.enableAggressiveHandover(enabled); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } - } - - /** - * Get the WiFi Handover aggressiveness.This is used by settings - * to decide what to show within the picker. - * @hide - */ - public int getAggressiveHandover() { - try { - return mService.getAggressiveHandover(); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } - } - - /** * Removes all saved wifi networks. * * @hide |