Merge "Changed dataset methods that take a Pattern filter to accept null."
diff --git a/Android.bp b/Android.bp
index d0bee28..129d676 100644
--- a/Android.bp
+++ b/Android.bp
@@ -319,6 +319,10 @@
"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",
@@ -483,16 +487,19 @@
"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",
@@ -510,13 +517,10 @@
"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",
@@ -643,8 +647,10 @@
],
},
- // 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",
],
@@ -697,6 +703,18 @@
],
}
+// 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/Android.mk b/Android.mk
index 35b5f92..d72e398 100644
--- a/Android.mk
+++ b/Android.mk
@@ -821,6 +821,8 @@
LOCAL_SRC_FILES := \
$(call all-proto-files-under, core/proto) \
$(call all-proto-files-under, libs/incident/proto/android/os)
+# Protos have lots of MissingOverride and similar.
+LOCAL_ERROR_PRONE_FLAGS := -XepDisableAllChecks
include $(BUILD_STATIC_JAVA_LIBRARY)
# ==== hiddenapi lists =======================================
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 0000000..fc6302e
--- /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 0000000..98f2bd5
--- /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 682885b..bab2a85 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 @@
@LargeTest
@RunWith(AndroidJUnit4.class)
public class StaticLayoutPerfTest {
+ 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 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;
-
- 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;
- }
+ 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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
@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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 0000000..dccb34b
--- /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 7a54e6d..63db312 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -94,6 +94,7 @@
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";
@@ -6497,7 +6498,7 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 {
@@ -16338,7 +16374,7 @@
method public void addSurface(android.view.Surface);
method public int describeContents();
method public void enableSurfaceSharing();
- method public static int getMaxSharedSurfaceCount();
+ method public int getMaxSharedSurfaceCount();
method public android.view.Surface getSurface();
method public int getSurfaceGroupId();
method public java.util.List<android.view.Surface> getSurfaces();
@@ -23287,6 +23323,7 @@
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 @@
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);
@@ -38159,6 +38198,37 @@
method public java.security.KeyPair getKeyPair();
}
+ public class ConfirmationAlreadyPresentingException extends java.lang.Exception {
+ ctor public ConfirmationAlreadyPresentingException();
+ ctor public ConfirmationAlreadyPresentingException(java.lang.String);
+ }
+
+ public abstract class ConfirmationCallback {
+ ctor public ConfirmationCallback();
+ method public void onConfirmedByUser(byte[]);
+ method public void onDismissedByApplication();
+ method public void onDismissedByUser();
+ method public void onError(java.lang.Exception);
+ }
+
+ public class ConfirmationDialog {
+ method public void cancelPrompt();
+ method public static boolean isSupported();
+ method public void presentPrompt(java.util.concurrent.Executor, android.security.ConfirmationCallback) throws android.security.ConfirmationAlreadyPresentingException, android.security.ConfirmationNotAvailableException;
+ }
+
+ public static class ConfirmationDialog.Builder {
+ ctor public ConfirmationDialog.Builder();
+ method public android.security.ConfirmationDialog build(android.content.Context);
+ method public android.security.ConfirmationDialog.Builder setExtraData(byte[]);
+ method public android.security.ConfirmationDialog.Builder setPromptText(java.lang.CharSequence);
+ }
+
+ public class ConfirmationNotAvailableException extends java.lang.Exception {
+ ctor public ConfirmationNotAvailableException();
+ ctor public ConfirmationNotAvailableException(java.lang.String);
+ }
+
public final class KeyChain {
ctor public KeyChain();
method public static void choosePrivateKeyAlias(android.app.Activity, android.security.KeyChainAliasCallback, java.lang.String[], java.security.Principal[], java.lang.String, int, java.lang.String);
@@ -38268,6 +38338,7 @@
method public boolean isTrustedUserPresenceRequired();
method public boolean isUserAuthenticationRequired();
method public boolean isUserAuthenticationValidWhileOnBody();
+ method public boolean isUserConfirmationRequired();
}
public static final class KeyGenParameterSpec.Builder {
@@ -38295,6 +38366,7 @@
method public android.security.keystore.KeyGenParameterSpec.Builder setUserAuthenticationRequired(boolean);
method public android.security.keystore.KeyGenParameterSpec.Builder setUserAuthenticationValidWhileOnBody(boolean);
method public android.security.keystore.KeyGenParameterSpec.Builder setUserAuthenticationValidityDurationSeconds(int);
+ method public android.security.keystore.KeyGenParameterSpec.Builder setUserConfirmationRequired(boolean);
}
public class KeyInfo implements java.security.spec.KeySpec {
@@ -38316,6 +38388,7 @@
method public boolean isUserAuthenticationRequired();
method public boolean isUserAuthenticationRequirementEnforcedBySecureHardware();
method public boolean isUserAuthenticationValidWhileOnBody();
+ method public boolean isUserConfirmationRequired();
}
public class KeyNotYetValidException extends java.security.InvalidKeyException {
@@ -38383,6 +38456,7 @@
method public boolean isRandomizedEncryptionRequired();
method public boolean isUserAuthenticationRequired();
method public boolean isUserAuthenticationValidWhileOnBody();
+ method public boolean isUserConfirmationRequired();
}
public static final class KeyProtection.Builder {
@@ -38401,6 +38475,7 @@
method public android.security.keystore.KeyProtection.Builder setUserAuthenticationRequired(boolean);
method public android.security.keystore.KeyProtection.Builder setUserAuthenticationValidWhileOnBody(boolean);
method public android.security.keystore.KeyProtection.Builder setUserAuthenticationValidityDurationSeconds(int);
+ method public android.security.keystore.KeyProtection.Builder setUserConfirmationRequired(boolean);
}
public class StrongBoxUnavailableException extends java.security.ProviderException {
@@ -38498,6 +38573,20 @@
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 +38756,7 @@
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 +38766,7 @@
}
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 +44378,11 @@
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 +49623,7 @@
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 +50079,8 @@
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 +50094,8 @@
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 +50136,7 @@
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 +50166,42 @@
}
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 +50212,22 @@
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 +50248,75 @@
}
+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 +52441,7 @@
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 {
@@ -57399,6 +57588,7 @@
method public boolean enqueue();
method public T get();
method public boolean isEnqueued();
+ method public static void reachabilityFence(java.lang.Object);
}
public class ReferenceQueue<T> {
@@ -70421,7 +70611,6 @@
public class ExemptionMechanism {
ctor protected ExemptionMechanism(javax.crypto.ExemptionMechanismSpi, java.security.Provider, java.lang.String);
- method protected void finalize();
method public final byte[] genExemptionBlob() throws javax.crypto.ExemptionMechanismException, java.lang.IllegalStateException;
method public final int genExemptionBlob(byte[]) throws javax.crypto.ExemptionMechanismException, java.lang.IllegalStateException, javax.crypto.ShortBufferException;
method public final int genExemptionBlob(byte[], int) throws javax.crypto.ExemptionMechanismException, java.lang.IllegalStateException, javax.crypto.ShortBufferException;
diff --git a/api/system-current.txt b/api/system-current.txt
index 7109b5d..034ee30 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -29,6 +29,7 @@
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 @@
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;
}
@@ -4452,6 +4458,24 @@
}
+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 {
@@ -5089,16 +5113,669 @@
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.internal.stub {
+package android.telephony.ims.feature {
- public class SmsImplBase {
- ctor public SmsImplBase();
+ 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.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 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();
@@ -5117,6 +5794,29 @@
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 4e8f904..d834cf7 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -565,6 +565,14 @@
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 @@
}
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 d1af71d..54785ca 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 @@
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 44adaec..3f0e331 100644
--- a/cmds/incidentd/src/Privacy.cpp
+++ b/cmds/incidentd/src/Privacy.cpp
@@ -65,7 +65,7 @@
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 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 9e15ff4..4f3db67 100644
--- a/cmds/incidentd/src/Privacy.h
+++ b/cmds/incidentd/src/Privacy.h
@@ -65,8 +65,6 @@
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 @@
// 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 bd559d6..b9f479b 100644
--- a/cmds/incidentd/src/Reporter.cpp
+++ b/cmds/incidentd/src/Reporter.cpp
@@ -64,7 +64,8 @@
ReportRequestSet::ReportRequestSet()
:mRequests(),
mSections(),
- mMainFd(-1)
+ mMainFd(-1),
+ mMainDest(-1)
{
}
@@ -86,6 +87,12 @@
mMainFd = fd;
}
+void
+ReportRequestSet::setMainDest(int dest)
+{
+ mMainDest = dest;
+}
+
bool
ReportRequestSet::containsSection(int id) {
return mSections.containsSection(id);
@@ -125,12 +132,14 @@
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 @@
// 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 2615c62..f30ecf0 100644
--- a/cmds/incidentd/src/Reporter.h
+++ b/cmds/incidentd/src/Reporter.h
@@ -53,6 +53,7 @@
void add(const sp<ReportRequest>& request);
void setMainFd(int fd);
+ void setMainDest(int dest);
typedef vector<sp<ReportRequest>>::iterator iterator;
@@ -61,10 +62,12 @@
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 0827785..faeab87 100644
--- a/cmds/incidentd/src/Section.cpp
+++ b/cmds/incidentd/src/Section.cpp
@@ -152,36 +152,40 @@
// 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 @@
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 32b9e42..c7bfe55 100644
--- a/cmds/incidentd/tests/PrivacyBuffer_test.cpp
+++ b/cmds/incidentd/tests/PrivacyBuffer_test.cpp
@@ -73,7 +73,7 @@
}
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 @@
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 565b092..eabbb96 100644
--- a/cmds/statsd/Android.mk
+++ b/cmds/statsd/Android.mk
@@ -137,7 +137,7 @@
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 288ebe9..857a6dd 100644
--- a/cmds/statsd/src/HashableDimensionKey.cpp
+++ b/cmds/statsd/src/HashableDimensionKey.cpp
@@ -69,18 +69,17 @@
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 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 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 edc9f2c..a4066aa 100644
--- a/cmds/statsd/src/StatsLogProcessor.cpp
+++ b/cmds/statsd/src/StatsLogProcessor.cpp
@@ -91,23 +91,22 @@
}
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 @@
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 fb85aa8..c19ff63 100644
--- a/cmds/statsd/src/StatsLogProcessor.h
+++ b/cmds/statsd/src/StatsLogProcessor.h
@@ -61,6 +61,8 @@
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 31994e1..f545bb0 100644
--- a/cmds/statsd/src/StatsService.cpp
+++ b/cmds/statsd/src/StatsService.cpp
@@ -175,8 +175,13 @@
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 @@
/**
* 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 ba6bd24..be20893 100644
--- a/cmds/statsd/src/StatsService.h
+++ b/cmds/statsd/src/StatsService.h
@@ -140,7 +140,7 @@
/**
* 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 27fa672..4e570a6 100644
--- a/cmds/statsd/src/atoms.proto
+++ b/cmds/statsd/src/atoms.proto
@@ -93,6 +93,8 @@
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 @@
}
/**
+ * 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 @@
// # 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 @@
// # 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 @@
// # 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 7a1bb0c..5cfc349 100644
--- a/cmds/statsd/src/condition/SimpleConditionTracker.cpp
+++ b/cmds/statsd/src/condition/SimpleConditionTracker.cpp
@@ -289,7 +289,8 @@
}
// 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 ddfb8d1..3b2d480 100644
--- a/cmds/statsd/src/condition/condition_util.cpp
+++ b/cmds/statsd/src/condition/condition_util.cpp
@@ -116,28 +116,30 @@
}
}
-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 @@
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 @@
}
}
-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 598027b..a7288be 100644
--- a/cmds/statsd/src/condition/condition_util.h
+++ b/cmds/statsd/src/condition/condition_util.h
@@ -40,8 +40,9 @@
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 bb7a044..04445ca 100644
--- a/cmds/statsd/src/dimension.cpp
+++ b/cmds/statsd/src/dimension.cpp
@@ -38,7 +38,7 @@
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 @@
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 @@
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 @@
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 @@
{
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 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 @@
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 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 d0f96a2..e900c5e 100644
--- a/cmds/statsd/src/dimension.h
+++ b/cmds/statsd/src/dimension.h
@@ -33,8 +33,7 @@
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 @@
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 4ff4f74..acf64fe 100644
--- a/cmds/statsd/src/field_util.cpp
+++ b/cmds/statsd/src/field_util.cpp
@@ -102,24 +102,13 @@
}
}
-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 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 @@
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 @@
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 @@
{
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 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(
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 a4dfddd..b04465d 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 @@
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 @@
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 63bde7d..77f5456 100644
--- a/cmds/statsd/src/guardrail/StatsdStats.cpp
+++ b/cmds/statsd/src/guardrail/StatsdStats.cpp
@@ -45,6 +45,8 @@
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_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 @@
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 @@
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 @@
"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 @@
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 7cb48ea..1f4bfa6 100644
--- a/cmds/statsd/src/guardrail/StatsdStats.h
+++ b/cmds/statsd/src/guardrail/StatsdStats.h
@@ -49,6 +49,8 @@
// 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 @@
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 @@
// 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 1ca793c..9e72f5b 100644
--- a/cmds/statsd/src/logd/LogEvent.cpp
+++ b/cmds/statsd/src/logd/LogEvent.cpp
@@ -198,7 +198,8 @@
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 @@
// 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 @@
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::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 5a4efd4..eb2c008 100644
--- a/cmds/statsd/src/logd/LogEvent.h
+++ b/cmds/statsd/src/logd/LogEvent.h
@@ -92,7 +92,6 @@
* 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 5d43ef3..0fe896b 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 @@
// 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 48f62e7..b6f440f 100644
--- a/cmds/statsd/src/matchers/matcher_util.cpp
+++ b/cmds/statsd/src/matchers/matcher_util.cpp
@@ -93,25 +93,28 @@
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 @@
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 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,30 +200,30 @@
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;
- for (auto it = itLower; it != itUpper; ++it) {
- int index = getPositionByReferenceField(field, it->first);
- if (index >= 0) {
- indexes.insert(index);
- }
- }
bool matched = false;
- for (const int index : indexes) {
- Field any_field = field;
- setPositionForLeaf(&any_field, index);
- matched |= matchesNonRepeatedField(uidMap, fieldMap, matcher, any_field);
+ for (auto it = itLower; it != itUpper; ++it) {
+ int index = getPositionByReferenceField(*rootField, it->first);
+ if (index >= 0) {
+ leafField->set_position_index(index);
+ matched |= matchesNonRepeatedField(uidMap, fieldMap, matcher, rootField, leafField);
+ leafField->clear_position_index();
+ if (matched) {
+ break;
+ }
+ }
}
return matched;
}
@@ -226,14 +235,16 @@
}
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 @@
*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 704cb4c..a45a9fb 100644
--- a/cmds/statsd/src/matchers/matcher_util.h
+++ b/cmds/statsd/src/matchers/matcher_util.h
@@ -42,13 +42,11 @@
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 16fc7ee..061b7a3 100644
--- a/cmds/statsd/src/metrics/CountMetricProducer.h
+++ b/cmds/statsd/src/metrics/CountMetricProducer.h
@@ -68,6 +68,8 @@
// 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 e26fe56..000874c 100644
--- a/cmds/statsd/src/metrics/DurationMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/DurationMetricProducer.cpp
@@ -233,6 +233,21 @@
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 @@
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 e06b9a1..d8cab92 100644
--- a/cmds/statsd/src/metrics/DurationMetricProducer.h
+++ b/cmds/statsd/src/metrics/DurationMetricProducer.h
@@ -68,6 +68,8 @@
// 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 a57b07d..9da0dd0 100644
--- a/cmds/statsd/src/metrics/EventMetricProducer.h
+++ b/cmds/statsd/src/metrics/EventMetricProducer.h
@@ -62,6 +62,8 @@
// 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 24dc5b0..1072c5a 100644
--- a/cmds/statsd/src/metrics/GaugeMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/GaugeMetricProducer.cpp
@@ -58,6 +58,7 @@
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 @@
: 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 @@
}
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 @@
}
// 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 @@
(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 @@
if (mPullTagId == -1) {
return;
}
- // No need to pull again. Either scheduled pull or condition on true happened
- if (!mCondition) {
+
+ 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;
+ }
+ if (!triggerPuller) {
return;
}
- // Already have gauge metric for the current bucket, do not do it again.
- if (mCurrentSlicedBucket->size() > 0) {
- return;
- }
+
vector<std::shared_ptr<LogEvent>> allData;
if (!mStatsPullerManager->Pull(mPullTagId, &allData)) {
ALOGE("Stats puller failed for tag: %d", mPullTagId);
@@ -257,20 +282,24 @@
}
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 @@
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 @@
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 @@
}
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 f267e98..6c01347 100644
--- a/cmds/statsd/src/metrics/GaugeMetricProducer.h
+++ b/cmds/statsd/src/metrics/GaugeMetricProducer.h
@@ -32,15 +32,20 @@
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 @@
// 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 @@
// 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 @@
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 @@
// 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 d620a7e..e74924a 100644
--- a/cmds/statsd/src/metrics/MetricProducer.cpp
+++ b/cmds/statsd/src/metrics/MetricProducer.cpp
@@ -32,8 +32,7 @@
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 @@
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 3779c44..6f33073 100644
--- a/cmds/statsd/src/metrics/MetricProducer.h
+++ b/cmds/statsd/src/metrics/MetricProducer.h
@@ -96,6 +96,11 @@
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 @@
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 f929517..d0737de 100644
--- a/cmds/statsd/src/metrics/MetricsManager.cpp
+++ b/cmds/statsd/src/metrics/MetricsManager.cpp
@@ -154,6 +154,20 @@
}
}
+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 a0239fc..9cdbafc 100644
--- a/cmds/statsd/src/metrics/MetricsManager.h
+++ b/cmds/statsd/src/metrics/MetricsManager.h
@@ -61,6 +61,8 @@
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 @@
// 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 3e7032d..9f750cf 100644
--- a/cmds/statsd/src/metrics/ValueMetricProducer.h
+++ b/cmds/statsd/src/metrics/ValueMetricProducer.h
@@ -67,6 +67,8 @@
// 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 371460e..c2d2cea 100644
--- a/cmds/statsd/src/metrics/duration_helper/DurationTracker.h
+++ b/cmds/statsd/src/metrics/duration_helper/DurationTracker.h
@@ -97,6 +97,8 @@
// 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 0c99391..412a0c9 100644
--- a/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.cpp
+++ b/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.cpp
@@ -291,6 +291,11 @@
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 5d3c158..661d131 100644
--- a/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.h
+++ b/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.h
@@ -48,9 +48,10 @@
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 6bf4228..75d7c08 100644
--- a/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.cpp
+++ b/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.cpp
@@ -314,6 +314,12 @@
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 638b7ad..43469ca 100644
--- a/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.h
+++ b/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.h
@@ -48,6 +48,7 @@
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 @@
// 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 f73c4a5..af21ca4 100644
--- a/cmds/statsd/src/stats_log.proto
+++ b/cmds/statsd/src/stats_log.proto
@@ -100,7 +100,9 @@
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 @@
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 07bbcb2..2ea79a6 100644
--- a/cmds/statsd/src/statsd_config.proto
+++ b/cmds/statsd/src/statsd_config.proto
@@ -222,6 +222,12 @@
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 e56a6c5..a80fdc5 100644
--- a/cmds/statsd/tests/e2e/GaugeMetric_e2e_test.cpp
+++ b/cmds/statsd/tests/e2e/GaugeMetric_e2e_test.cpp
@@ -153,23 +153,26 @@
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 @@
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 897328d..4ad2097 100644
--- a/cmds/statsd/tests/metrics/CountMetricProducer_test.cpp
+++ b/cmds/statsd/tests/metrics/CountMetricProducer_test.cpp
@@ -149,8 +149,8 @@
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 34cde60..da00cae 100644
--- a/cmds/statsd/tests/metrics/EventMetricProducer_test.cpp
+++ b/cmds/statsd/tests/metrics/EventMetricProducer_test.cpp
@@ -98,8 +98,8 @@
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 82772d8..4533ac6 100644
--- a/cmds/statsd/tests/metrics/GaugeMetricProducer_test.cpp
+++ b/cmds/statsd/tests/metrics/GaugeMetricProducer_test.cpp
@@ -78,7 +78,7 @@
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 @@
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 @@
// 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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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/cmds/statsd/tools/dogfood/Android.mk b/cmds/statsd/tools/dogfood/Android.mk
index 32a85b1..a65095f 100644
--- a/cmds/statsd/tools/dogfood/Android.mk
+++ b/cmds/statsd/tools/dogfood/Android.mk
@@ -27,6 +27,7 @@
LOCAL_PROTOC_OPTIMIZE_TYPE := lite
LOCAL_PRIVILEGED_MODULE := true
LOCAL_DEX_PREOPT := false
+LOCAL_CERTIFICATE := platform
LOCAL_PROGUARD_ENABLED := disabled
include $(BUILD_PACKAGE)
diff --git a/cmds/statsd/tools/dogfood/AndroidManifest.xml b/cmds/statsd/tools/dogfood/AndroidManifest.xml
index 7bfde40..cd76c9d 100644
--- a/cmds/statsd/tools/dogfood/AndroidManifest.xml
+++ b/cmds/statsd/tools/dogfood/AndroidManifest.xml
@@ -18,6 +18,7 @@
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.statsd.dogfood"
+ android:sharedUserId="android.uid.system"
android:versionCode="1"
android:versionName="1.0" >
diff --git a/cmds/statsd/tools/dogfood/res/raw/statsd_baseline_config b/cmds/statsd/tools/dogfood/res/raw/statsd_baseline_config
index c1c3914..d050061 100644
--- a/cmds/statsd/tools/dogfood/res/raw/statsd_baseline_config
+++ b/cmds/statsd/tools/dogfood/res/raw/statsd_baseline_config
Binary files differ
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index fd7ad88..7bfb20f 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 @@
* 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 @@
* @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 @@
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 @@
mHandler.getLooper().dump(new PrintWriterPrinter(writer), prefix);
- final AutofillManager afm = mAutofillManager;
+ final AutofillManager afm = getAutofillManager();
if (afm != null) {
afm.dump(prefix, writer);
} else {
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index 4d5ac6f..e2ce8b1 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -449,31 +449,6 @@
*/
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 3dedeea..e8535cd1 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -389,7 +389,7 @@
ActivityInfo activityInfo;
CompatibilityInfo compatInfo;
- public LoadedApk loadedApk;
+ public LoadedApk packageInfo;
List<ResultInfo> pendingResults;
List<ReferrerIntent> pendingIntents;
@@ -432,7 +432,7 @@
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 @@
}
static final class AppBindData {
- LoadedApk loadedApk;
+ LoadedApk info;
String processName;
ApplicationInfo appInfo;
List<ProviderInfo> providers;
@@ -1913,13 +1913,13 @@
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 @@
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 @@
+ mBoundApplication.processName
+ "/" + mBoundApplication.appInfo.uid);
}
- return loadedApk;
+ return packageInfo;
}
}
@@ -1961,13 +1961,13 @@
}
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 @@
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 @@
}
}
- 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 @@
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 @@
/** 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 @@
}
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 @@
}
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 @@
// 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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
// 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 @@
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 @@
}
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 @@
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 @@
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 @@
}
}
- return !r.activity.mFinished && saveState ? r.state : null;
+ return shouldSaveState ? r.state : null;
}
private void performPauseActivityIfNeeded(ActivityClientRecord r, String reason) {
@@ -4149,32 +4151,42 @@
}
}
- // Next have the activity save its current state and managed dialogs...
- if (!r.activity.mFinished && saveState) {
- if (r.state == null) {
- callCallActivityOnSaveInstanceState(r);
- }
- }
-
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 "
- + 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);
+ callActivityOnStop(r, saveState, reason);
}
}
}
+ /**
+ * 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);
+ }
+
private void updateVisibility(ActivityClientRecord r, boolean show) {
View v = r.activity.mDecor;
if (v != null) {
@@ -4292,24 +4304,7 @@
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 @@
}
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 @@
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 @@
// 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 @@
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 @@
}
}
- 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 @@
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 @@
}
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 @@
}
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 @@
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 @@
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 @@
break;
}
}
- ApplicationPackageManager.handlePackageBroadcast(cmd, packages, hasLoadedApk);
+ ApplicationPackageManager.handlePackageBroadcast(cmd, packages, hasPkgInfo);
}
final void handleLowMemory() {
@@ -5636,10 +5616,10 @@
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 @@
// 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 @@
}
} 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 @@
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 @@
}
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 @@
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 @@
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 @@
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 @@
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 5a739ea..a13ac49 100644
--- a/core/java/android/app/Application.java
+++ b/core/java/android/app/Application.java
@@ -197,7 +197,7 @@
*/
/* 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 cc68c05..6b0a2f9 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -1409,7 +1409,7 @@
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 0f66652..5b61fdf 100644
--- a/core/java/android/app/ClientTransactionHandler.java
+++ b/core/java/android/app/ClientTransactionHandler.java
@@ -111,7 +111,7 @@
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 ea94042..6496110 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -159,7 +159,7 @@
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 @@
@Override
public Context getApplicationContext() {
- return (mLoadedApk != null) ?
- mLoadedApk.getApplication() : mMainThread.getApplication();
+ return (mPackageInfo != null) ?
+ mPackageInfo.getApplication() : mMainThread.getApplication();
}
@Override
@@ -302,15 +302,15 @@
@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 @@
@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 @@
// 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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
@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 @@
@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 @@
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 @@
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 @@
}
}
+ 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 @@
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 @@
@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 @@
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 @@
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 @@
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 @@
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 @@
@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 @@
}
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 @@
* 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 @@
}
}
- 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 @@
// 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 @@
}
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 @@
// 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 @@
}
mUser = user;
- mLoadedApk = loadedApk;
+ mPackageInfo = packageInfo;
mSplitName = splitName;
mClassLoader = classLoader;
mResourcesManager = ResourcesManager.getInstance();
@@ -2341,8 +2369,8 @@
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 @@
}
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 @@
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 6dcecf1..02be002 100644
--- a/core/java/android/app/IActivityManager.aidl
+++ b/core/java/android/app/IActivityManager.aidl
@@ -456,11 +456,10 @@
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 f90b276..67f5a2e 100644
--- a/core/java/android/app/Instrumentation.java
+++ b/core/java/android/app/Instrumentation.java
@@ -1216,10 +1216,10 @@
+ " 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 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 @@
.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 d24d4f3..ea5932c 100644
--- a/core/java/android/app/LoadedApk.java
+++ b/core/java/android/app/LoadedApk.java
@@ -32,7 +32,6 @@
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 @@
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 c34f4d9..1d34595 100644
--- a/core/java/android/app/LocalActivityManager.java
+++ b/core/java/android/app/LocalActivityManager.java
@@ -224,8 +224,8 @@
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 0b5b363..6e40986 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -493,7 +493,7 @@
* </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/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 3b4511e..e190fd4 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -6609,15 +6609,81 @@
}
/**
+ * 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 @@
*
* @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 @@
* 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 @@
}
/**
- * 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 @@
/**
* 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 @@
}
/**
+ * 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
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index 7fc31b1..5197de4 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -227,9 +227,9 @@
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);
@@ -413,4 +413,6 @@
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 d3b66d0..08effd9 100644
--- a/core/java/android/app/admin/SecurityLog.java
+++ b/core/java/android/app/admin/SecurityLog.java
@@ -18,6 +18,7 @@
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 @@
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 @@
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 @@
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 39371c7..be62678 100644
--- a/core/java/android/app/admin/SecurityLogTags.logtags
+++ b/core/java/android/app/admin/SecurityLogTags.logtags
@@ -10,3 +10,28 @@
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 8304c1c..073d28c 100644
--- a/core/java/android/app/servertransaction/PendingTransactionActions.java
+++ b/core/java/android/app/servertransaction/PendingTransactionActions.java
@@ -134,7 +134,7 @@
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 5808f8b..126deef 100644
--- a/core/java/android/app/slice/Slice.java
+++ b/core/java/android/app/slice/Slice.java
@@ -146,11 +146,6 @@
*/
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 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 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 00e8cca..336bd478 100644
--- a/core/java/android/app/slice/SliceProvider.java
+++ b/core/java/android/app/slice/SliceProvider.java
@@ -147,6 +147,14 @@
* @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 @@
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 @@
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 @@
}
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 4923171..e02a294 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -7028,7 +7028,7 @@
*
* @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 @@
* @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 @@
* @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 @@
* @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 @@
* @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 @@
* @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 @@
* @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 @@
* @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 @@
* @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 @@
*
* @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 @@
*
* @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 @@
*
* @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 @@
*
* @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 @@
*
* @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 @@
*
* @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 @@
*
* @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 @@
*
* @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 @@
*
* @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 @@
*
* @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 @@
*
* @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 @@
*
* @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 @@
*
* @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 @@
*
* @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 @@
*
* @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 @@
*
* @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 @@
*
* @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 @@
*
* @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 @@
*
* @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 @@
*
* @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 @@
*
* @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 @@
* @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 0000000..2aaac02
--- /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/PackageBackwardCompatibility.java b/core/java/android/content/pm/PackageBackwardCompatibility.java
index 67e4cd5..a16f81b 100644
--- a/core/java/android/content/pm/PackageBackwardCompatibility.java
+++ b/core/java/android/content/pm/PackageBackwardCompatibility.java
@@ -16,6 +16,7 @@
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;
@@ -52,12 +53,22 @@
"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, updaterArray);
+ bootClassPathContainsOAHL, bootClassPathContainsATB, updaterArray);
}
/**
@@ -105,11 +116,14 @@
private final boolean mBootClassPathContainsOAHL;
+ private final boolean mBootClassPathContainsATB;
+
private final PackageSharedLibraryUpdater[] mPackageUpdaters;
public PackageBackwardCompatibility(boolean bootClassPathContainsOAHL,
- PackageSharedLibraryUpdater[] packageUpdaters) {
+ boolean bootClassPathContainsATB, PackageSharedLibraryUpdater[] packageUpdaters) {
this.mBootClassPathContainsOAHL = bootClassPathContainsOAHL;
+ this.mBootClassPathContainsATB = bootClassPathContainsATB;
this.mPackageUpdaters = packageUpdaters;
}
@@ -140,6 +154,14 @@
}
/**
+ * True if the android.test.base is on the bootclasspath, false otherwise.
+ */
+ @VisibleForTesting
+ public static boolean bootClassPathContainsATB() {
+ return INSTANCE.mBootClassPathContainsATB;
+ }
+
+ /**
* Add android.test.mock dependency for any APK that depends on android.test.runner.
*
* <p>This is needed to maintain backwards compatibility as in previous versions of Android the
@@ -173,4 +195,18 @@
}
}
+
+ /**
+ * 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) {
+ removeLibrary(pkg, ANDROID_TEST_BASE);
+ }
+ }
}
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index 3d26af1..2da8937 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -78,8 +78,10 @@
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 @@
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 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 @@
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/SharedLibraryNames.java b/core/java/android/content/pm/SharedLibraryNames.java
index 3fbf664..83e8663 100644
--- a/core/java/android/content/pm/SharedLibraryNames.java
+++ b/core/java/android/content/pm/SharedLibraryNames.java
@@ -22,6 +22,8 @@
*/
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";
diff --git a/core/java/android/content/pm/Signature.java b/core/java/android/content/pm/Signature.java
index fdc54ae..a2a14ed 100644
--- a/core/java/android/content/pm/Signature.java
+++ b/core/java/android/content/pm/Signature.java
@@ -285,6 +285,29 @@
}
/**
+ * 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 f84ec65..a748f4d 100644
--- a/core/java/android/database/CursorWindow.java
+++ b/core/java/android/database/CursorWindow.java
@@ -28,6 +28,7 @@
import android.util.LongSparseArray;
import android.util.SparseIntArray;
+import dalvik.annotation.optimization.FastNative;
import dalvik.system.CloseGuard;
/**
@@ -62,28 +63,43 @@
private static native void nativeDispose(long windowPtr);
private static native void nativeWriteToParcel(long windowPtr, Parcel parcel);
- private static native void nativeClear(long windowPtr);
-
- private static native int nativeGetNumRows(long windowPtr);
- private static native boolean nativeSetNumColumns(long windowPtr, int columnNum);
- private static native boolean nativeAllocRow(long windowPtr);
- private static native void nativeFreeLastRow(long windowPtr);
-
- private static native int nativeGetType(long windowPtr, int row, int column);
+ 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 long nativeGetLong(long windowPtr, int row, int column);
- 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);
+ 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);
+ @FastNative
+ private static native long nativeGetLong(long windowPtr, int row, int column);
+ @FastNative
+ private static native double nativeGetDouble(long windowPtr, 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 7fb0c89..7297426 100644
--- a/core/java/android/hardware/Sensor.java
+++ b/core/java/android/hardware/Sensor.java
@@ -22,7 +22,9 @@
/**
* 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/hardware/camera2/params/OutputConfiguration.java b/core/java/android/hardware/camera2/params/OutputConfiguration.java
index f47cd66..eb4bced 100644
--- a/core/java/android/hardware/camera2/params/OutputConfiguration.java
+++ b/core/java/android/hardware/camera2/params/OutputConfiguration.java
@@ -576,7 +576,7 @@
*
* @see #enableSurfaceSharing
*/
- public static int getMaxSharedSurfaceCount() {
+ public int getMaxSharedSurfaceCount() {
return MAX_SURFACES_COUNT;
}
diff --git a/core/java/android/inputmethodservice/KeyboardView.java b/core/java/android/inputmethodservice/KeyboardView.java
index 13b9206..2306e5f 100644
--- a/core/java/android/inputmethodservice/KeyboardView.java
+++ b/core/java/android/inputmethodservice/KeyboardView.java
@@ -61,6 +61,7 @@
* @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 f3a95b9..a86237d 100644
--- a/core/java/android/os/BatteryManagerInternal.java
+++ b/core/java/android/os/BatteryManagerInternal.java
@@ -24,26 +24,63 @@
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 eb264d6d..4aadc5b 100644
--- a/core/java/android/os/Binder.java
+++ b/core/java/android/os/Binder.java
@@ -21,7 +21,9 @@
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 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 @@
}
}
+ /**
+ * 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/HwBinder.java b/core/java/android/os/HwBinder.java
index 2a088a6..cdee110 100644
--- a/core/java/android/os/HwBinder.java
+++ b/core/java/android/os/HwBinder.java
@@ -87,6 +87,9 @@
* 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 @@
/**
* 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 0c592e1..a565dee 100644
--- a/core/java/android/os/IHwBinder.java
+++ b/core/java/android/os/IHwBinder.java
@@ -30,6 +30,11 @@
/**
* 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 @@
/**
* 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 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 @@
/**
* 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 a2f59a9..1d9e2b0 100644
--- a/core/java/android/os/IHwInterface.java
+++ b/core/java/android/os/IHwInterface.java
@@ -21,7 +21,7 @@
@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 1336c66..9b6d6e5 100644
--- a/core/java/android/os/IncidentManager.java
+++ b/core/java/android/os/IncidentManager.java
@@ -33,10 +33,12 @@
@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 @@
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 fd0ebcf..9fa129c 100644
--- a/core/java/android/os/IncidentReportArgs.java
+++ b/core/java/android/os/IncidentReportArgs.java
@@ -32,6 +32,9 @@
@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 @@
* Construct an incident report args with no fields.
*/
public IncidentReportArgs() {
+ mDest = DEST_AUTO;
}
/**
@@ -143,7 +147,14 @@
* @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/provider/Settings.java b/core/java/android/provider/Settings.java
index 2440b48..1f0d683 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -10520,6 +10520,18 @@
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/ConfirmationAlreadyPresentingException.java b/core/java/android/security/ConfirmationAlreadyPresentingException.java
new file mode 100644
index 0000000..ae4ec5a
--- /dev/null
+++ b/core/java/android/security/ConfirmationAlreadyPresentingException.java
@@ -0,0 +1,29 @@
+/*
+ * 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.security;
+
+/**
+ * This exception is thrown when presenting a prompt fails because another prompt is already
+ * being presented.
+ */
+public class ConfirmationAlreadyPresentingException extends Exception {
+ public ConfirmationAlreadyPresentingException() {}
+
+ public ConfirmationAlreadyPresentingException(String message) {
+ super(message);
+ }
+}
diff --git a/core/java/android/security/ConfirmationCallback.java b/core/java/android/security/ConfirmationCallback.java
new file mode 100644
index 0000000..4670bce
--- /dev/null
+++ b/core/java/android/security/ConfirmationCallback.java
@@ -0,0 +1,54 @@
+/*
+ * 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.security;
+
+import android.annotation.NonNull;
+
+/**
+ * Callback class used when signaling that a prompt is no longer being presented.
+ */
+public abstract class ConfirmationCallback {
+ /**
+ * Called when the requested prompt was accepted by the user.
+ *
+ * The format of 'dataThatWasConfirmed' parameter is a <a href="http://cbor.io/">CBOR</a>
+ * encoded map (type 5) with (at least) the keys <strong>prompt</strong> and
+ * <strong>extra</strong>. The keys are encoded as CBOR text string (type 3). The value of
+ * promptText is encoded as CBOR text string (type 3), and the value of extraData is encoded as
+ * CBOR byte string (type 2). Other keys may be added in the future.
+ *
+ * @param dataThatWasConfirmed the data that was confirmed, see above for the format.
+ */
+ public void onConfirmedByUser(@NonNull byte[] dataThatWasConfirmed) {}
+
+ /**
+ * Called when the requested prompt was dismissed (not accepted) by the user.
+ */
+ public void onDismissedByUser() {}
+
+ /**
+ * Called when the requested prompt was dismissed by the application.
+ */
+ public void onDismissedByApplication() {}
+
+ /**
+ * Called when the requested prompt was dismissed because of a low-level error.
+ *
+ * @param e an exception representing the error.
+ */
+ public void onError(Exception e) {}
+}
diff --git a/core/java/android/security/ConfirmationDialog.java b/core/java/android/security/ConfirmationDialog.java
new file mode 100644
index 0000000..e9df370
--- /dev/null
+++ b/core/java/android/security/ConfirmationDialog.java
@@ -0,0 +1,283 @@
+/*
+ * 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.security;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.text.TextUtils;
+import android.util.Log;
+
+import java.util.Locale;
+import java.util.concurrent.Executor;
+
+/**
+ * Class used for displaying confirmation prompts.
+ *
+ * <p>Confirmation prompts are prompts shown to the user to confirm a given text and are
+ * implemented in a way that a positive response indicates with high confidence that the user has
+ * seen the given text, even if the Android framework (including the kernel) was
+ * compromised. Implementing confirmation prompts with these guarantees requires dedicated
+ * hardware-support and may not always be available.
+ *
+ * <p>Confirmation prompts are typically used with an external entitity - the <i>Relying Party</i> -
+ * in the following way. The setup steps are as follows:
+ * <ul>
+ * <li> Before first use, the application generates a key-pair with the
+ * {@link android.security.keystore.KeyGenParameterSpec.Builder#setUserConfirmationRequired
+ * CONFIRMATION tag} set. Device attestation,
+ * e.g. {@link java.security.KeyStore#getCertificateChain getCertificateChain()}, is used to
+ * generate a certificate chain that includes the public key (<code>Kpub</code> in the following)
+ * of the newly generated key.
+ * <li> The application sends <code>Kpub</code> and the certificate chain resulting from device
+ * attestation to the <i>Relying Party</i>.
+ * <li> The <i>Relying Party</i> validates the certificate chain which involves checking the root
+ * certificate is what is expected (e.g. a certificate from Google), each certificate signs the
+ * next one in the chain, ending with <code>Kpub</code>, and that the attestation certificate
+ * asserts that <code>Kpub</code> has the
+ * {@link android.security.keystore.KeyGenParameterSpec.Builder#setUserConfirmationRequired
+ * CONFIRMATION tag} set.
+ * Additionally the relying party stores <code>Kpub</code> and associates it with the device
+ * it was received from.
+ * </ul>
+ *
+ * <p>The <i>Relying Party</i> is typically an external device (for example connected via
+ * Bluetooth) or application server.
+ *
+ * <p>Before executing a transaction which requires a high assurance of user content, the
+ * application does the following:
+ * <ul>
+ * <li> The application gets a cryptographic nonce from the <i>Relying Party</i> and passes this as
+ * the <code>extraData</code> (via the Builder helper class) to the
+ * {@link #presentPrompt presentPrompt()} method. The <i>Relying Party</i> stores the nonce locally
+ * since it'll use it in a later step.
+ * <li> If the user approves the prompt a <i>Confirmation Response</i> is returned in the
+ * {@link ConfirmationCallback#onConfirmedByUser onConfirmedByUser(byte[])} callback as the
+ * <code>dataThatWasConfirmed</code> parameter. This blob contains the text that was shown to the
+ * user, the <code>extraData</code> parameter, and possibly other data.
+ * <li> The application signs the <i>Confirmation Response</i> with the previously created key and
+ * sends the blob and the signature to the <i>Relying Party</i>.
+ * <li> The <i>Relying Party</i> checks that the signature was made with <code>Kpub</code> and then
+ * extracts <code>promptText</code> matches what is expected and <code>extraData</code> matches the
+ * previously created nonce. If all checks passes, the transaction is executed.
+ * </ul>
+ *
+ * <p>A common way of implementing the "<code>promptText</code> is what is expected" check in the
+ * last bullet, is to have the <i>Relying Party</i> generate <code>promptText</code> and store it
+ * along the nonce in the <code>extraData</code> blob.
+ */
+public class ConfirmationDialog {
+ private static final String TAG = "ConfirmationDialog";
+
+ private CharSequence mPromptText;
+ private byte[] mExtraData;
+ private ConfirmationCallback mCallback;
+ private Executor mExecutor;
+
+ private final KeyStore mKeyStore = KeyStore.getInstance();
+
+ private void doCallback(int responseCode, byte[] dataThatWasConfirmed,
+ ConfirmationCallback callback) {
+ switch (responseCode) {
+ case KeyStore.CONFIRMATIONUI_OK:
+ callback.onConfirmedByUser(dataThatWasConfirmed);
+ break;
+
+ case KeyStore.CONFIRMATIONUI_CANCELED:
+ callback.onDismissedByUser();
+ break;
+
+ case KeyStore.CONFIRMATIONUI_ABORTED:
+ callback.onDismissedByApplication();
+ break;
+
+ case KeyStore.CONFIRMATIONUI_SYSTEM_ERROR:
+ callback.onError(new Exception("System error returned by ConfirmationUI."));
+ break;
+
+ default:
+ callback.onError(new Exception("Unexpected responseCode=" + responseCode
+ + " from onConfirmtionPromptCompleted() callback."));
+ break;
+ }
+ }
+
+ private final android.os.IBinder mCallbackBinder =
+ new android.security.IConfirmationPromptCallback.Stub() {
+ @Override
+ public void onConfirmationPromptCompleted(
+ int responseCode, final byte[] dataThatWasConfirmed)
+ throws android.os.RemoteException {
+ if (mCallback != null) {
+ ConfirmationCallback callback = mCallback;
+ Executor executor = mExecutor;
+ mCallback = null;
+ mExecutor = null;
+ if (executor == null) {
+ doCallback(responseCode, dataThatWasConfirmed, callback);
+ } else {
+ executor.execute(new Runnable() {
+ @Override
+ public void run() {
+ doCallback(responseCode, dataThatWasConfirmed, callback);
+ }
+ });
+ }
+ }
+ }
+ };
+
+ /**
+ * A builder that collects arguments, to be shown on the system-provided confirmation dialog.
+ */
+ public static class Builder {
+
+ private CharSequence mPromptText;
+ private byte[] mExtraData;
+
+ /**
+ * Creates a builder for the confirmation dialog.
+ */
+ public Builder() {
+ }
+
+ /**
+ * Sets the prompt text for the dialog.
+ *
+ * @param promptText the text to present in the prompt.
+ * @return the builder.
+ */
+ public Builder setPromptText(CharSequence promptText) {
+ mPromptText = promptText;
+ return this;
+ }
+
+ /**
+ * Sets the extra data for the dialog.
+ *
+ * @param extraData data to include in the response data.
+ * @return the builder.
+ */
+ public Builder setExtraData(byte[] extraData) {
+ mExtraData = extraData;
+ return this;
+ }
+
+ /**
+ * Creates a {@link ConfirmationDialog} with the arguments supplied to this builder.
+ *
+ * @param context the application context
+ * @return a {@link ConfirmationDialog}
+ * @throws IllegalArgumentException if any of the required fields are not set.
+ */
+ public ConfirmationDialog build(Context context) {
+ if (TextUtils.isEmpty(mPromptText)) {
+ throw new IllegalArgumentException("prompt text must be set and non-empty");
+ }
+ if (mExtraData == null) {
+ throw new IllegalArgumentException("extraData must be set");
+ }
+ return new ConfirmationDialog(mPromptText, mExtraData);
+ }
+ }
+
+ private ConfirmationDialog(CharSequence promptText, byte[] extraData) {
+ mPromptText = promptText;
+ mExtraData = extraData;
+ }
+
+ /**
+ * Requests a confirmation prompt to be presented to the user.
+ *
+ * When the prompt is no longer being presented, one of the methods in
+ * {@link ConfirmationCallback} is called on the supplied callback object.
+ *
+ * @param executor the executor identifying the thread that will receive the callback.
+ * @param callback the callback to use when the dialog is done showing.
+ * @throws IllegalArgumentException if the prompt text is too long or malfomed.
+ * @throws ConfirmationAlreadyPresentingException if another prompt is being presented.
+ * @throws ConfirmationNotAvailableException if confirmation prompts are not supported.
+ */
+ public void presentPrompt(@NonNull Executor executor, @NonNull ConfirmationCallback callback)
+ throws ConfirmationAlreadyPresentingException,
+ ConfirmationNotAvailableException {
+ if (mCallback != null) {
+ throw new ConfirmationAlreadyPresentingException();
+ }
+ mCallback = callback;
+ mExecutor = executor;
+
+ int uiOptionsAsFlags = 0;
+ // TODO: set AccessibilityInverted, AccessibilityMagnified in uiOptionsAsFlags as needed.
+ String locale = Locale.getDefault().toLanguageTag();
+ int responseCode = mKeyStore.presentConfirmationPrompt(
+ mCallbackBinder, mPromptText.toString(), mExtraData, locale, uiOptionsAsFlags);
+ switch (responseCode) {
+ case KeyStore.CONFIRMATIONUI_OK:
+ return;
+
+ case KeyStore.CONFIRMATIONUI_OPERATION_PENDING:
+ throw new ConfirmationAlreadyPresentingException();
+
+ case KeyStore.CONFIRMATIONUI_UNIMPLEMENTED:
+ throw new ConfirmationNotAvailableException();
+
+ case KeyStore.CONFIRMATIONUI_UIERROR:
+ throw new IllegalArgumentException();
+
+ default:
+ // Unexpected error code.
+ Log.w(TAG,
+ "Unexpected responseCode=" + responseCode
+ + " from presentConfirmationPrompt() call.");
+ throw new IllegalArgumentException();
+ }
+ }
+
+ /**
+ * Cancels a prompt currently being displayed.
+ *
+ * On success, the
+ * {@link ConfirmationCallback#onDismissedByApplication onDismissedByApplication()} method on
+ * the supplied callback object will be called asynchronously.
+ *
+ * @throws IllegalStateException if no prompt is currently being presented.
+ */
+ public void cancelPrompt() {
+ int responseCode = mKeyStore.cancelConfirmationPrompt(mCallbackBinder);
+ if (responseCode == KeyStore.CONFIRMATIONUI_OK) {
+ return;
+ } else if (responseCode == KeyStore.CONFIRMATIONUI_OPERATION_PENDING) {
+ throw new IllegalStateException();
+ } else {
+ // Unexpected error code.
+ Log.w(TAG,
+ "Unexpected responseCode=" + responseCode
+ + " from cancelConfirmationPrompt() call.");
+ throw new IllegalStateException();
+ }
+ }
+
+ /**
+ * Checks if the device supports confirmation prompts.
+ *
+ * @return true if confirmation prompts are supported by the device.
+ */
+ public static boolean isSupported() {
+ // TODO: read and return system property.
+ return true;
+ }
+}
diff --git a/core/java/android/security/ConfirmationNotAvailableException.java b/core/java/android/security/ConfirmationNotAvailableException.java
new file mode 100644
index 0000000..8d0e672
--- /dev/null
+++ b/core/java/android/security/ConfirmationNotAvailableException.java
@@ -0,0 +1,30 @@
+/*
+ * 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.security;
+
+/**
+ * This exception is thrown when presenting a prompt fails because the the environment lacks
+ * facilities for showing confirmations.
+ */
+public class ConfirmationNotAvailableException extends Exception {
+ public ConfirmationNotAvailableException() {
+ }
+
+ public ConfirmationNotAvailableException(String message) {
+ super(message);
+ }
+}
diff --git a/core/java/android/security/keymaster/KeymasterDefs.java b/core/java/android/security/keymaster/KeymasterDefs.java
index 3464370..1d13335 100644
--- a/core/java/android/security/keymaster/KeymasterDefs.java
+++ b/core/java/android/security/keymaster/KeymasterDefs.java
@@ -74,6 +74,7 @@
public static final int KM_TAG_AUTH_TIMEOUT = KM_UINT | 505;
public static final int KM_TAG_ALLOW_WHILE_ON_BODY = KM_BOOL | 506;
public static final int KM_TAG_TRUSTED_USER_PRESENCE_REQUIRED = KM_BOOL | 507;
+ public static final int KM_TAG_TRUSTED_CONFIRMATION_REQUIRED = KM_BOOL | 508;
public static final int KM_TAG_ALL_APPLICATIONS = KM_BOOL | 600;
public static final int KM_TAG_APPLICATION_ID = KM_BYTES | 601;
diff --git a/core/java/android/security/keystore/recovery/KeyChainProtectionParams.java b/core/java/android/security/keystore/recovery/KeyChainProtectionParams.java
index a43952a..aa09f10 100644
--- a/core/java/android/security/keystore/recovery/KeyChainProtectionParams.java
+++ b/core/java/android/security/keystore/recovery/KeyChainProtectionParams.java
@@ -235,17 +235,7 @@
}
/**
- * 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 5c7388f..4f2f6cb 100644
--- a/core/java/android/service/autofill/AutofillServiceInfo.java
+++ b/core/java/android/service/autofill/AutofillServiceInfo.java
@@ -25,16 +25,20 @@
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 @@
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 0000000..4e1425d
--- /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 0000000..0f7b540
--- /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 9017848..6bab6aa 100644
--- a/core/java/android/service/autofill/UserData.java
+++ b/core/java/android/service/autofill/UserData.java
@@ -30,6 +30,7 @@
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 @@
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 @@
return mAlgorithm;
}
+ /**
+ * Gets the id.
+ */
+ public String getId() {
+ return mId;
+ }
+
/** @hide */
public Bundle getAlgorithmArgs() {
return mAlgorithmArgs;
@@ -92,6 +102,7 @@
/** @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 @@
* 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 @@
* 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 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 @@
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 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 @@
@Override
public void writeToParcel(Parcel parcel, int flags) {
+ parcel.writeString(mId);
parcel.writeStringArray(mRemoteIds);
parcel.writeStringArray(mValues);
parcel.writeString(mAlgorithm);
@@ -279,9 +306,10 @@
// 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 1705b7d..7f195d6 100644
--- a/core/java/android/service/autofill/ValueFinder.java
+++ b/core/java/android/service/autofill/ValueFinder.java
@@ -19,6 +19,7 @@
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 @@
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/telephony/java/android/telephony/ims/internal/aidl/IImsConfigCallback.aidl b/core/java/android/service/textclassifier/ITextClassificationCallback.aidl
similarity index 63%
copy from telephony/java/android/telephony/ims/internal/aidl/IImsConfigCallback.aidl
copy to core/java/android/service/textclassifier/ITextClassificationCallback.aidl
index 52efd23..10bfe63 100644
--- a/telephony/java/android/telephony/ims/internal/aidl/IImsConfigCallback.aidl
+++ b/core/java/android/service/textclassifier/ITextClassificationCallback.aidl
@@ -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.
@@ -14,15 +14,15 @@
* limitations under the License.
*/
+package android.service.textclassifier;
-package android.telephony.ims.internal.aidl;
+import android.view.textclassifier.TextClassification;
/**
- * Provides callback interface for ImsConfig when a value has changed.
- *
- * {@hide}
+ * Callback for a TextClassification request.
+ * @hide
*/
-oneway interface IImsConfigCallback {
- void onIntConfigChanged(int item, int value);
- void onStringConfigChanged(int item, String value);
+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 0000000..d2ffe34
--- /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/telephony/java/android/telephony/ims/internal/aidl/IImsRcsFeature.aidl b/core/java/android/service/textclassifier/ITextLinksCallback.aidl
similarity index 66%
copy from telephony/java/android/telephony/ims/internal/aidl/IImsRcsFeature.aidl
copy to core/java/android/service/textclassifier/ITextLinksCallback.aidl
index f6005b6..a9e0dde 100644
--- a/telephony/java/android/telephony/ims/internal/aidl/IImsRcsFeature.aidl
+++ b/core/java/android/service/textclassifier/ITextLinksCallback.aidl
@@ -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,12 +14,15 @@
* limitations under the License.
*/
-package android.telephony.ims.internal.aidl;
+package android.service.textclassifier;
+
+import android.view.textclassifier.TextLinks;
/**
- * See RcsFeature for more information.
- * {@hide}
+ * Callback for a TextLinks request.
+ * @hide
*/
-interface IImsRcsFeature {
- //Empty Default Implementation
+oneway interface ITextLinksCallback {
+ void onSuccess(in TextLinks links);
+ void onFailure();
}
\ No newline at end of file
diff --git a/telephony/java/android/telephony/ims/internal/aidl/IImsRcsFeature.aidl b/core/java/android/service/textclassifier/ITextSelectionCallback.aidl
similarity index 64%
copy from telephony/java/android/telephony/ims/internal/aidl/IImsRcsFeature.aidl
copy to core/java/android/service/textclassifier/ITextSelectionCallback.aidl
index f6005b6..1b4c4d1 100644
--- a/telephony/java/android/telephony/ims/internal/aidl/IImsRcsFeature.aidl
+++ b/core/java/android/service/textclassifier/ITextSelectionCallback.aidl
@@ -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,12 +14,15 @@
* limitations under the License.
*/
-package android.telephony.ims.internal.aidl;
+package android.service.textclassifier;
+
+import android.view.textclassifier.TextSelection;
/**
- * See RcsFeature for more information.
- * {@hide}
+ * Callback for a TextSelection request.
+ * @hide
*/
-interface IImsRcsFeature {
- //Empty Default Implementation
+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 0000000..6c8c8bc
--- /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 45fbf6f..aafcf44 100644
--- a/core/java/android/text/MeasuredParagraph.java
+++ b/core/java/android/text/MeasuredParagraph.java
@@ -672,6 +672,13 @@
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 @@
@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 ff23395..bb7a9e0 100644
--- a/core/java/android/text/MeasuredText.java
+++ b/core/java/android/text/MeasuredText.java
@@ -333,6 +333,20 @@
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 de2dcce..413cd10 100755
--- a/core/java/android/text/format/DateFormat.java
+++ b/core/java/android/text/format/DateFormat.java
@@ -431,7 +431,7 @@
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 @@
: 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 768aee9..d973d4a 100644
--- a/core/java/android/text/util/Linkify.java
+++ b/core/java/android/text/util/Linkify.java
@@ -19,6 +19,7 @@
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.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.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 @@
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/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java
index 25a177e..57d23ce 100644
--- a/core/java/android/util/FeatureFlagUtils.java
+++ b/core/java/android/util/FeatureFlagUtils.java
@@ -38,11 +38,9 @@
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 a4c590f..9436b29 100644
--- a/core/java/android/util/apk/ApkSignatureSchemeV3Verifier.java
+++ b/core/java/android/util/apk/ApkSignatureSchemeV3Verifier.java
@@ -438,8 +438,8 @@
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 @@
// - 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 @@
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/View.java b/core/java/android/view/View.java
index 5150c1f..9770fba 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -17,7 +17,6 @@
package android.view;
import static android.view.accessibility.AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED;
-
import static java.lang.Math.max;
import android.animation.AnimatorInflater;
@@ -3975,6 +3974,7 @@
/**
* Current clip bounds. to which all drawing of this view are constrained.
*/
+ @ViewDebug.ExportedProperty(category = "drawing")
Rect mClipBounds = null;
private boolean mLastIsOpaque;
@@ -26979,6 +26979,8 @@
stream.addProperty("drawing:scaleY", getScaleY());
stream.addProperty("drawing:pivotX", getPivotX());
stream.addProperty("drawing:pivotY", getPivotY());
+ stream.addProperty("drawing:clipBounds",
+ mClipBounds == null ? null : mClipBounds.toString());
stream.addProperty("drawing:opaque", isOpaque());
stream.addProperty("drawing:alpha", getAlpha());
stream.addProperty("drawing:transitionAlpha", getTransitionAlpha());
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 0af1254..7bd197e 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -1995,6 +1995,7 @@
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 4b24a71..dac1998 100644
--- a/core/java/android/view/autofill/AutofillManager.java
+++ b/core/java/android/view/autofill/AutofillManager.java
@@ -1099,6 +1099,30 @@
}
/**
+ * 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 @@
}
/**
- * 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 1a11fbb..0018547 100644
--- a/core/java/android/view/autofill/IAutoFillManager.aidl
+++ b/core/java/android/view/autofill/IAutoFillManager.aidl
@@ -56,6 +56,7 @@
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 0000000..af55dcd
--- /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/telephony/java/com/android/ims/ImsStreamMediaProfile.aidl b/core/java/android/view/textclassifier/TextClassification.aidl
similarity index 77%
copy from telephony/java/com/android/ims/ImsStreamMediaProfile.aidl
copy to core/java/android/view/textclassifier/TextClassification.aidl
index d648a35..9fefe5d 100644
--- a/telephony/java/com/android/ims/ImsStreamMediaProfile.aidl
+++ b/core/java/android/view/textclassifier/TextClassification.aidl
@@ -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.
@@ -14,6 +14,7 @@
* limitations under the License.
*/
-package com.android.ims;
+package android.view.textclassifier;
-parcelable ImsStreamMediaProfile;
+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 54e93d5..a6a2a94 100644
--- a/core/java/android/view/textclassifier/TextClassification.java
+++ b/core/java/android/view/textclassifier/TextClassification.java
@@ -97,7 +97,7 @@
* });
* }</pre>
*/
-public final class TextClassification {
+public final class TextClassification implements Parcelable {
/**
* @hide
@@ -310,42 +310,6 @@
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 @@
}
}
- /**
- * Parcelable wrapper for TextClassification objects.
- * @hide
- */
- public static final class ParcelableWrapper implements Parcelable {
+ @Override
+ public int describeContents() {
+ return 0;
+ }
- @NonNull private TextClassification mTextClassification;
-
- public ParcelableWrapper(@NonNull TextClassification textClassification) {
- Preconditions.checkNotNull(textClassification);
- mTextClassification = textClassification;
+ @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);
}
-
- @NonNull
- public TextClassification getTextClassification() {
- return mTextClassification;
+ 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);
+ }
- @Override
- public int describeContents() {
- return 0;
- }
+ public static final Parcelable.Creator<TextClassification> CREATOR =
+ new Parcelable.Creator<TextClassification>() {
+ @Override
+ public TextClassification createFromParcel(Parcel in) {
+ return new TextClassification(in);
+ }
- @Override
- public void writeToParcel(Parcel dest, int flags) {
- mTextClassification.writeToParcel(dest, flags);
- }
+ @Override
+ public TextClassification[] newArray(int size) {
+ return new TextClassification[size];
+ }
+ };
- public static final Parcelable.Creator<ParcelableWrapper> CREATOR =
- new Parcelable.Creator<ParcelableWrapper>() {
- @Override
- public ParcelableWrapper createFromParcel(Parcel in) {
- return new ParcelableWrapper(new TextClassification(in));
- }
-
- @Override
- public ParcelableWrapper[] newArray(int size) {
- return new ParcelableWrapper[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 d7b0776..300aef2 100644
--- a/core/java/android/view/textclassifier/TextClassificationManager.java
+++ b/core/java/android/view/textclassifier/TextClassificationManager.java
@@ -19,6 +19,8 @@
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 @@
@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 @@
}
/**
+ * 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 @@
* 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 04ab447..9f75c4a 100644
--- a/core/java/android/view/textclassifier/TextClassifier.java
+++ b/core/java/android/view/textclassifier/TextClassifier.java
@@ -23,9 +23,12 @@
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 @@
/**
* 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 @@
* 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 @@
@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 @@
* {@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 @@
* 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 @@
* 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 @@
@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 @@
* 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 @@
* 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 @@
}
/**
- * 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 @@
@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 @@
*
* @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 @@
* 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 6a44fb3..b03c70d 100644
--- a/core/java/android/view/textclassifier/TextClassifierImpl.java
+++ b/core/java/android/view/textclassifier/TextClassifierImpl.java
@@ -35,6 +35,8 @@
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 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 @@
*
* @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 @@
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 @@
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 @@
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 @@
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 @@
}
@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 @@
}
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 @@
}
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 @@
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 @@
} 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 @@
}
}
- @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 @@
}
}
- @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 @@
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/telephony/java/com/android/ims/ImsStreamMediaProfile.aidl b/core/java/android/view/textclassifier/TextLinks.aidl
similarity index 79%
copy from telephony/java/com/android/ims/ImsStreamMediaProfile.aidl
copy to core/java/android/view/textclassifier/TextLinks.aidl
index d648a35..1bbb798 100644
--- a/telephony/java/com/android/ims/ImsStreamMediaProfile.aidl
+++ b/core/java/android/view/textclassifier/TextLinks.aidl
@@ -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.
@@ -14,6 +14,7 @@
* limitations under the License.
*/
-package com.android.ims;
+package android.view.textclassifier;
-parcelable ImsStreamMediaProfile;
+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 ba854e0..670efdd 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.text.style.ClickableSpan;
-import android.view.View;
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.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 @@
* 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 @@
* 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 Success or failure.
+ * @return a status code indicating whether or not the links were successfully applied
+ *
+ * @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 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 @@
*
* @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 @@
*
* @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 @@
* @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 @@
@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 @@
private TextLink(Parcel in) {
mEntityScores = EntityConfidence.CREATOR.createFromParcel(in);
- mOriginalText = in.readString();
mStart = in.readInt();
mEnd = in.readInt();
}
@@ -227,6 +295,32 @@
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 @@
}
/**
+ * 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 @@
}
/**
- * @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 @@
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 @@
if (mEntityConfig != null) {
mEntityConfig.writeToParcel(dest, flags);
}
+ dest.writeInt(mApplyStrategy);
}
public static final Parcelable.Creator<Options> CREATOR =
@@ -305,39 +448,53 @@
if (in.readInt() > 0) {
mEntityConfig = TextClassifier.EntityConfig.CREATOR.createFromParcel(in);
}
+ mApplyStrategy = in.readInt();
}
}
/**
* A function to create spans from TextLinks.
- *
- * Applies only to TextViews.
- * We can hide this until we are convinced we want it to be part of the public API.
- *
- * @hide
*/
- 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);
- }
- }
- };
+ private static final Function<TextLink, TextLinkSpan> DEFAULT_SPAN_FACTORY =
+ textLink -> new TextLinkSpan(textLink);
+
+ /**
+ * A ClickableSpan for a TextLink.
+ *
+ * <p>Applies only to TextViews.
+ */
+ 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 @@
* Adds a TextLink.
*
* @return this instance.
+ *
+ * @throws IllegalArgumentException if entityScores is null or empty.
*/
- public Builder addLink(TextLink link) {
- Preconditions.checkNotNull(link);
- mLinks.add(link);
+ 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 clearTextLinks() {
+ mLinks.clear();
return this;
}
@@ -364,4 +530,14 @@
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/telephony/java/com/android/ims/ImsStreamMediaProfile.aidl b/core/java/android/view/textclassifier/TextSelection.aidl
similarity index 78%
copy from telephony/java/com/android/ims/ImsStreamMediaProfile.aidl
copy to core/java/android/view/textclassifier/TextSelection.aidl
index d648a35..dab1aef 100644
--- a/telephony/java/com/android/ims/ImsStreamMediaProfile.aidl
+++ b/core/java/android/view/textclassifier/TextSelection.aidl
@@ -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.
@@ -14,6 +14,7 @@
* limitations under the License.
*/
-package com.android.ims;
+package android.view.textclassifier;
-parcelable ImsStreamMediaProfile;
+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 774d42d..1c93be7 100644
--- a/core/java/android/view/textclassifier/TextSelection.java
+++ b/core/java/android/view/textclassifier/TextSelection.java
@@ -34,7 +34,7 @@
/**
* 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 @@
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 @@
}
}
- /**
- * Parcelable wrapper for TextSelection objects.
- * @hide
- */
- public static final class ParcelableWrapper implements Parcelable {
+ @Override
+ public int describeContents() {
+ return 0;
+ }
- @NonNull private TextSelection mTextSelection;
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(mStartIndex);
+ dest.writeInt(mEndIndex);
+ mEntityConfidence.writeToParcel(dest, flags);
+ dest.writeString(mSignature);
+ }
- public ParcelableWrapper(@NonNull TextSelection textSelection) {
- Preconditions.checkNotNull(textSelection);
- mTextSelection = textSelection;
- }
+ public static final Parcelable.Creator<TextSelection> CREATOR =
+ new Parcelable.Creator<TextSelection>() {
+ @Override
+ public TextSelection createFromParcel(Parcel in) {
+ return new TextSelection(in);
+ }
- @NonNull
- public TextSelection getTextSelection() {
- return mTextSelection;
- }
+ @Override
+ public TextSelection[] newArray(int size) {
+ return new TextSelection[size];
+ }
+ };
- @Override
- public int describeContents() {
- return 0;
- }
-
- @Override
- public void writeToParcel(Parcel dest, int flags) {
- mTextSelection.writeToParcel(dest, flags);
- }
-
- public static final Parcelable.Creator<ParcelableWrapper> CREATOR =
- new Parcelable.Creator<ParcelableWrapper>() {
- @Override
- public ParcelableWrapper createFromParcel(Parcel in) {
- return new ParcelableWrapper(new TextSelection(in));
- }
-
- @Override
- public ParcelableWrapper[] newArray(int size) {
- return new ParcelableWrapper[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 0000000..6b84835
--- /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 0000000..40e4d8c
--- /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 0000000..f40b655
--- /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 d2cb70e..65deb3b 100644
--- a/core/java/android/webkit/WebView.java
+++ b/core/java/android/webkit/WebView.java
@@ -2106,6 +2106,18 @@
* 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 @@
* 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 e42217f..7a4c800 100644
--- a/core/java/android/widget/Magnifier.java
+++ b/core/java/android/widget/Magnifier.java
@@ -182,8 +182,6 @@
/**
* 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()) {
diff --git a/core/java/android/widget/MediaControlView2.java b/core/java/android/widget/MediaControlView2.java
index 84d1850..2e4cccf 100644
--- a/core/java/android/widget/MediaControlView2.java
+++ b/core/java/android/widget/MediaControlView2.java
@@ -19,17 +19,17 @@
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 @@
* 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 @@
@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 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 @@
}
/**
- * Hide the control view from the screen.
- */
- public void hide() {
- mProvider.hide_impl();
- }
-
- /**
- * 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.
- */
- public void showSubtitle() {
- mProvider.showSubtitle_impl();
- }
-
- /**
- * Hides the currently displayed subtitle.
- */
- public void hideSubtitle() {
- mProvider.hideSubtitle_impl();
- }
-
- /**
- * 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.
+ * Changes the visibility state of an individual button. Default value is View.Visible.
*
- * @param next Listener for clicking next button
- * @param prev Listener for clicking previous button
+ * @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 setPrevNextListeners(View.OnClickListener next, View.OnClickListener prev) {
- mProvider.setPrevNextListeners_impl(next, prev);
+ public void setButtonVisibility(@Button int button, @Visibility int visibility) {
+ mProvider.setButtonVisibility_impl(button, visibility);
}
/**
- * 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
+ * Requests focus for the play/pause button.
*/
- public void setButtonVisibility(int button, boolean visible) {
- mProvider.setButtonVisibility_impl(button, visible);
+ public void requestPlayButtonFocus() {
+ mProvider.requestPlayButtonFocus_impl();
+ }
+
+ /**
+ * Sets a new timeout value (in milliseconds) for showing MediaControlView2. The default value
+ * is set as 2 seconds.
+ * @param timeout the
+ */
+ public void setTimeout(long timeout) {
+ mProvider.setTimeout_impl(timeout);
+ }
+
+ /**
+ * Retrieves current timeout value (in milliseconds) for showing MediaControlView2. The default
+ * value is set as 2 seconds.
+ */
+ public long getTimeout() {
+ return mProvider.getTimeout_impl();
}
@Override
- protected void onAttachedToWindow() {
- mProvider.onAttachedToWindow_impl();
- }
+ // TODO Move this method to ViewProvider
+ public void onVisibilityAggregated(boolean isVisible) {
- @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();
- }
-
- @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 3bfa520..2e354c1 100644
--- a/core/java/android/widget/SelectionActionModeHelper.java
+++ b/core/java/android/widget/SelectionActionModeHelper.java
@@ -37,8 +37,8 @@
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 @@
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 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 @@
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 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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 706b0ce..77670b3 100644
--- a/core/java/android/widget/TimePickerClockDelegate.java
+++ b/core/java/android/widget/TimePickerClockDelegate.java
@@ -50,6 +50,7 @@
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 @@
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 ac83313..78ca011 100644
--- a/core/java/android/widget/VideoView2.java
+++ b/core/java/android/widget/VideoView2.java
@@ -27,12 +27,11 @@
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 @@
*
* @hide
*/
-public class VideoView2 extends FrameLayout {
+public class VideoView2 extends FrameLayoutHelper<VideoView2Provider> {
/** @hide */
@IntDef({
VIEW_TYPE_TEXTUREVIEW,
@@ -112,21 +111,19 @@
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 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 @@
}
/**
- * 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 @@
*/
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 997d47f..6e0ba341 100644
--- a/core/java/com/android/internal/app/ChooserActivity.java
+++ b/core/java/com/android/internal/app/ChooserActivity.java
@@ -841,7 +841,7 @@
}
@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 @@
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 86731bc..398d087 100644
--- a/core/java/com/android/internal/app/IntentForwarderActivity.java
+++ b/core/java/com/android/internal/app/IntentForwarderActivity.java
@@ -107,7 +107,7 @@
|| 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 d6d4490..ceb06f5 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.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 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 @@
}
@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 @@
* @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/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index e91b67a..7def876 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -10896,7 +10896,7 @@
return null;
}
- /**
+ /**
* Distribute WiFi energy info and network traffic to apps.
* @param info The energy information from the WiFi controller.
*/
@@ -11160,6 +11160,9 @@
}
}
+ private ModemActivityInfo mLastModemActivityInfo =
+ new ModemActivityInfo(0, 0, 0, new int[0], 0, 0);
+
/**
* Distribute Cell radio energy info and network traffic to apps.
*/
@@ -11180,6 +11183,22 @@
}
}
+ 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) {
@@ -11191,11 +11210,11 @@
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.
@@ -11203,16 +11222,15 @@
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);
}
@@ -11293,7 +11311,7 @@
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);
}
@@ -11301,7 +11319,7 @@
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);
}
@@ -11322,6 +11340,10 @@
}
}
+ // 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.
@@ -11338,14 +11360,17 @@
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;
@@ -11424,9 +11449,25 @@
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(
@@ -11446,8 +11487,8 @@
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 =
@@ -11478,12 +11519,9 @@
}
}
- 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(
@@ -11491,8 +11529,10 @@
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;
}
/**
diff --git a/core/java/com/android/internal/util/CollectionUtils.java b/core/java/com/android/internal/util/CollectionUtils.java
index 2f2c747..433d14f 100644
--- a/core/java/com/android/internal/util/CollectionUtils.java
+++ b/core/java/com/android/internal/util/CollectionUtils.java
@@ -138,14 +138,14 @@
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 e3436e8..fb18669 100644
--- a/core/java/com/android/server/BootReceiver.java
+++ b/core/java/com/android/server/BootReceiver.java
@@ -35,6 +35,7 @@
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;
@@ -112,6 +113,8 @@
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 @@
}
}
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 @@
// 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 @@
}
}
+ 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 0e562c0..c88cf5c 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 @@
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 @@
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 @@
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 @@
{ "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 743a7ef..e2ce1a4 100644
--- a/core/jni/android/graphics/ImageDecoder.cpp
+++ b/core/jni/android/graphics/ImageDecoder.cpp
@@ -74,7 +74,8 @@
// 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 49cbb54..115d0d5 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 @@
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 @@
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 0d09b56..86cda44 100644
--- a/core/jni/android_database_CursorWindow.cpp
+++ b/core/jni/android_database_CursorWindow.cpp
@@ -519,8 +519,21 @@
(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 @@
(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_text_MeasuredParagraph.cpp b/core/jni/android_text_MeasuredParagraph.cpp
index f0e449d..d33337d 100644
--- a/core/jni/android_text_MeasuredParagraph.cpp
+++ b/core/jni/android_text_MeasuredParagraph.cpp
@@ -72,8 +72,7 @@
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 @@
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 @@
// 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_view_Surface.cpp b/core/jni/android_view_Surface.cpp
index 421e0de..f5c09fd 100644
--- a/core/jni/android_view_Surface.cpp
+++ b/core/jni/android_view_Surface.cpp
@@ -524,15 +524,15 @@
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 13e6fcd..32945bf 100644
--- a/core/jni/com_android_internal_os_Zygote.cpp
+++ b/core/jni/com_android_internal_os_Zygote.cpp
@@ -141,32 +141,45 @@
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 @@
bool is_system_server, jintArray fdsToClose,
jintArray fdsToIgnore,
jstring instructionSet, jstring dataDir) {
- SetSigChldHandler();
+ SetSignalHandlers();
sigset_t sigchld;
sigemptyset(&sigchld);
@@ -682,7 +695,8 @@
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 4d748e8..1e8ace4 100644
--- a/core/proto/android/app/window_configuration.proto
+++ b/core/proto/android/app/window_configuration.proto
@@ -21,9 +21,12 @@
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 860d608..ef04720 100644
--- a/core/proto/android/media/audioattributes.proto
+++ b/core/proto/android/media/audioattributes.proto
@@ -20,15 +20,19 @@
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 3ec6f05..8a04bf7 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 d0fd0b4..43c869c 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 @@
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 60c060c..0ada895 100644
--- a/core/proto/android/server/intentresolver.proto
+++ b/core/proto/android/server/intentresolver.proto
@@ -19,9 +19,15 @@
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 fa430d8..2ae526a 100644
--- a/core/proto/android/server/statlogger.proto
+++ b/core/proto/android/server/statlogger.proto
@@ -20,8 +20,12 @@
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 89cf2f8..2118deb 100644
--- a/core/proto/android/server/wirelesschargerdetector.proto
+++ b/core/proto/android/server/wirelesschargerdetector.proto
@@ -19,8 +19,14 @@
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 9013a23..5c40e5f 100644
--- a/core/proto/android/service/notification.proto
+++ b/core/proto/android/service/notification.proto
@@ -50,7 +50,7 @@
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 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/util/event_log_tags.proto b/core/proto/android/util/event_log_tags.proto
index cb039be..457219f 100644
--- a/core/proto/android/util/event_log_tags.proto
+++ b/core/proto/android/util/event_log_tags.proto
@@ -19,17 +19,25 @@
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 @@
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 ff13fab..ee258b7 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 9288b4f..665d688 100644
--- a/core/proto/android/view/surfacecontrol.proto
+++ b/core/proto/android/view/surfacecontrol.proto
@@ -19,10 +19,14 @@
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 2779cd6..e5ba6d7 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/layout/popup_menu_item_layout.xml b/core/res/res/layout/popup_menu_item_layout.xml
index 3b89f0d..1be915e 100644
--- a/core/res/res/layout/popup_menu_item_layout.xml
+++ b/core/res/res/layout/popup_menu_item_layout.xml
@@ -28,18 +28,18 @@
android:layout_marginBottom="4dip"
android:background="@drawable/list_divider_material" />
- <!-- Icon will be inserted here. -->
-
- <!-- The title and summary have some gap between them,
- and this 'group' should be centered vertically. -->
<LinearLayout
android:id="@+id/content"
- android:layout_width="wrap_content"
+ android:layout_width="match_parent"
android:layout_height="?attr/dropdownListPreferredItemHeight"
android:paddingEnd="16dip"
android:duplicateParentState="true" >
- <LinearLayout
+ <!-- Icon will be inserted here. -->
+
+ <!-- The title and summary have some gap between them,
+ and this 'group' should be centered vertically. -->
+ <RelativeLayout
android:layout_width="0dip"
android:layout_weight="1"
android:layout_height="wrap_content"
@@ -71,7 +71,7 @@
android:duplicateParentState="true"
android:textAlignment="viewStart" />
- </LinearLayout>
+ </RelativeLayout>
<ImageView
android:id="@+id/submenuarrow"
@@ -81,8 +81,9 @@
android:layout_marginStart="8dp"
android:scaleType="center"
android:visibility="gone" />
- </LinearLayout>
- <!-- Checkbox, and/or radio button will be inserted here. -->
+ <!-- Checkbox, and/or radio button will be inserted here. -->
+
+ </LinearLayout>
</com.android.internal.view.menu.ListMenuItemView>
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 5184dda..0543305 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 38f890a..607414d 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/symbols.xml b/core/res/res/values/symbols.xml
index e8ab0be..a62d49e 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/tests/coretests/Android.mk b/core/tests/coretests/Android.mk
index 66731df..2ea1b46 100644
--- a/core/tests/coretests/Android.mk
+++ b/core/tests/coretests/Android.mk
@@ -53,6 +53,7 @@
android.test.base \
android.test.mock \
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 0000000..dce22ce
--- /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
index a305261..866de93 100644
--- a/core/tests/coretests/src/android/content/pm/AndroidTestRunnerSplitUpdaterTest.java
+++ b/core/tests/coretests/src/android/content/pm/AndroidTestRunnerSplitUpdaterTest.java
@@ -38,7 +38,7 @@
PackageBuilder before = builder().optionalLibraries(ANDROID_TEST_RUNNER);
PackageBuilder after = builder()
- .optionalLibraries(ANDROID_TEST_RUNNER, ANDROID_TEST_MOCK);
+ .optionalLibraries(ANDROID_TEST_MOCK, ANDROID_TEST_RUNNER);
checkBackwardsCompatibility(before, after);
}
diff --git a/core/tests/coretests/src/android/content/pm/PackageBackwardCompatibilityTest.java b/core/tests/coretests/src/android/content/pm/PackageBackwardCompatibilityTest.java
index b26fc48..c64d520 100644
--- a/core/tests/coretests/src/android/content/pm/PackageBackwardCompatibilityTest.java
+++ b/core/tests/coretests/src/android/content/pm/PackageBackwardCompatibilityTest.java
@@ -17,10 +17,12 @@
package android.content.pm;
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.PackageBackwardCompatibility.RemoveUnnecessaryAndroidTestBaseLibrary;
import android.os.Build;
import android.support.test.filters.SmallTest;
@@ -56,8 +58,19 @@
}
/**
+ * 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 detectWhenATBisOnBCP() {
+ Assume.assumeTrue(PackageBackwardCompatibility.bootClassPathContainsATB());
+ }
+
+ /**
* Ensures that the {@link PackageBackwardCompatibility} uses {@link OrgApacheHttpLegacyUpdater}
- * when necessary.
+ * and {@link AndroidTestBaseUpdater} when necessary.
*
* <p>More comprehensive tests for that class can be found in
* {@link OrgApacheHttpLegacyUpdaterTest}.
@@ -68,6 +81,9 @@
.targetSdkVersion(Build.VERSION_CODES.O);
List<String> expected = new ArrayList<>();
+ if (!PackageBackwardCompatibility.bootClassPathContainsATB()) {
+ expected.add(ANDROID_TEST_BASE);
+ }
if (!PackageBackwardCompatibility.bootClassPathContainsOAHL()) {
expected.add(ORG_APACHE_HTTP_LEGACY);
}
@@ -104,6 +120,30 @@
}
/**
+ * 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 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}.
*
@@ -114,8 +154,15 @@
public void android_test_runner_in_usesLibraries() {
PackageBuilder before = builder().requiredLibraries(ANDROID_TEST_RUNNER);
+ 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(ANDROID_TEST_RUNNER, ANDROID_TEST_MOCK);
+ .requiredLibraries(expected);
checkBackwardsCompatibility(before, after);
}
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 0000000..3dba440
--- /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/provider/SettingsBackupTest.java b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
index 733f7a1..0083b01 100644
--- a/core/tests/coretests/src/android/provider/SettingsBackupTest.java
+++ b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
@@ -245,6 +245,7 @@
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 8a81743..cf41eb8 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 @@
.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 a82542c..d6ac845 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 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 e920236..a6ea021 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 @@
.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 bbca12f..69e5670 100644
--- a/core/tests/coretests/src/android/widget/TextViewActivityTest.java
+++ b/core/tests/coretests/src/android/widget/TextViewActivityTest.java
@@ -325,9 +325,9 @@
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 c0bc3a8..b18fa74 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.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 @@
}
@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 @@
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 99bcd6c..a6c5373 100644
--- a/core/tests/hosttests/test-apps/MultiDexLegacyTestServices/Android.mk
+++ b/core/tests/hosttests/test-apps/MultiDexLegacyTestServices/Android.mk
@@ -36,10 +36,8 @@
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 e3068920..7cd01e54 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 7b83999..cb0a591 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 @@
// 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 @@
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 0000000..f3d98a8
--- /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 0000000..0ab2959
--- /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 0000000..900f203
--- /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 09192f4..04006b1 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 6c8aaf0..8addffbb 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -369,7 +369,6 @@
<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/drawable/AnimatedImageDrawable.java b/graphics/java/android/graphics/drawable/AnimatedImageDrawable.java
index 0ec19f9..bd49b87 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 @@
private int mIntrinsicHeight;
private boolean mStarting;
- private boolean mRunning;
private Handler mHandler;
@@ -222,8 +223,8 @@
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 @@
mStarting = false;
postOnAnimationStart();
-
- mRunning = true;
}
long nextUpdate = nDraw(mState.mNativePtr, canvas.getNativeCanvasWrapper());
@@ -244,12 +243,9 @@
// 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 @@
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 @@
*/
@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 @@
throw new IllegalStateException("called stop on empty AnimatedImageDrawable");
}
nStop(mState.mNativePtr);
- mRunning = false;
}
// Animatable2 overrides
@@ -405,18 +417,29 @@
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/keystore/java/android/security/keystore/AndroidKeyStoreKeyGeneratorSpi.java b/keystore/java/android/security/keystore/AndroidKeyStoreKeyGeneratorSpi.java
index f721ed3..09b3b9b 100644
--- a/keystore/java/android/security/keystore/AndroidKeyStoreKeyGeneratorSpi.java
+++ b/keystore/java/android/security/keystore/AndroidKeyStoreKeyGeneratorSpi.java
@@ -248,7 +248,8 @@
spec.getUserAuthenticationValidityDurationSeconds(),
spec.isUserAuthenticationValidWhileOnBody(),
spec.isInvalidatedByBiometricEnrollment(),
- GateKeeper.INVALID_SECURE_USER_ID /* boundToSpecificSecureUserId */);
+ GateKeeper.INVALID_SECURE_USER_ID /* boundToSpecificSecureUserId */,
+ spec.isUserConfirmationRequired());
} catch (IllegalStateException | IllegalArgumentException e) {
throw new InvalidAlgorithmParameterException(e);
}
@@ -289,7 +290,8 @@
spec.getUserAuthenticationValidityDurationSeconds(),
spec.isUserAuthenticationValidWhileOnBody(),
spec.isInvalidatedByBiometricEnrollment(),
- GateKeeper.INVALID_SECURE_USER_ID /* boundToSpecificSecureUserId */);
+ GateKeeper.INVALID_SECURE_USER_ID /* boundToSpecificSecureUserId */,
+ spec.isUserConfirmationRequired());
if (spec.isTrustedUserPresenceRequired()) {
args.addBoolean(KeymasterDefs.KM_TAG_TRUSTED_USER_PRESENCE_REQUIRED);
}
diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreKeyPairGeneratorSpi.java b/keystore/java/android/security/keystore/AndroidKeyStoreKeyPairGeneratorSpi.java
index d1eb688..e33e3cd 100644
--- a/keystore/java/android/security/keystore/AndroidKeyStoreKeyPairGeneratorSpi.java
+++ b/keystore/java/android/security/keystore/AndroidKeyStoreKeyPairGeneratorSpi.java
@@ -349,7 +349,8 @@
mSpec.getUserAuthenticationValidityDurationSeconds(),
mSpec.isUserAuthenticationValidWhileOnBody(),
mSpec.isInvalidatedByBiometricEnrollment(),
- GateKeeper.INVALID_SECURE_USER_ID /* boundToSpecificSecureUserId */);
+ GateKeeper.INVALID_SECURE_USER_ID /* boundToSpecificSecureUserId */,
+ mSpec.isUserConfirmationRequired());
} catch (IllegalArgumentException | IllegalStateException e) {
throw new InvalidAlgorithmParameterException(e);
}
@@ -545,7 +546,8 @@
mSpec.getUserAuthenticationValidityDurationSeconds(),
mSpec.isUserAuthenticationValidWhileOnBody(),
mSpec.isInvalidatedByBiometricEnrollment(),
- GateKeeper.INVALID_SECURE_USER_ID /* boundToSpecificSecureUserId */);
+ GateKeeper.INVALID_SECURE_USER_ID /* boundToSpecificSecureUserId */,
+ mSpec.isUserConfirmationRequired());
args.addDateIfNotNull(KeymasterDefs.KM_TAG_ACTIVE_DATETIME, mSpec.getKeyValidityStart());
args.addDateIfNotNull(KeymasterDefs.KM_TAG_ORIGINATION_EXPIRE_DATETIME,
mSpec.getKeyValidityForOriginationEnd());
diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreSecretKeyFactorySpi.java b/keystore/java/android/security/keystore/AndroidKeyStoreSecretKeyFactorySpi.java
index 9df37f5..7bbc099 100644
--- a/keystore/java/android/security/keystore/AndroidKeyStoreSecretKeyFactorySpi.java
+++ b/keystore/java/android/security/keystore/AndroidKeyStoreSecretKeyFactorySpi.java
@@ -190,6 +190,8 @@
&& !keymasterSecureUserIds.contains(getGateKeeperSecureUserId());
}
+ boolean userConfirmationRequired = keyCharacteristics.hwEnforced.getBoolean(KeymasterDefs.KM_TAG_TRUSTED_CONFIRMATION_REQUIRED);
+
return new KeyInfo(entryAlias,
insideSecureHardware,
origin,
@@ -207,7 +209,8 @@
userAuthenticationRequirementEnforcedBySecureHardware,
userAuthenticationValidWhileOnBody,
trustedUserPresenceRequred,
- invalidatedByBiometricEnrollment);
+ invalidatedByBiometricEnrollment,
+ userConfirmationRequired);
}
private static BigInteger getGateKeeperSecureUserId() throws ProviderException {
diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreSpi.java b/keystore/java/android/security/keystore/AndroidKeyStoreSpi.java
index 440e086..05cc74a 100644
--- a/keystore/java/android/security/keystore/AndroidKeyStoreSpi.java
+++ b/keystore/java/android/security/keystore/AndroidKeyStoreSpi.java
@@ -502,7 +502,8 @@
spec.getUserAuthenticationValidityDurationSeconds(),
spec.isUserAuthenticationValidWhileOnBody(),
spec.isInvalidatedByBiometricEnrollment(),
- spec.getBoundToSpecificSecureUserId());
+ spec.getBoundToSpecificSecureUserId(),
+ spec.isUserConfirmationRequired());
importArgs.addDateIfNotNull(KeymasterDefs.KM_TAG_ACTIVE_DATETIME,
spec.getKeyValidityStart());
importArgs.addDateIfNotNull(KeymasterDefs.KM_TAG_ORIGINATION_EXPIRE_DATETIME,
@@ -704,7 +705,8 @@
params.getUserAuthenticationValidityDurationSeconds(),
params.isUserAuthenticationValidWhileOnBody(),
params.isInvalidatedByBiometricEnrollment(),
- params.getBoundToSpecificSecureUserId());
+ params.getBoundToSpecificSecureUserId(),
+ params.isUserConfirmationRequired());
KeymasterUtils.addMinMacLengthAuthorizationIfNecessary(
args,
keymasterAlgorithm,
diff --git a/keystore/java/android/security/keystore/KeyGenParameterSpec.java b/keystore/java/android/security/keystore/KeyGenParameterSpec.java
index a896c72..da23c70 100644
--- a/keystore/java/android/security/keystore/KeyGenParameterSpec.java
+++ b/keystore/java/android/security/keystore/KeyGenParameterSpec.java
@@ -264,6 +264,7 @@
private final boolean mUserAuthenticationValidWhileOnBody;
private final boolean mInvalidatedByBiometricEnrollment;
private final boolean mIsStrongBoxBacked;
+ private final boolean mUserConfirmationRequired;
/**
* @hide should be built with Builder
@@ -293,7 +294,8 @@
boolean uniqueIdIncluded,
boolean userAuthenticationValidWhileOnBody,
boolean invalidatedByBiometricEnrollment,
- boolean isStrongBoxBacked) {
+ boolean isStrongBoxBacked,
+ boolean userConfirmationRequired) {
if (TextUtils.isEmpty(keyStoreAlias)) {
throw new IllegalArgumentException("keyStoreAlias must not be empty");
}
@@ -341,6 +343,7 @@
mUserAuthenticationValidWhileOnBody = userAuthenticationValidWhileOnBody;
mInvalidatedByBiometricEnrollment = invalidatedByBiometricEnrollment;
mIsStrongBoxBacked = isStrongBoxBacked;
+ mUserConfirmationRequired = userConfirmationRequired;
}
/**
@@ -547,6 +550,26 @@
}
/**
+ * Returns {@code true} if the key is authorized to be used only for messages confirmed by the
+ * user.
+ *
+ * Confirmation is separate from user authentication (see
+ * {@link Builder#setUserAuthenticationRequired(boolean)}). Keys can be created that require
+ * confirmation but not user authentication, or user authentication but not confirmation, or
+ * both. Confirmation verifies that some user with physical possession of the device has
+ * approved a displayed message. User authentication verifies that the correct user is present
+ * and has authenticated.
+ *
+ * <p>This authorization applies only to secret key and private key operations. Public key
+ * operations are not restricted.
+ *
+ * @see Builder#setUserConfirmationRequired(boolean)
+ */
+ public boolean isUserConfirmationRequired() {
+ return mUserConfirmationRequired;
+ }
+
+ /**
* Gets the duration of time (seconds) for which this key is authorized to be used after the
* user is successfully authenticated. This has effect only if user authentication is required
* (see {@link #isUserAuthenticationRequired()}).
@@ -675,6 +698,7 @@
private boolean mUserAuthenticationValidWhileOnBody;
private boolean mInvalidatedByBiometricEnrollment = true;
private boolean mIsStrongBoxBacked = false;
+ private boolean mUserConfirmationRequired;
/**
* Creates a new instance of the {@code Builder}.
@@ -1063,6 +1087,29 @@
}
/**
+ * Sets whether this key is authorized to be used only for messages confirmed by the
+ * user.
+ *
+ * Confirmation is separate from user authentication (see
+ * {@link #setUserAuthenticationRequired(boolean)}). Keys can be created that require
+ * confirmation but not user authentication, or user authentication but not confirmation,
+ * or both. Confirmation verifies that some user with physical possession of the device has
+ * approved a displayed message. User authentication verifies that the correct user is
+ * present and has authenticated.
+ *
+ * <p>This authorization applies only to secret key and private key operations. Public key
+ * operations are not restricted.
+ *
+ * @see {@link android.security.ConfirmationPrompter ConfirmationPrompter} class for
+ * more details about user confirmations.
+ */
+ @NonNull
+ public Builder setUserConfirmationRequired(boolean required) {
+ mUserConfirmationRequired = required;
+ return this;
+ }
+
+ /**
* Sets the duration of time (seconds) for which this key is authorized to be used after the
* user is successfully authenticated. This has effect if the key requires user
* authentication for its use (see {@link #setUserAuthenticationRequired(boolean)}).
@@ -1249,7 +1296,8 @@
mUniqueIdIncluded,
mUserAuthenticationValidWhileOnBody,
mInvalidatedByBiometricEnrollment,
- mIsStrongBoxBacked);
+ mIsStrongBoxBacked,
+ mUserConfirmationRequired);
}
}
}
diff --git a/keystore/java/android/security/keystore/KeyInfo.java b/keystore/java/android/security/keystore/KeyInfo.java
index 864f62a..0a75cd5 100644
--- a/keystore/java/android/security/keystore/KeyInfo.java
+++ b/keystore/java/android/security/keystore/KeyInfo.java
@@ -82,6 +82,7 @@
private final boolean mUserAuthenticationValidWhileOnBody;
private final boolean mTrustedUserPresenceRequired;
private final boolean mInvalidatedByBiometricEnrollment;
+ private final boolean mUserConfirmationRequired;
/**
* @hide
@@ -103,7 +104,8 @@
boolean userAuthenticationRequirementEnforcedBySecureHardware,
boolean userAuthenticationValidWhileOnBody,
boolean trustedUserPresenceRequired,
- boolean invalidatedByBiometricEnrollment) {
+ boolean invalidatedByBiometricEnrollment,
+ boolean userConfirmationRequired) {
mKeystoreAlias = keystoreKeyAlias;
mInsideSecureHardware = insideSecureHardware;
mOrigin = origin;
@@ -125,6 +127,7 @@
mUserAuthenticationValidWhileOnBody = userAuthenticationValidWhileOnBody;
mTrustedUserPresenceRequired = trustedUserPresenceRequired;
mInvalidatedByBiometricEnrollment = invalidatedByBiometricEnrollment;
+ mUserConfirmationRequired = userConfirmationRequired;
}
/**
@@ -260,6 +263,27 @@
}
/**
+ * Returns {@code true} if the key is authorized to be used only for messages confirmed by the
+ * user.
+ *
+ * Confirmation is separate from user authentication (see
+ * {@link #isUserAuthenticationRequired()}). Keys can be created that require confirmation but
+ * not user authentication, or user authentication but not confirmation, or both. Confirmation
+ * verifies that some user with physical possession of the device has approved a displayed
+ * message. User authentication verifies that the correct user is present and has
+ * authenticated.
+ *
+ * <p>This authorization applies only to secret key and private key operations. Public key
+ * operations are not restricted.
+ *
+ * @see KeyGenParameterSpec.Builder#setUserConfirmationRequired(boolean)
+ * @see KeyProtection.Builder#setUserConfirmationRequired(boolean)
+ */
+ public boolean isUserConfirmationRequired() {
+ return mUserConfirmationRequired;
+ }
+
+ /**
* Gets the duration of time (seconds) for which this key is authorized to be used after the
* user is successfully authenticated. This has effect only if user authentication is required
* (see {@link #isUserAuthenticationRequired()}).
diff --git a/keystore/java/android/security/keystore/KeyProtection.java b/keystore/java/android/security/keystore/KeyProtection.java
index dbacb9c..b5b3281 100644
--- a/keystore/java/android/security/keystore/KeyProtection.java
+++ b/keystore/java/android/security/keystore/KeyProtection.java
@@ -228,6 +228,7 @@
private final boolean mInvalidatedByBiometricEnrollment;
private final long mBoundToSecureUserId;
private final boolean mCriticalToDeviceEncryption;
+ private final boolean mUserConfirmationRequired;
private KeyProtection(
Date keyValidityStart,
@@ -244,7 +245,8 @@
boolean userAuthenticationValidWhileOnBody,
boolean invalidatedByBiometricEnrollment,
long boundToSecureUserId,
- boolean criticalToDeviceEncryption) {
+ boolean criticalToDeviceEncryption,
+ boolean userConfirmationRequired) {
mKeyValidityStart = Utils.cloneIfNotNull(keyValidityStart);
mKeyValidityForOriginationEnd = Utils.cloneIfNotNull(keyValidityForOriginationEnd);
mKeyValidityForConsumptionEnd = Utils.cloneIfNotNull(keyValidityForConsumptionEnd);
@@ -262,6 +264,7 @@
mInvalidatedByBiometricEnrollment = invalidatedByBiometricEnrollment;
mBoundToSecureUserId = boundToSecureUserId;
mCriticalToDeviceEncryption = criticalToDeviceEncryption;
+ mUserConfirmationRequired = userConfirmationRequired;
}
/**
@@ -396,6 +399,26 @@
}
/**
+ * Returns {@code true} if the key is authorized to be used only for messages confirmed by the
+ * user.
+ *
+ * Confirmation is separate from user authentication (see
+ * {@link #isUserAuthenticationRequired()}). Keys can be created that require confirmation but
+ * not user authentication, or user authentication but not confirmation, or both. Confirmation
+ * verifies that some user with physical possession of the device has approved a displayed
+ * message. User authentication verifies that the correct user is present and has
+ * authenticated.
+ *
+ * <p>This authorization applies only to secret key and private key operations. Public key
+ * operations are not restricted.
+ *
+ * @see Builder#setUserConfirmationRequired(boolean)
+ */
+ public boolean isUserConfirmationRequired() {
+ return mUserConfirmationRequired;
+ }
+
+ /**
* Gets the duration of time (seconds) for which this key is authorized to be used after the
* user is successfully authenticated. This has effect only if user authentication is required
* (see {@link #isUserAuthenticationRequired()}).
@@ -488,6 +511,7 @@
private int mUserAuthenticationValidityDurationSeconds = -1;
private boolean mUserAuthenticationValidWhileOnBody;
private boolean mInvalidatedByBiometricEnrollment = true;
+ private boolean mUserConfirmationRequired;
private long mBoundToSecureUserId = GateKeeper.INVALID_SECURE_USER_ID;
private boolean mCriticalToDeviceEncryption = false;
@@ -719,6 +743,29 @@
}
/**
+ * Sets whether this key is authorized to be used only for messages confirmed by the
+ * user.
+ *
+ * Confirmation is separate from user authentication (see
+ * {@link #setUserAuthenticationRequired(boolean)}). Keys can be created that require
+ * confirmation but not user authentication, or user authentication but not confirmation,
+ * or both. Confirmation verifies that some user with physical possession of the device has
+ * approved a displayed message. User authentication verifies that the correct user is
+ * present and has authenticated.
+ *
+ * <p>This authorization applies only to secret key and private key operations. Public key
+ * operations are not restricted.
+ *
+ * @see {@link android.security.ConfirmationPrompter ConfirmationPrompter} class for
+ * more details about user confirmations.
+ */
+ @NonNull
+ public Builder setUserConfirmationRequired(boolean required) {
+ mUserConfirmationRequired = required;
+ return this;
+ }
+
+ /**
* Sets the duration of time (seconds) for which this key is authorized to be used after the
* user is successfully authenticated. This has effect if the key requires user
* authentication for its use (see {@link #setUserAuthenticationRequired(boolean)}).
@@ -866,7 +913,8 @@
mUserAuthenticationValidWhileOnBody,
mInvalidatedByBiometricEnrollment,
mBoundToSecureUserId,
- mCriticalToDeviceEncryption);
+ mCriticalToDeviceEncryption,
+ mUserConfirmationRequired);
}
}
}
diff --git a/keystore/java/android/security/keystore/KeymasterUtils.java b/keystore/java/android/security/keystore/KeymasterUtils.java
index 34c8d1f..4e28601 100644
--- a/keystore/java/android/security/keystore/KeymasterUtils.java
+++ b/keystore/java/android/security/keystore/KeymasterUtils.java
@@ -16,6 +16,7 @@
package android.security.keystore;
+import android.util.Log;
import android.hardware.fingerprint.FingerprintManager;
import android.security.GateKeeper;
import android.security.KeyStore;
@@ -93,6 +94,8 @@
* overriding the default logic in this method where the key is bound to either the root
* SID of the current user, or the fingerprint SID if explicit fingerprint authorization
* is requested.
+ * @param userConfirmationRequired whether user confirmation is required to authorize the use
+ * of the key.
* @throws IllegalStateException if user authentication is required but the system is in a wrong
* state (e.g., secure lock screen not set up) for generating or importing keys that
* require user authentication.
@@ -102,7 +105,12 @@
int userAuthenticationValidityDurationSeconds,
boolean userAuthenticationValidWhileOnBody,
boolean invalidatedByBiometricEnrollment,
- long boundToSpecificSecureUserId) {
+ long boundToSpecificSecureUserId,
+ boolean userConfirmationRequired) {
+ if (userConfirmationRequired) {
+ args.addBoolean(KeymasterDefs.KM_TAG_TRUSTED_CONFIRMATION_REQUIRED);
+ }
+
if (!userAuthenticationRequired) {
args.addBoolean(KeymasterDefs.KM_TAG_NO_AUTH_REQUIRED);
return;
diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp
index 17f9b7c..3323bce 100644
--- a/libs/hwui/Android.bp
+++ b/libs/hwui/Android.bp
@@ -138,6 +138,7 @@
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 e01bf3d..5356d3b 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 @@
}
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);
-
- if (!mSkAnimatedImage->isRunning()) {
- return;
- }
-
+// Only called on the RenderThread while UI thread is locked.
+bool AnimatedImageDrawable::isDirty() {
const double currentTime = SkTime::GetMSecs();
- if (currentTime >= mNextFrameTime) {
- mNextFrameTime = mSkAnimatedImage->update(currentTime);
- mSnapshot.reset(mSkAnimatedImage->newPictureSnapshot());
- mIsDirty = true;
+ const double lastWallTime = mLastWallTime;
+
+ mLastWallTime = currentTime;
+ if (!mRunning) {
+ mDidDraw = false;
+ return false;
}
+
+ 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 @@
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;
}
- mIsDirty = false;
+ 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));
+ }
+
+ 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 @@
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 df8a5e4..9d84ed5 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 @@
};
/**
- * 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 0000000..c899003
--- /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 0000000..9e35374
--- /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 ba877d3..43f46ef 100644
--- a/libs/hwui/hwui/MinikinUtils.cpp
+++ b/libs/hwui/hwui/MinikinUtils.cpp
@@ -31,7 +31,7 @@
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 @@
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 @@
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 cf0b6a4..aa14699 100644
--- a/libs/hwui/pipeline/skia/SkiaDisplayList.cpp
+++ b/libs/hwui/pipeline/skia/SkiaDisplayList.cpp
@@ -98,8 +98,6 @@
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 534782a..9db39d9 100644
--- a/libs/hwui/pipeline/skia/SkiaPipeline.cpp
+++ b/libs/hwui/pipeline/skia/SkiaPipeline.cpp
@@ -40,7 +40,6 @@
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 @@
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 cc75e9c..3800194 100644
--- a/libs/hwui/pipeline/skia/SkiaPipeline.h
+++ b/libs/hwui/pipeline/skia/SkiaPipeline.h
@@ -55,12 +55,6 @@
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 @@
*/
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 f5685d9..c2907a6 100644
--- a/libs/services/src/os/DropBoxManager.cpp
+++ b/libs/services/src/os/DropBoxManager.cpp
@@ -208,6 +208,11 @@
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 @@
}
}} // 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 603926f..98e67c2 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 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 @@
msg.standardDeviationTopFourAverageCn0DbHz
= topFourAverageCn0Statistics.getStandardDeviation();
}
+ msg.powerMetrics = mGnssPowerMetrics.buildProto();
String s = Base64.encodeToString(GnssLog.toByteArray(msg), Base64.DEFAULT);
reset();
return s;
@@ -218,6 +222,21 @@
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 @@
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 @@
}
/**
+ * 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 @@
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 62f5677..8424601 100644
--- a/location/lib/Android.mk
+++ b/location/lib/Android.mk
@@ -22,9 +22,7 @@
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 41f9f09..3d879f5 100644
--- a/media/java/android/media/AudioDeviceInfo.java
+++ b/media/java/android/media/AudioDeviceInfo.java
@@ -22,6 +22,7 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.util.Objects;
import java.util.TreeSet;
/**
@@ -176,6 +177,19 @@
}
}
+ @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 2ac4063..22fa620 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -400,6 +400,18 @@
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 @@
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 6c65223..88d0a60 100644
--- a/media/java/android/media/IAudioService.aidl
+++ b/media/java/android/media/IAudioService.aidl
@@ -166,7 +166,8 @@
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/MediaController2.java b/media/java/android/media/MediaController2.java
index 3d27a48..b32e539 100644
--- a/media/java/android/media/MediaController2.java
+++ b/media/java/android/media/MediaController2.java
@@ -30,6 +30,7 @@
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 @@
*
* @param info new playback info
*/
- public void onAudioInfoChanged(PlaybackInfo info) { }
+ public void onPlaybackInfoChanged(PlaybackInfo info) { }
/**
* Called when the allowed commands are changed by session.
@@ -160,21 +161,14 @@
*/
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;
}
/**
@@ -187,7 +181,7 @@
* @return The type of playback this session is using.
*/
public int getPlaybackType() {
- return mVolumeType;
+ return mProvider.getPlaybackType_impl();
}
/**
@@ -199,7 +193,7 @@
* @return The attributes for this session.
*/
public AudioAttributes getAudioAttributes() {
- return mAudioAttrs;
+ return mProvider.getAudioAttributes_impl();
}
/**
@@ -210,11 +204,10 @@
* <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();
}
/**
@@ -223,7 +216,7 @@
* @return The maximum allowed volume where this session is playing.
*/
public int getMaxVolume() {
- return mMaxVolume;
+ return mProvider.getMaxVolume_impl();
}
/**
@@ -232,7 +225,7 @@
* @return The current volume where this session is playing.
*/
public int getCurrentVolume() {
- return mCurrentVolume;
+ return mProvider.getCurrentVolume_impl();
}
}
@@ -277,6 +270,9 @@
mProvider.close_impl();
}
+ /**
+ * @hide
+ */
@SystemApi
public MediaController2Provider getProvider() {
return mProvider;
diff --git a/media/java/android/media/MediaDrm.java b/media/java/android/media/MediaDrm.java
index a0edefa..90fcaab 100644
--- a/media/java/android/media/MediaDrm.java
+++ b/media/java/android/media/MediaDrm.java
@@ -942,43 +942,78 @@
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 releaseAllSecureStops();
+ 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 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 2e9894b..eae4436 100644
--- a/media/java/android/media/MediaItem2.java
+++ b/media/java/android/media/MediaItem2.java
@@ -24,7 +24,6 @@
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;
@@ -69,7 +68,7 @@
public MediaItem2(@NonNull Context context, @NonNull String mediaId,
@NonNull DataSourceDesc dsd, @Nullable MediaMetadata2 metadata,
@Flags int flags) {
- mProvider = ApiLoader.getProvider(context).createMediaItem2Provider(
+ mProvider = ApiLoader.getProvider(context).createMediaItem2(
context, this, mediaId, dsd, metadata, flags);
}
diff --git a/media/java/android/media/MediaLibraryService2.java b/media/java/android/media/MediaLibraryService2.java
index 94ada13..a901c68 100644
--- a/media/java/android/media/MediaLibraryService2.java
+++ b/media/java/android/media/MediaLibraryService2.java
@@ -19,6 +19,7 @@
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 @@
/**
* 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 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 @@
/**
* 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 fcdb4f7..54a9057 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 @@
*
* @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 @@
@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 @@
* @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 @@
* @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 @@
* 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 @@
* @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 @@
* @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 @@
* the given key.
*
* @param key The key the value is stored under
- * @return A {@link Rating2} or null
+ * @return A {@link Rating2} or {@code 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;
+ public @Nullable Rating2 getRating(@RatingKey String key) {
+ return mProvider.getRating_impl(key);
}
/**
@@ -525,14 +452,7 @@
* @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 @@
*
* @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 @@
* @return The number of fields in the metadata.
*/
public int size() {
- return mBundle.size();
+ return mProvider.size_impl();
}
/**
@@ -564,8 +478,8 @@
*
* @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 @@
*
* @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 @@
* 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 @@
*
* @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 @@
* @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 @@
* @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 @@
* @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 @@
* @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 @@
* @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 @@
*
* @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 d36df84..d84eedf 100644
--- a/media/java/android/media/MediaPlayer2.java
+++ b/media/java/android/media/MediaPlayer2.java
@@ -96,22 +96,13 @@
* {@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 86a285c..222c66e 100644
--- a/media/java/android/media/MediaPlayer2Impl.java
+++ b/media/java/android/media/MediaPlayer2Impl.java
@@ -1960,6 +1960,13 @@
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 @@
stayAwake(false);
updateSurfaceScreenOn();
synchronized (mEventCbLock) {
- mEventCb = null;
- mEventExec = null;
+ mEventCallbackRecords.clear();
}
if (mTimeProvider != null) {
mTimeProvider.close();
@@ -3061,8 +3067,7 @@
// Modular DRM clean up
mOnDrmConfigHelper = null;
synchronized (mDrmEventCbLock) {
- mDrmEventCb = null;
- mDrmEventExec = null;
+ mDrmEventCallbackRecords.clear();
}
resetDrmState();
@@ -3118,18 +3123,8 @@
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 @@
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;
+ // The parcel was parsed already in postEventFromNative
+ final DrmInfoImpl drmInfo;
- synchronized (mDrmLock) {
- if (mDrmInfoImpl != null) {
- drmInfo = mDrmInfoImpl.makeCopy();
- } else {
- drmInfo = null;
- }
+ 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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
}
}
- 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 @@
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 @@
@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 @@
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 @@
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 @@
@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 @@
// 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 @@
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 @@
} // 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 @@
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/MediaSession2.java b/media/java/android/media/MediaSession2.java
index aa329ab..acf9615 100644
--- a/media/java/android/media/MediaSession2.java
+++ b/media/java/android/media/MediaSession2.java
@@ -30,19 +30,20 @@
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.IInterface;
-import android.os.Parcelable;
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;
@@ -82,34 +83,178 @@
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;
- 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 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;
- public static final int COMMAND_CODE_PLAYBACK_SET_PLAYLIST_PARAMS = 20;
+ /**
+ * 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;
+
+ /**
+ * 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}.
@@ -120,43 +265,31 @@
*/
// 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 CommandProvider mProvider;
- 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;
+ 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();
}
/**
@@ -164,28 +297,7 @@
* @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
@@ -193,74 +305,65 @@
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));
- mCommands.add(new Command(COMMAND_CODE_PLAYBACK_SET_PLAYLIST_PARAMS));
+ 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;
}
/**
@@ -268,40 +371,16 @@
* @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);
}
}
@@ -313,6 +392,12 @@
*/
// 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.
@@ -325,7 +410,7 @@
*/
// 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;
}
@@ -338,22 +423,36 @@
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
@@ -361,7 +460,8 @@
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.
@@ -373,7 +473,48 @@
@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.
@@ -383,28 +524,41 @@
* <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.
@@ -414,51 +568,14 @@
* <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) { }
};
/**
@@ -467,36 +584,11 @@
* @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);
}
/**
@@ -508,9 +600,9 @@
*
* @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;
}
/**
@@ -526,9 +618,9 @@
* <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;
}
/**
@@ -538,9 +630,9 @@
*
* @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;
}
/**
@@ -553,12 +645,9 @@
* @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;
}
/**
@@ -568,17 +657,10 @@
* @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;
}
/**
@@ -588,7 +670,9 @@
* @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();
+ }
}
/**
@@ -597,31 +681,18 @@
* 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;
@@ -633,7 +704,7 @@
public ControllerInfo(Context context, int uid, int pid, String packageName,
IInterface callback) {
mProvider = ApiLoader.getProvider(context)
- .createMediaSession2ControllerInfoProvider(
+ .createMediaSession2ControllerInfo(
context, this, uid, pid, packageName, callback);
}
@@ -662,6 +733,9 @@
return mProvider.isTrusted_impl();
}
+ /**
+ * @hide
+ */
@SystemApi
public ControllerInfoProvider getProvider() {
return mProvider;
@@ -788,9 +862,9 @@
* @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));
@@ -860,7 +934,7 @@
/**
* Parameter for the playlist.
*/
- public static class PlaylistParams {
+ public final static class PlaylistParams {
/**
* @hide
*/
@@ -915,79 +989,71 @@
*/
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);
}
+ /**
+ * Create a new bundle for this object.
+ *
+ * @return
+ */
+ public @NonNull Bundle toBundle() {
+ return mProvider.toBundle_impl();
+ }
+
+ /**
+ * 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);
+ }
+
+ /**
+ * Get repeat mode
+ *
+ * @return repeat mode
+ * @see #REPEAT_MODE_NONE, #REPEAT_MODE_ONE, #REPEAT_MODE_ALL, #REPEAT_MODE_GROUP
+ */
public @RepeatMode int getRepeatMode() {
- return mRepeatMode;
+ return mProvider.getRepeatMode_impl();
}
+ /**
+ * Get shuffle mode
+ *
+ * @return shuffle mode
+ * @see #SHUFFLE_MODE_NONE, #SHUFFLE_MODE_ALL, #SHUFFLE_MODE_GROUP
+ */
public @ShuffleMode int getShuffleMode() {
- return mShuffleMode;
- }
-
- public MediaMetadata2 getPlaylistMetadata() {
- return mPlaylistMetadata;
+ return mProvider.getShuffleMode_impl();
}
/**
- * Returns this object as a bundle to share between processes.
+ * Get metadata for the playlist
*
- * @hide
+ * @return metadata. Can be {@code null}
*/
- 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;
- }
-
- /**
- * Creates an instance from a bundle which is previously created by {@link #toBundle()}.
- *
- * @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
- */
- 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();
}
}
@@ -1005,23 +1071,15 @@
* 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();
+ mProvider = provider;
}
- 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);
- }
-
+ /**
+ * @hide
+ */
@SystemApi
public MediaSession2Provider getProvider() {
return mProvider;
diff --git a/media/java/android/media/PlaybackState2.java b/media/java/android/media/PlaybackState2.java
index da776eb..627974a 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 @@
* @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 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 @@
* <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 @@
* content.
*/
public long getBufferedPosition() {
- return mBufferedPosition;
+ return mProvider.getBufferedPosition_impl();
}
/**
@@ -176,7 +178,7 @@
* @return The current speed of playback.
*/
public float getPlaybackSpeed() {
- return mSpeed;
+ return mProvider.getPlaybackSpeed_impl();
}
/**
@@ -184,7 +186,7 @@
* {@link PlaybackState2#STATE_ERROR}.
*/
public CharSequence getErrorMessage() {
- return mErrorMessage;
+ return mProvider.getErrorMessage_impl();
}
/**
@@ -194,7 +196,7 @@
* @return The last time the position was updated.
*/
public long getLastPositionUpdateTime() {
- return mUpdateTime;
+ return mProvider.getLastPositionUpdateTime_impl();
}
/**
@@ -203,53 +205,26 @@
* @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 93aea6f..4f77ecd 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 @@
* @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 static int RATING_PERCENTAGE = 6;
- private final static float RATING_NOT_RATED = -1.0f;
+ private final Rating2Provider mProvider;
- private final int mRatingStyle;
-
- private final float mRatingValue;
-
- private Rating2(@Style int ratingStyle, float rating) {
- mRatingStyle = ratingStyle;
- mRatingValue = rating;
+ /**
+ * @hide
+ */
+ @SystemApi
+ public Rating2(@NonNull Rating2Provider provider) {
+ mProvider = provider;
}
@Override
public String toString() {
- return "Rating2:style=" + mRatingStyle + " rating="
- + (mRatingValue < 0.0f ? "unrated" : String.valueOf(mRatingValue));
+ return mProvider.toString_impl();
+ }
+
+ /**
+ * @hide
+ */
+ @SystemApi
+ public Rating2Provider getProvider() {
+ return mProvider;
+ }
+
+ @Override
+ 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 @@
* @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 @@
* 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 @@
* @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 @@
*/
@Style
public int getRatingStyle() {
- return mRatingStyle;
+ return mProvider.getRatingStyle_impl();
}
/**
@@ -250,11 +241,7 @@
* 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 @@
* 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 @@
* 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 @@
* 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/VolumeProvider2.java b/media/java/android/media/VolumeProvider2.java
new file mode 100644
index 0000000..00746e2
--- /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 7e88c27..4de731a 100644
--- a/media/java/android/media/audiopolicy/AudioPolicy.java
+++ b/media/java/android/media/audiopolicy/AudioPolicy.java
@@ -89,6 +89,8 @@
private AudioPolicyFocusListener mFocusListener;
+ private final AudioPolicyVolumeCallback mVolCb;
+
private Context mContext;
private AudioPolicyConfig mConfig;
@@ -99,12 +101,15 @@
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 @@
mFocusListener = fl;
mStatusListener = sl;
mIsFocusPolicy = isFocusPolicy;
+ mVolCb = vc;
}
/**
@@ -134,6 +140,7 @@
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 @@
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 @@
+ "an AudioPolicyFocusListener");
}
return new AudioPolicy(new AudioPolicyConfig(mMixes), mContext, mLooper,
- mFocusListener, mStatusListener, mIsFocusPolicy);
+ mFocusListener, mStatusListener, mIsFocusPolicy, mVolCb);
}
}
@@ -455,6 +478,23 @@
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 void notifyVolumeAdjust(int adjustment) {
+ sendMsg(MSG_VOL_ADJUST, null /* ignored */, adjustment);
+ if (DEBUG) {
+ Log.v(TAG, "notifyVolumeAdjust: " + adjustment);
+ }
+ }
};
//==================================================
@@ -528,6 +575,7 @@
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 @@
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 86abbb4..107e7cd 100644
--- a/media/java/android/media/audiopolicy/IAudioPolicyCallback.aidl
+++ b/media/java/android/media/audiopolicy/IAudioPolicyCallback.aidl
@@ -31,4 +31,7 @@
// 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/update/FrameLayoutHelper.java b/media/java/android/media/update/FrameLayoutHelper.java
new file mode 100644
index 0000000..983dc70
--- /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 6b38c92..95fe363 100644
--- a/media/java/android/media/update/MediaControlView2Provider.java
+++ b/media/java/android/media/update/MediaControlView2Provider.java
@@ -36,12 +36,10 @@
// 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/MediaLibraryService2Provider.java b/media/java/android/media/update/MediaLibraryService2Provider.java
index a568839..87f509a 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 0000000..55ac43d
--- /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 8ff0ea5..da4d0c7 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 @@
/**
* @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);
@@ -61,6 +63,25 @@
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 @@
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/telephony/java/android/telephony/ims/internal/aidl/IImsServiceControllerListener.aidl b/media/java/android/media/update/PlaybackInfoProvider.java
similarity index 60%
copy from telephony/java/android/telephony/ims/internal/aidl/IImsServiceControllerListener.aidl
copy to media/java/android/media/update/PlaybackInfoProvider.java
index 01cca2db..36eb58a 100644
--- a/telephony/java/android/telephony/ims/internal/aidl/IImsServiceControllerListener.aidl
+++ b/media/java/android/media/update/PlaybackInfoProvider.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2017 The Android Open Source Project
+ * 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.
@@ -14,14 +14,18 @@
* limitations under the License.
*/
-package android.telephony.ims.internal.aidl;
+package android.media.update;
-import android.telephony.ims.internal.stub.ImsFeatureConfiguration;
+import android.media.AudioAttributes;
/**
- * See ImsService#Listener for more information.
- * {@hide}
+ * @hide
*/
-oneway interface IImsServiceControllerListener {
- void onUpdateSupportedImsFeatures(in ImsFeatureConfiguration c);
+// 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 0000000..2875e98
--- /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/telephony/java/com/android/ims/ImsStreamMediaProfile.aidl b/media/java/android/media/update/ProviderCreator.java
similarity index 74%
copy from telephony/java/com/android/ims/ImsStreamMediaProfile.aidl
copy to media/java/android/media/update/ProviderCreator.java
index d648a35..f5f3e47 100644
--- a/telephony/java/com/android/ims/ImsStreamMediaProfile.aidl
+++ b/media/java/android/media/update/ProviderCreator.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2013 The Android Open Source Project
+ * 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.
@@ -14,6 +14,10 @@
* limitations under the License.
*/
-package com.android.ims;
+package android.media.update;
-parcelable ImsStreamMediaProfile;
+/** @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 0000000..8966196
--- /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/StaticProvider.java b/media/java/android/media/update/StaticProvider.java
index 14b23c8..922b452 100644
--- a/media/java/android/media/update/StaticProvider.java
+++ b/media/java/android/media/update/StaticProvider.java
@@ -17,7 +17,6 @@
package android.media.update;
import android.annotation.Nullable;
-import android.app.PendingIntent;
import android.content.Context;
import android.media.DataSourceDesc;
import android.media.MediaBrowser2;
@@ -27,17 +26,24 @@
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;
@@ -60,29 +66,64 @@
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,
String packageName, String serviceName, int uid);
SessionToken2 SessionToken2_fromBundle(Context context, Bundle bundle);
SessionPlayer2Provider createSessionPlayer2(Context context, SessionPlayer2 instance);
- MediaItem2Provider createMediaItem2Provider(Context context, MediaItem2 mediaItem2,
+ 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 c5b58f7..10f03d2 100644
--- a/media/java/android/media/update/VideoView2Provider.java
+++ b/media/java/android/media/update/VideoView2Provider.java
@@ -47,8 +47,7 @@
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/telephony/java/android/telephony/ims/internal/aidl/IImsRcsFeature.aidl b/media/java/android/media/update/VolumeProvider2Provider.java
similarity index 64%
copy from telephony/java/android/telephony/ims/internal/aidl/IImsRcsFeature.aidl
copy to media/java/android/media/update/VolumeProvider2Provider.java
index f6005b6..5657af6 100644
--- a/telephony/java/android/telephony/ims/internal/aidl/IImsRcsFeature.aidl
+++ b/media/java/android/media/update/VolumeProvider2Provider.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2017 The Android Open Source Project
+ * 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.
@@ -13,13 +13,15 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-
-package android.telephony.ims.internal.aidl;
+package android.media.update;
/**
- * See RcsFeature for more information.
- * {@hide}
+ * @hide
*/
-interface IImsRcsFeature {
- //Empty Default Implementation
-}
\ No newline at end of file
+// 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 051c802..7e5f581 100644
--- a/media/jni/Android.bp
+++ b/media/jni/Android.bp
@@ -34,6 +34,7 @@
"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 95b07f1..4f06caa 100644
--- a/media/jni/android_media_MediaDrm.cpp
+++ b/media/jni/android_media_MediaDrm.cpp
@@ -1134,6 +1134,27 @@
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 @@
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 @@
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 @@
{ "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 42ebf6a..ff854c5 100644
--- a/media/jni/android_media_MediaMetadataRetriever.cpp
+++ b/media/jni/android_media_MediaMetadataRetriever.cpp
@@ -68,17 +68,26 @@
}
}
-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 @@
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 @@
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_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 @@
{
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 @@
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 @@
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 @@
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 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 @@
{
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_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 ea1ac2b..e88c0f1 100644
--- a/media/lib/remotedisplay/Android.mk
+++ b/media/lib/remotedisplay/Android.mk
@@ -22,9 +22,7 @@
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 b0d3177..69ca4d2 100644
--- a/media/lib/signer/Android.mk
+++ b/media/lib/signer/Android.mk
@@ -22,9 +22,7 @@
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 06838c2..1ffdd62 100644
--- a/media/lib/tvremote/Android.mk
+++ b/media/lib/tvremote/Android.mk
@@ -22,9 +22,7 @@
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_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 5680d9f..b3f443b 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 @@
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 8141fa7..e67b3be 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 9fa7a664..d6b6bf8 100644
--- a/packages/BackupRestoreConfirmation/src/com/android/backupconfirm/BackupRestoreConfirmation.java
+++ b/packages/BackupRestoreConfirmation/src/com/android/backupconfirm/BackupRestoreConfirmation.java
@@ -52,7 +52,9 @@
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 @@
int mToken;
boolean mIsEncrypted;
boolean mDidAcknowledge;
+ String mAction;
TextView mStatusView;
TextView mCurPassword;
@@ -134,26 +137,9 @@
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 @@
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 @@
// 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 @@
@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/SettingsLib/Android.mk b/packages/SettingsLib/Android.mk
index 69287e8..4338d26 100644
--- a/packages/SettingsLib/Android.mk
+++ b/packages/SettingsLib/Android.mk
@@ -14,10 +14,10 @@
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 14f0625..3c2ca2d 100644
--- a/packages/SettingsLib/common.mk
+++ b/packages/SettingsLib/common.mk
@@ -64,7 +64,7 @@
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 fce5dd9..7728f66 100644
--- a/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtils.java
@@ -363,6 +363,26 @@
}
/**
+ * 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 5c73d54..3a0ae9f 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.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 @@
// 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 754b881..e11017c 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.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.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.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 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 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 @@
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 @@
*/
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 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 @@
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 @@
}
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 @@
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 @@
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 @@
*/
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 @@
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 @@
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 @@
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 @@
}
int rssi = UNREACHABLE_RSSI;
- for (ScanResult result : mScanResultCache.values()) {
+ for (ScanResult result : mScanResults) {
if (result.level > rssi) {
rssi = result.level;
}
@@ -853,7 +901,6 @@
}
if (WifiTracker.sVerboseLogging) {
- evictOldScanResults();
summary.append(WifiUtils.buildLoggingSummary(this, config));
}
@@ -950,28 +997,6 @@
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 @@
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 @@
}
/**
- * 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();
+ void setScanResults(Collection<ScanResult> scanResults) {
- /* 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);
- }
+ // 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 dd55188..109eb97 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 @@
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 3dec1d3..2993a0d 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.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 @@
private String mCarrierName = null;
Context mContext;
- private ArrayList<ScanResult> mScanResultCache;
+ private ArrayList<ScanResult> mScanResults;
private ArrayList<TimestampedScoredNetwork> mScoredNetworkCache;
@Keep
@@ -84,8 +85,9 @@
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 @@
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 c56e1da..1ac56a9 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.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 @@
*/
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 @@
= 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 @@
}
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 @@
/* 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 @@
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 @@
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 @@
scoresToRequest.add(key);
}
- 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;
- }
+ String apKey = AccessPoint.getKey(result);
+ List<ScanResult> resultList;
+ if (scanResultsByApKey.containsKey(apKey)) {
+ resultList = scanResultsByApKey.get(apKey);
+ } else {
+ resultList = new ArrayList<>();
+ scanResultsByApKey.put(apKey, resultList);
}
- if (!found && mIncludeScans) {
- AccessPoint accessPoint = getCachedOrCreate(result, cachedAccessPoints);
+
+ 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 : existingApMap.getAll(firstResult.SSID)) {
+ accessPoint.setScanResults(entry.getValue());
+ found = true;
+ break;
+ }
+
+ // 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 @@
}
accessPoints.add(accessPoint);
- apMap.put(accessPoint.getSsidStr(), accessPoint);
}
}
}
@@ -662,17 +699,18 @@
}
@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 932c6fd..fd48eea 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 @@
// 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 ec594a6..1440311 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.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 @@
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 @@
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 @@
}
@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 @@
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 @@
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 @@
.setActive(true)
.setNetworkId(networkId)
.setSsid(TEST_SSID)
- .setScanResultCache(scanResults)
+ .setScanResults(scanResults)
.setWifiInfo(info)
.build();
@@ -990,7 +995,7 @@
.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 @@
.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 6615b8c..b36dda9 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.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.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 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 @@
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 a4c821f..3fee16b 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 @@
}
@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 c5795d3..9310b73 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.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 @@
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/SystemUI/Android.mk b/packages/SystemUI/Android.mk
index 251ae2d..2bcf4ef 100644
--- a/packages/SystemUI/Android.mk
+++ b/packages/SystemUI/Android.mk
@@ -48,8 +48,8 @@
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 1fc36be..9613a6a 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/qs/QSTileView.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTileView.java
index 18d27bb..53f7e44 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 @@
@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 eda8c69..faa2c17 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 5aca7f9..28a0935 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 3a41681..8a48e7b 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/rounded_corners.xml b/packages/SystemUI/res/layout/rounded_corners.xml
index d1ef5d6..734c877 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 1d1f95b..3e80085 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/config.xml b/packages/SystemUI/res/values/config.xml
index 6768470..1cc1cc8 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 edda613..f9aa821 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 9b6af43..72615ce 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 5f75f7d..21b0ed8 100644
--- a/packages/SystemUI/shared/Android.mk
+++ b/packages/SystemUI/shared/Android.mk
@@ -30,7 +30,7 @@
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 @@
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/src/com/android/keyguard/KeyguardEsimArea.java b/packages/SystemUI/src/com/android/keyguard/KeyguardEsimArea.java
index cb5afec..b8a07cd 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 @@
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 @@
@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 e7432ba..703b205 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinView.java
@@ -131,7 +131,7 @@
}
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 @@
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 afee8ec..347c979 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukView.java
@@ -181,7 +181,7 @@
}
}
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 @@
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 8666b0c..1ae06d7 100644
--- a/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java
+++ b/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java
@@ -119,18 +119,10 @@
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 @@
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 5d2e4d0..0000000
--- 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/RoundedCorners.java b/packages/SystemUI/src/com/android/systemui/RoundedCorners.java
deleted file mode 100644
index c960fa1..0000000
--- 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 0000000..0b3e9e5
--- /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 085ece7..0000000
--- 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 ac22568..0000000
--- 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 238ab29..c28b7ee 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 @@
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 @@
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 @@
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());
@@ -871,10 +874,9 @@
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 95185c0..001b409 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 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 @@
clearAnimationState();
mAllViews.clear();
- mTopFiveQs.clear();
+ mQuickQsViews.clear();
QSTileLayout tileLayout = mQsPanel.getTileLayout();
mAllViews.add((View) tileLayout);
@@ -198,7 +202,7 @@
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 @@
@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 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 @@
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 77768b1..4d7333b 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 @@
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 @@
//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 b4cfda6..c9c678c 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 @@
return mIcon;
}
+ public View getIconWithBackground() {
+ return mIconFrame;
+ }
+
@Override
public boolean performClick() {
mClicked = true;
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java
index 0132fa8..bf4a225 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 @@
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 @@
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 @@
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 15e92f4..406eef8 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 @@
private volatile boolean mIsVrModeEnabled;
private boolean mListening;
private boolean mExternalChange;
- private boolean mControlInitialized;
+ private boolean mControlValueInitialized;
private ValueAnimator mSliderAnimator;
@@ -337,6 +337,7 @@
mBackgroundHandler.post(mStopListeningRunnable);
mListening = false;
+ mControlValueInitialized = false;
}
@Override
@@ -428,10 +429,10 @@
}
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 3ebeb4d..3dfb913 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 @@
// 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 907af69..11d20b2 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 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 @@
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 0000000..c45c538
--- /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 c85571c..2bfdefe 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 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 @@
*/
public class HeadsUpTouchHelper implements Gefingerpoken {
- private HeadsUpManager mHeadsUpManager;
+ private HeadsUpManagerPhone mHeadsUpManager;
private NotificationStackScrollLayout mStackScroller;
private int mTrackingPointer;
private float mTouchSlop;
@@ -43,7 +43,7 @@
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 b8b309b..9d20e4e 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 @@
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/NotificationPanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
index 31b8159..2111d2e 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.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 @@
private void updatePanelExpanded() {
boolean isExpanded = !isFullyCollapsed();
if (mPanelExpanded != isExpanded) {
- mHeadsUpManager.setIsExpanded(isExpanded);
+ mHeadsUpManager.setIsPanelExpanded(isExpanded);
mStatusBar.setPanelExpanded(isExpanded);
mPanelExpanded = isExpanded;
}
@@ -2338,7 +2338,7 @@
}
@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 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 2b7e474..6daabed 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.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 @@
}
protected StatusBar mStatusBar;
- protected HeadsUpManager mHeadsUpManager;
+ protected HeadsUpManagerPhone mHeadsUpManager;
private float mPeekHeight;
private float mHintDistance;
@@ -1252,7 +1252,7 @@
*/
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 20b5018..6444cc8 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 @@
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 b519824..116e3f9 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.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.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 @@
.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 @@
@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 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 @@
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 @@
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 @@
@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 @@
// for heads up notifications
- protected HeadsUpManager mHeadsUpManager;
+ protected HeadsUpManagerPhone mHeadsUpManager;
private AboveShelfObserver mAboveShelfObserver;
@@ -4900,7 +4903,7 @@
// 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 e32914f..a79a41b 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.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 @@
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 6a573f5..d85e18c 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 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 53dfb24..a2b896d 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;
+ 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 final int mTouchAcceptanceDelay;
+ protected int mHeadsUpNotificationDecay;
+ protected int mMinimumDisplayTime;
+ protected int mTouchAcceptanceDelay;
+ protected int mSnoozeLengthMs;
+ protected boolean mHasPinnedNotification;
+ protected int mUser;
+
+ private final HashMap<String, HeadsUpEntry> mHeadsUpEntries = new HashMap<>();
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>() {
+ private final ContentObserver mSettingsObserver;
- private Stack<HeadsUpEntry> mPoolObjects = new Stack<>();
-
- @Override
- public HeadsUpEntry acquire() {
- if (!mPoolObjects.isEmpty()) {
- return mPoolObjects.pop();
- }
- return new HeadsUpEntry();
- }
-
- @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 @@
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 @@
/**
* 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 @@
// 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 @@
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 @@
}
}
- 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 @@
* @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;
- }
+ public boolean removeNotification(@NonNull String key, boolean ignoreEarliestRemovalTime) {
+ if (DEBUG) Log.v(TAG, "removeNotification");
+ releaseImmediately(key);
+ return true;
}
- 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();
- }
-
+ /**
+ * 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 @@
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 @@
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;
}
- public Collection<HeadsUpEntry> getAllEntries() {
- return mHeadsUpEntries.values();
+ /**
+ * Returns the stream of all current Heads Up Notifications.
+ */
+ @NonNull
+ public Stream<NotificationData.Entry> getAllEntries() {
+ return mHeadsUpEntries.values().stream().map(headsUpEntry -> headsUpEntry.entry);
}
- public HeadsUpEntry getTopEntry() {
+ /**
+ * 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;
+ }
+
+ /**
+ * Returns if any heads up notification is available or not.
+ */
+ public boolean hasHeadsUpNotifications() {
+ return !mHeadsUpEntries.isEmpty();
+ }
+
+ protected HeadsUpEntry getTopHeadsUpEntry() {
if (mHeadsUpEntries.isEmpty()) {
return null;
}
@@ -387,56 +338,21 @@
}
/**
- * 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 @@
}
}
+ /**
+ * Returns if there are any pinned Heads Up Notifications or not.
+ */
public boolean hasPinnedHeadsUp() {
return mHasPinnedNotification;
}
@@ -464,14 +383,8 @@
}
/**
- * 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 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 @@
}
/**
- * 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 */);
- }
+ 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);
}
}
- @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);
+ @NonNull
+ protected HeadsUpEntry createHeadsUpEntry() {
+ return new HeadsUpEntry();
}
- public void setVisualStabilityManager(VisualStabilityManager visualStabilityManager) {
- mVisualStabilityManager = visualStabilityManager;
- }
-
- 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 @@
}
@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 @@
: -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 @@
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 0000000..1e3c123c
--- /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 424858a..d7a810e 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 @@
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 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 ad8a0eb..b28e1a9 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.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 @@
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 @@
}
};
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 @@
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 @@
} 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 @@
}
private void updateAlgorithmHeightAndPadding() {
- if (mPulsing != null) {
+ if (mPulsing) {
mTopPadding = mClockBottom;
} else {
mTopPadding = mAmbientState.isDark() ? mDarkTopPadding : mRegularTopPadding;
@@ -919,6 +920,27 @@
}
/**
+ * @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 @@
int minNotificationsForShelf = 1;
if (mTrackingHeadsUp
|| (mHeadsUpManager.hasPinnedHeadsUp() && !mAmbientState.isDark())) {
- appearPosition = mHeadsUpManager.getTopHeadsUpPinnedHeight();
+ appearPosition = getTopHeadsUpPinnedHeight();
minNotificationsForShelf = 2;
} else {
appearPosition = 0;
@@ -1197,9 +1219,9 @@
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 @@
@Override
public boolean hasPulsingNotifications() {
- return mPulsing != null;
+ return mPulsing;
}
private void updateScrollability() {
@@ -2304,8 +2326,9 @@
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 @@
}
private boolean isClickedHeadsUp(View child) {
- return HeadsUpManager.isClickedHeadsUpNotification(child);
+ return HeadsUpUtil.isClickedHeadsUpNotification(child);
}
/**
@@ -3858,17 +3881,12 @@
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 @@
}
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 @@
mAnimationFinishedRunnables.add(runnable);
}
- public void setHeadsUpManager(HeadsUpManager headsUpManager) {
+ public void setHeadsUpManager(HeadsUpManagerPhone headsUpManager) {
mHeadsUpManager = headsUpManager;
mAmbientState.setHeadsUpManager(headsUpManager);
}
@@ -4324,8 +4342,8 @@
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 @@
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 682b849..04a7bd7 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.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 @@
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/VolumeDialogControllerImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
index a166db5..9aee00e 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
@@ -59,7 +59,9 @@
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;
@@ -76,7 +78,7 @@
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);
@@ -109,8 +111,10 @@
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;
@@ -1055,26 +1059,25 @@
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 8cfdeeb6..001a582 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.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.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 @@
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 @@
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 @@
initRingerH();
}
+ protected ViewGroup getDialogView() {
+ return mDialogView;
+ }
+
private ColorStateList loadColorStateList(int colorResId) {
return ColorStateList.valueOf(mContext.getColor(colorResId));
}
@@ -258,6 +261,7 @@
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 @@
}
}
- private void onStateChangedH(State state) {
+ protected void onStateChangedH(State state) {
mState = state;
mDynamic.clear();
// add any new dynamic rows
@@ -894,7 +898,7 @@
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 066cfe5..936ff51 100644
--- a/packages/SystemUI/tests/Android.mk
+++ b/packages/SystemUI/tests/Android.mk
@@ -50,8 +50,8 @@
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
similarity index 66%
rename from packages/SystemUI/tests/src/com/android/systemui/RoundedCornersTest.java
rename to packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
index 2a44771..2f05b06 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.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.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 @@
@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 @@
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 @@
@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 @@
}
@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 @@
// 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 8e0426a..0000000
--- 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 6e7477f..f3c1171 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.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 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 0000000..28f9417
--- /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 bdf9b1f..31442af 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.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 @@
@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 @@
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 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 0000000..2d28c9f
--- /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 0fdbfd1..4ab2063 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.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 c54ddad..0168392 100644
--- a/proto/src/gnss.proto
+++ b/proto/src/gnss.proto
@@ -42,4 +42,20 @@
// 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 03dfd46..7539d88 100644
--- a/proto/src/metrics_constants.proto
+++ b/proto/src/metrics_constants.proto
@@ -5168,6 +5168,11 @@
// 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 0e2ca14..2b73b33 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 @@
}
@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 6b44fa5..6108afa 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 @@
* 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 @@
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 @@
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 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 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 14b76ab..3b80f55 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 @@
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 342b48e..30dfee8 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.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.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.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 @@
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 @@
AppOpsManager mAppOps;
DeviceIdleController.LocalService mLocalDeviceIdleController;
+ private UsageStatsManagerInternal mUsageStatsManagerInternal;
final Object mLock = new Object();
@@ -215,6 +222,14 @@
}
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 @@
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 @@
= "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 @@
// 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 @@
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 @@
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 @@
}
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 @@
}
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 @@
rescheduleKernelAlarmsLocked();
updateNextAlarmClockLocked();
+ mStatLogger.logDurationStat(Stats.REBATCH_ALL_ALARMS, start);
}
void reAddAlarmLocked(Alarm a, long nowElapsed, boolean doValidate) {
@@ -905,7 +950,7 @@
// 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 @@
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 @@
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 @@
mAllowWhileIdleDispatches.add(ent);
}
}
+ adjustDeliveryTimeBasedOnStandbyBucketLocked(a);
int whichBatch = ((a.flags&AlarmManager.FLAG_STANDALONE) != 0)
? -1 : attemptCoalesceLocked(a.whenElapsed, a.maxWhenElapsed);
@@ -1655,6 +1748,9 @@
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 @@
}
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 @@
}
}
}
+ pw.println();
+ mStatLogger.dump(pw, " ");
if (RECORD_DEVICE_IDLE_ALARMS) {
pw.println();
@@ -2746,8 +2853,7 @@
// 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 @@
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 @@
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 @@
|| _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 @@
alarmClock = _info;
uid = _uid;
packageName = _pkgName;
-
+ sourcePackage = (operation != null) ? operation.getCreatorPackage() : packageName;
creatorUid = (operation != null) ? operation.getCreatorUid() : uid;
}
@@ -2980,9 +3091,7 @@
}
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 @@
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 @@
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 @@
// 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 @@
}
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 @@
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 @@
}
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 @@
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 @@
}
}
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 @@
}
};
+ /**
+ * 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 @@
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 @@
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/BatteryService.java b/services/core/java/com/android/server/BatteryService.java
index ba9883b..8265262 100644
--- a/services/core/java/com/android/server/BatteryService.java
+++ b/services/core/java/com/android/server/BatteryService.java
@@ -835,6 +835,9 @@
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 @@
}
@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 fd3f708..44974ff 100644
--- a/services/core/java/com/android/server/DeviceIdleController.java
+++ b/services/core/java/com/android/server/DeviceIdleController.java
@@ -1352,14 +1352,21 @@
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 @@
mLocalActivityManager.setDeviceIdleWhitelist(mPowerSaveWhitelistAllAppIdArray);
mLocalPowerManager.setDeviceIdleWhitelist(mPowerSaveWhitelistAllAppIdArray);
+ mLocalActivityManager.registerScreenObserver(mScreenObserver);
+
passWhiteListToForceAppStandbyTrackerLocked();
updateInteractivityLocked();
}
@@ -2028,7 +2037,6 @@
}
}
-
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 732ac66..219facd 100644
--- a/services/core/java/com/android/server/EventLogTags.logtags
+++ b/services/core/java/com/android/server/EventLogTags.logtags
@@ -34,6 +34,7 @@
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/PinnerService.java b/services/core/java/com/android/server/PinnerService.java
index 3d7408e..2869114 100644
--- a/services/core/java/com/android/server/PinnerService.java
+++ b/services/core/java/com/android/server/PinnerService.java
@@ -372,15 +372,19 @@
@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 8ec4ef6..84b93e3 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.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.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.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 @@
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) {
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 06e2edf..5b8b691 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.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 @@
// 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 @@
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
@@ -1881,8 +1856,6 @@
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 @@
}
}
} 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;
}
}
};
@@ -4845,54 +4805,16 @@
}
- /**
- * 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);
- }
+ 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) {
- 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;
- }
-
- @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;
@@ -4900,47 +4822,17 @@
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.
@@ -4951,7 +4843,6 @@
+ sourceRecord.launchedFromUid);
}
}
-
if (ignoreTargetSecurity) {
if (intent.getComponent() == null) {
throw new SecurityException(
@@ -6446,7 +6337,8 @@
if (!keepState) {
synchronized (this) {
// Remove all permissions granted from/to this package
- removeUriPermissionsForPackageLocked(packageName, resolvedUserId, true);
+ removeUriPermissionsForPackageLocked(packageName, resolvedUserId, true,
+ false);
}
// Reset notification state
@@ -7101,7 +6993,7 @@
}
// 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--) {
@@ -9824,9 +9716,11 @@
* @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");
}
@@ -9845,7 +9739,7 @@
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
@@ -10099,8 +9993,7 @@
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));
@@ -10283,7 +10176,9 @@
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
@@ -13017,7 +12912,6 @@
long ident = Binder.clearCallingIdentity();
try {
mKeyguardController.setKeyguardShown(showing, secondaryDisplayShowing);
- mLocalDeviceIdleController.keyguardShowing(showing);
} finally {
Binder.restoreCallingIdentity(ident);
}
@@ -14849,6 +14743,8 @@
mUserController.sendUserSwitchBroadcasts(-1, currentUserId);
BinderInternal.nSetBinderProxyCountEnabled(true);
+ //STOPSHIP: Temporary BinderProxy Threshold for b/71353150
+ BinderInternal.nSetBinderProxyCountWatermarks(1500, 1200);
BinderInternal.setBinderProxyCountCallback(
new BinderInternal.BinderProxyLimitListener() {
@Override
@@ -20876,7 +20772,8 @@
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);
@@ -25527,6 +25424,7 @@
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 3bef877..cae0d2b 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 @@
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 ec8cf91..ab2dc36 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 @@
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 @@
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 @@
wm.deferSurfaceLayout();
try {
if (!animate && topActivity != null) {
- mNoAnimActivities.add(topActivity);
+ mStackSupervisor.mNoAnimActivities.add(topActivity);
}
super.setWindowingMode(windowingMode);
@@ -2457,7 +2454,7 @@
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 @@
} 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 @@
}
} 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 @@
}
}
- 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 @@
"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 @@
}
}
mWindowManager.prepareAppTransition(transit, keepCurTransition);
- mNoAnimActivities.remove(r);
+ mStackSupervisor.mNoAnimActivities.remove(r);
}
boolean doShow = true;
if (newTask) {
@@ -4494,7 +4488,7 @@
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 @@
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 510a3fa..6beafcb 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 @@
* 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 0480646..b86a8a6 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.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 @@
}
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 @@
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 @@
}
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 @@
*
* @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 @@
mAInfo = mSupervisor.resolveActivity(mIntent, mRInfo, mStartFlags, null /*profilerInfo*/);
return true;
}
-
}
diff --git a/services/core/java/com/android/server/am/KeyguardController.java b/services/core/java/com/android/server/am/KeyguardController.java
index 79f3fe3..05305f3 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 @@
proto.write(KEYGUARD_OCCLUDED, mOccluded);
proto.end(token);
}
+
+ public void notifyAppTransitionDone() {
+ setKeyguardGoingAway(false);
+ }
}
diff --git a/services/core/java/com/android/server/am/RecentsAnimation.java b/services/core/java/com/android/server/am/RecentsAnimation.java
index fe576fda..e7b067b 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 @@
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);
- 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);
- 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?
+ }
- // TODO: Maybe wait for app to draw in this particular case?
+ final ActivityRecord homeActivity = mStackSupervisor.getHomeActivity();
+ final ActivityDisplay display = homeActivity.getDisplay();
+
+ // Save the initial position of the home activity stack to be restored to after the
+ // animation completes
+ mRestoreHomeBehindStack = hasExistingHomeActivity
+ ? display.getStackAboveHome()
+ : null;
+
+ // Move the home activity into place for the animation
+ display.moveHomeStackBehindBottomMostVisibleStack();
+
+ // 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;
+
+ // Fetch all the surface controls and pass them to the client to get the animation
+ // started
+ mWindowManager.initializeRecentsAnimation(recentsAnimationRunner, this,
+ display.mDisplayId);
+
+ // 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();
}
-
- final ActivityRecord homeActivity = mStackSupervisor.getHomeActivity();
- final ActivityDisplay display = homeActivity.getDisplay();
-
- // Save the initial position of the home activity stack to be restored to after the
- // animation completes
- mRestoreHomeBehindStack = hasExistingHomeActivity
- ? display.getStackAboveHome()
- : null;
-
- // Move the home activity into place for the animation
- display.moveHomeStackBehindBottomMostVisibleStack();
-
- // 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;
-
- // Fetch all the surface controls and pass them to the client to get the animation
- // started
- mWindowManager.initializeRecentsAnimation(recentsAnimationRunner, this, display.mDisplayId);
-
- // 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);
}
@Override
@@ -128,31 +134,40 @@
if (mWindowManager.getRecentsAnimationController() == null) return;
mWindowManager.inSurfaceTransaction(() -> {
- mWindowManager.cleanupRecentsAnimation();
+ mWindowManager.deferSurfaceLayout();
+ try {
+ mWindowManager.cleanupRecentsAnimation();
- // Move the home stack to the front
- final ActivityRecord homeActivity = mStackSupervisor.getHomeActivity();
- if (homeActivity == null) {
- return;
+ // 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();
}
-
- // 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.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 809f19f6..d679439 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 @@
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 f6bcb25..1825db8 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -1329,8 +1329,20 @@
/** @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,
@@ -2280,7 +2292,9 @@
// 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));
@@ -2654,7 +2668,9 @@
}
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;
@@ -6964,7 +6980,7 @@
// 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()
@@ -6987,7 +7003,7 @@
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);
@@ -7053,6 +7069,23 @@
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) {
@@ -7185,8 +7218,9 @@
*/
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
@@ -7198,11 +7232,12 @@
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
@@ -7211,6 +7246,9 @@
mMediaFocusControl.setFocusPolicy(mPolicyCallback);
}
}
+ if (mIsVolumeController) {
+ setExtVolumeController(mPolicyCallback);
+ }
connectMixes();
}
@@ -7220,6 +7258,11 @@
release();
mAudioPolicies.remove(mPolicyCallback.asBinder());
}
+ if (mIsVolumeController) {
+ synchronized (mExtVolumeControllerLock) {
+ mExtVolumeController = null;
+ }
+ }
}
String getRegistrationId() {
@@ -7236,11 +7279,15 @@
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 bd2e96e..e43d152 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 @@
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/job/JobSchedulerService.java b/services/core/java/com/android/server/job/JobSchedulerService.java
index 8fa3318..e0baeee 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 @@
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 f4bbf4c..cf27882 100644
--- a/services/core/java/com/android/server/job/JobStore.java
+++ b/services/core/java/com/android/server/job/JobStore.java
@@ -62,6 +62,7 @@
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
@@ -85,7 +86,7 @@
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
@@ -1000,10 +1001,11 @@
}
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>>();
@@ -1046,7 +1048,13 @@
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) {
@@ -1075,27 +1083,40 @@
/**
* 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 cfa1a79..ec72b22 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 @@
throws RemoteException, ServiceSpecificException {
byte[] locallyEncryptedKey;
try {
+ // TODO: Remove the extraneous logging here
+ 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) {
- // 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));
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 @@
}
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 @@
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 @@
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 0000000..52381b8
--- /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/net/watchlist/NetworkWatchlistService.java b/services/core/java/com/android/server/net/watchlist/NetworkWatchlistService.java
index 7165e60..5f4e471 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.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 @@
return;
}
try {
+ mService.init();
mService.initIpConnectivityMetrics();
mService.startWatchlistLogging();
} catch (RemoteException e) {
@@ -127,6 +130,10 @@
mIpConnectivityMetrics = ipConnectivityMetrics;
}
+ private void init() {
+ mConfig.removeTestModeConfig();
+ }
+
private void initIpConnectivityMetrics() {
mIpConnectivityMetrics = (IIpConnectivityMetrics) IIpConnectivityMetrics.Stub.asInterface(
ServiceManager.getService(IpConnectivityLog.SERVICE_NAME));
@@ -151,6 +158,22 @@
}
};
+ 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 0000000..9533823
--- /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 7387ad4..2714d5e 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.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 @@
// 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 @@
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 @@
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 3b6d59e..c4de4ac 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 @@
}
/**
+ * 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 @@
}
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 c73b0cf..4b577bb 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 @@
}
/**
- * 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 @@
}
/**
- * 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/pm/InstantAppRegistry.java b/services/core/java/com/android/server/pm/InstantAppRegistry.java
index af20cd7..30088dd 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.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.XmlUtils;
import libcore.io.IoUtils;
+
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlSerializer;
@@ -295,25 +297,26 @@
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/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 3049e98..01c44af 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.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.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 @@
}
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 @@
}
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 @@
}
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 @@
}
// 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 @@
}
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);
+ if (expectedCertDigests.length > 1) {
- final int certCount = libCertDigests.length;
- for (int j = 0; j < certCount; j++) {
- if (!libCertDigests[j].equalsIgnoreCase(expectedCertDigests[j])) {
+ // 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 @@
// 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 @@
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 @@
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 @@
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 76c199b..c02331d 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 @@
// 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 @@
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 @@
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 @@
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 @@
}
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 @@
}
// 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 18356c5..f14a684 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 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 @@
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 bfc858f..95f490e 100644
--- a/services/core/java/com/android/server/pm/PackageSignatures.java
+++ b/services/core/java/com/android/server/pm/PackageSignatures.java
@@ -291,11 +291,13 @@
PackageManagerService.reportSettingsProblem(Log.WARN,
"<pastSigs> encountered multiple times under the same <sigs> at "
+ parser.getPositionDescription());
+ XmlUtils.skipCurrentTag(parser);
}
} else {
PackageManagerService.reportSettingsProblem(Log.WARN,
"Unknown element under <sigs>: "
+ parser.getName());
+ XmlUtils.skipCurrentTag(parser);
}
}
return pos;
diff --git a/services/core/java/com/android/server/pm/SELinuxMMAC.java b/services/core/java/com/android/server/pm/SELinuxMMAC.java
index 2552643..fe5b3d4 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 @@
/** 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 @@
// 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 @@
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 @@
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 @@
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/permission/DefaultPermissionGrantPolicy.java b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
index e2123c2..6308766 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 @@
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 cb3b107..08f8bbd 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 @@
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 0f394a4..0502848 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 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 @@
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 @@
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_NOSENSOR:
- // TODO Can sensor be used to indirectly determine the orientation?
- return false;
-
- 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
- case ActivityInfo.SCREEN_ORIENTATION_USER:
- // 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
+ 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:
+ // 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);
}
+
+ 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 a538967..847c90a 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 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 @@
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 b986e04..bd4aa1c 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.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 @@
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 @@
}
}
+ if (SecurityLog.isLoggingEnabled()) {
+ SecurityLog.writeEvent(SecurityLog.TAG_OS_SHUTDOWN);
+ }
+
// start the thread that initiates shutdown
sInstance.mHandler = new Handler() {
};
@@ -410,6 +416,7 @@
public void run() {
TimingsTraceLog shutdownTimingLog = newTimingsLog();
shutdownTimingLog.traceBegin("SystemServerShutdown");
+ metricShutdownStart();
metricStarted(METRIC_SYSTEM_SERVER);
BroadcastReceiver br = new BroadcastReceiver() {
@@ -525,7 +532,7 @@
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 @@
}
}
+ 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 @@
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 d4627c2..32f38b7 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.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.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 @@
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 @@
@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 @@
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 @@
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 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 @@
enabled = mEnabled;
mIsInteractive = isInteractive;
-
if (enabled) {
fileValues = mBatterySaverPolicy.getFileValues(isInteractive);
} else {
@@ -293,6 +318,8 @@
pmi.powerHint(PowerHint.LOW_POWER, enabled ? 1 : 0);
}
+ updateBatterySavingStats();
+
if (ArrayUtils.isEmpty(fileValues)) {
mFileUpdater.restoreDefault();
} else {
@@ -332,7 +359,6 @@
mContext.sendBroadcastAsUser(intent, UserHandle.ALL,
Manifest.permission.DEVICE_POWER);
-
for (LowPowerModeListener listener : listeners) {
final PowerSaveState result =
mBatterySaverPolicy.getBatterySaverPolicy(
@@ -388,4 +414,28 @@
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 0000000..9466350
--- /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 5811714..8da16d7 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 @@
@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 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 @@
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 @@
synchronized (mLock) {
mPinnedPkgs.remove(pkg);
}
- return !isPinned();
+ return !hasPinOrListener();
}
public boolean isListening() {
@@ -156,8 +171,32 @@
}
}
+ 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 @@
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 @@
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 @@
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 @@
}
}
}
+
+ 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 0000000..591e809
--- /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 c191580..5db0fc0 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 @@
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.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.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 @@
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 @@
}
}
};
+ 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 @@
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 @@
throws RemoteException {
verifyCaller(pkg);
uri = maybeAddUserId(uri, Binder.getCallingUserHandle().getIdentifier());
- enforceAccess(pkg, uri);
if (getPinnedSlice(uri).removeSliceListener(listener)) {
removePinnedSlice(uri);
}
@@ -169,14 +203,14 @@
@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 @@
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 @@
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 @@
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;
+ 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);
+ }
}
- } 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 @@
}
}
}
- // 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 @@
}
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 @@
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 @@
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 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 0000000..853c7eb
--- /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/updates/SmartSelectionInstallReceiver.java b/services/core/java/com/android/server/updates/SmartSelectionInstallReceiver.java
index 1457366..eff9a9a 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 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 fc7ad09..2bdaa1a 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_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 @@
+ " 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/Dimmer.java b/services/core/java/com/android/server/wm/Dimmer.java
index 9fe16ae..b435605 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 DimState {
- SurfaceControl mSurfaceControl;
+ 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();
+ }
+ }
+
+ @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 @@
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 @@
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 @@
*/
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 @@
* 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 @@
/**
* 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 @@
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 @@
* 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 @@
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 3f49f0c..7674b5e 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 @@
}
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 @@
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/DockedStackDividerController.java b/services/core/java/com/android/server/wm/DockedStackDividerController.java
index 7ae1f24..80798bf 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 @@
: 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 c7d4b8e..fe5b65c 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 @@
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 7d4eafb..8269a3b 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 @@
* 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 0628436..7d970d9 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 @@
} else {
mStack.getBounds(mTmpRect);
mTmpRect.intersect(getBounds());
+ out.set(mTmpRect);
}
- out.set(mTmpRect);
} else {
out.set(getBounds());
}
@@ -640,6 +640,7 @@
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 bc0f9ad..ae5341b 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 @@
}
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 @@
|| 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 a1026bd..6bd7f22 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 @@
@Override
public void onConfigurationChanged(Configuration newParentConfig) {
super.onConfigurationChanged(newParentConfig);
- updateSurfacePosition(getPendingTransaction());
+ updateSurfacePosition();
scheduleAnimation();
}
@@ -1204,7 +1204,7 @@
}
}
- void updateSurfacePosition(SurfaceControl.Transaction transaction) {
+ void updateSurfacePosition() {
if (mSurfaceControl == null) {
return;
}
@@ -1214,12 +1214,8 @@
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) {
@@ -1231,4 +1227,11 @@
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 4fb2390..066e4e6 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 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 @@
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 @@
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 477dd2b..53a8d82 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 @@
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 @@
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 @@
if (dimmer != null) {
applyDims(dimmer);
}
-
- updateSurfacePosition(mPendingTransaction);
+ updateSurfacePosition();
mWinAnimator.prepareSurfaceLocked(true);
super.prepareSurfaces();
@@ -4554,7 +4541,11 @@
}
@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 5529426..9fcf3ee 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.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 @@
}
@Override
- public boolean startUserInBackground(ComponentName who, UserHandle userHandle) {
- return false;
- }
-
- @Override
public void setStartUserSessionMessage(
ComponentName admin, CharSequence startUserSessionMessage) {}
@@ -178,4 +171,10 @@
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 918a355..4c57f7f 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 @@
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.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.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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
// 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 @@
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 @@
@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 @@
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 @@
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 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 @@
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 @@
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 @@
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 @@
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 @@
return false;
}
}
-
@Override
public boolean resetPassword(String passwordOrNull, int flags) throws RemoteException {
final int callingUid = mInjector.binderGetCallingUid();
@@ -4958,6 +5005,11 @@
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 @@
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 @@
} 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 @@
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 @@
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 @@
}
@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 @@
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 @@
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 @@
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 @@
}
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 @@
}
}
+ @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);
}
}
@@ -12959,4 +13060,15 @@
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/SecurityLogMonitor.java b/services/devicepolicy/java/com/android/server/devicepolicy/SecurityLogMonitor.java
index a9fd8e5..3277adf 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 @@
*/
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 @@
@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 @@
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 @@
void stop() {
Slog.i(TAG, "Stopping security logging.");
+ SecurityLog.writeEvent(SecurityLog.TAG_LOGGING_STOPPED);
mLock.lock();
try {
if (mMonitorThread != null) {
@@ -205,6 +216,7 @@
mLock.lock();
mAllowedToRetrieve = false;
mPendingLogs = new ArrayList<>();
+ mCriticalLevelLogged = false;
mLock.unlock();
Slog.i(TAG, "Discarded all logs.");
}
@@ -222,6 +234,7 @@
+ RATE_LIMIT_INTERVAL_MILLISECONDS;
List<SecurityEvent> result = mPendingLogs;
mPendingLogs = new ArrayList<>();
+ mCriticalLevelLogged = false;
return result;
} else {
return null;
@@ -344,11 +357,14 @@
// 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 @@
}
@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 f95c6f0..210fd47 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.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 @@
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 @@
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 c397f23..0752537 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 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 @@
@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 b60ad4b..df09780 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.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 @@
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 1360828..e103464 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.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 @@
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 068fe81..44ac803 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.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 @@
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 55fb460..5810c30 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.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 @@
@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 db6e62f..ff1644c 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.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 @@
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 c94d598..d2a4d06 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 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 @@
* 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 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 @@
}
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 @@
* 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 @@
}
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 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 0000000..e01c0a4
--- /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 6b87ea9..00a85a5 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 @@
() -> 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 0000000..83bd9fc
--- /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 0000000..aad5295
--- /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 0000000..c3714c8
--- /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 70906df..396fef4 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.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 @@
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,12 +88,19 @@
@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 makeSurface() {
+ return sWm.makeSurfaceBuilder(mSession)
+ .setName("test surface")
+ .setSize(1, 1);
+ }
@Override
SurfaceControl.Builder makeChildSurface(WindowContainer child) {
@@ -93,11 +108,6 @@
}
@Override
- public SurfaceControl getSurfaceControl() {
- return mHostControl;
- }
-
- @Override
public SurfaceControl.Transaction getPendingTransaction() {
return mHostTransaction;
}
@@ -114,29 +124,37 @@
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 @@
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 @@
final float alpha = 0.8f;
mDimmer.dimAbove(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
@@ -181,11 +204,12 @@
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 @@
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 @@
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 897be34..f860195 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 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 @@
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 35ca493..81fd889 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.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 @@
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 @@
}
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/WindowTestsBase.java b/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java
index 69b1378..7918901 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 @@
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 @@
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 d3bb804..cfd155e 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.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.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 @@
@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 @@
}
@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 @@
}
@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 @@
@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 @@
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 @@
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 @@
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 @@
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 @@
}));
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 0000000..7c14d08
--- /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/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 91d86c6..a34e9f9 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -27,8 +27,8 @@
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 @@
"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 0474362..dfaaab9 100644
--- a/telephony/java/android/telephony/CellSignalStrengthCdma.java
+++ b/telephony/java/android/telephony/CellSignalStrengthCdma.java
@@ -20,6 +20,8 @@
import android.os.Parcelable;
import android.telephony.Rlog;
+import java.util.Objects;
+
/**
* Signal strength related information.
*/
@@ -293,9 +295,7 @@
@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 4137853..f68d2ca 100644
--- a/telephony/java/android/telephony/CellSignalStrengthGsm.java
+++ b/telephony/java/android/telephony/CellSignalStrengthGsm.java
@@ -20,6 +20,8 @@
import android.os.Parcelable;
import android.telephony.Rlog;
+import java.util.Objects;
+
/**
* GSM signal strength related information.
*/
@@ -185,8 +187,7 @@
@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 0d07a40..6ffc8b6 100644
--- a/telephony/java/android/telephony/CellSignalStrengthLte.java
+++ b/telephony/java/android/telephony/CellSignalStrengthLte.java
@@ -20,6 +20,8 @@
import android.os.Parcelable;
import android.telephony.Rlog;
+import java.util.Objects;
+
/**
* LTE signal strength related information.
*/
@@ -231,10 +233,7 @@
@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 b94b01d..2cd56b8 100644
--- a/telephony/java/android/telephony/CellSignalStrengthWcdma.java
+++ b/telephony/java/android/telephony/CellSignalStrengthWcdma.java
@@ -20,6 +20,8 @@
import android.os.Parcelable;
import android.telephony.Rlog;
+import java.util.Objects;
+
/**
* Wcdma signal strength related information.
*/
@@ -156,8 +158,7 @@
@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 03ce2d8..521adef 100644
--- a/telephony/java/android/telephony/ModemActivityInfo.java
+++ b/telephony/java/android/telephony/ModemActivityInfo.java
@@ -36,12 +36,12 @@
*/
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 @@
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 @@
return mTxTimeMs;
}
+ public void setTxTimeMillis(int[] txTimeMs) {
+ mTxTimeMs = txTimeMs;
+ }
+
/**
* @return sleep time in ms.
*/
@@ -125,6 +133,10 @@
return mSleepTimeMs;
}
+ public void setSleepTimeMillis(int sleepTimeMillis) {
+ mSleepTimeMs = sleepTimeMillis;
+ }
+
/**
* @return idle time in ms.
*/
@@ -132,6 +144,10 @@
return mIdleTimeMs;
}
+ public void setIdleTimeMillis(int idleTimeMillis) {
+ mIdleTimeMs = idleTimeMillis;
+ }
+
/**
* @return rx time in ms.
*/
@@ -139,6 +155,10 @@
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 @@
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 a6dbc06..577ea7d 100644
--- a/telephony/java/android/telephony/SmsMessage.java
+++ b/telephony/java/android/telephony/SmsMessage.java
@@ -18,8 +18,8 @@
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 @@
/**
* 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 0a6d960..5c290da 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -48,12 +48,13 @@
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 @@
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 @@
}
}
- /**
- * 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 @@
}
}
- /** @hide */
- @IntDef({ImsFeature.EMERGENCY_MMTEL, ImsFeature.MMTEL, ImsFeature.RCS})
- @Retention(RetentionPolicy.SOURCE)
- public @interface Feature {}
-
/**
- * 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.
+ * Enables IMS for the framework. This will trigger IMS registration and ImsFeature capability
+ * status updates, if not already enabled.
* @hide
*/
- public @Nullable IImsMMTelFeature getImsMMTelFeatureAndListen(int slotIndex,
- IImsServiceFeatureCallback callback) {
+ public void enableIms(int slotId) {
try {
ITelephony telephony = getITelephony();
if (telephony != null) {
- return telephony.getMMTelFeatureAndListen(slotIndex, callback);
+ telephony.enableIms(slotId);
}
} catch (RemoteException e) {
- Rlog.e(TAG, "getImsMMTelFeatureAndListen, RemoteException: "
+ Rlog.e(TAG, "enableIms, 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.
+ * Disables IMS for the framework. This will trigger IMS de-registration and trigger ImsFeature
+ * status updates to disabled.
+ * @hide
+ */
+ public void disableIms(int slotId) {
+ try {
+ ITelephony telephony = getITelephony();
+ if (telephony != null) {
+ telephony.disableIms(slotId);
+ }
+ } catch (RemoteException e) {
+ Rlog.e(TAG, "disableIms, 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
+ * @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 @@
}
/**
+ * @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 ef3a183..25f5133 100644
--- a/telephony/java/android/telephony/data/DataCallResponse.java
+++ b/telephony/java/android/telephony/data/DataCallResponse.java
@@ -27,6 +27,7 @@
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 @@
@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
similarity index 95%
rename from telephony/java/com/android/ims/ImsCallForwardInfo.aidl
rename to telephony/java/android/telephony/ims/ImsCallForwardInfo.aidl
index a7c3f9a..b322b39 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
similarity index 77%
rename from telephony/java/com/android/ims/ImsCallForwardInfo.java
rename to telephony/java/android/telephony/ims/ImsCallForwardInfo.java
index eeee0fc..6d72181 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 @@
*
* @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 @@
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
similarity index 95%
rename from telephony/java/com/android/ims/ImsCallProfile.aidl
rename to telephony/java/android/telephony/ims/ImsCallProfile.aidl
index a356d13..e24e145 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
similarity index 89%
rename from telephony/java/com/android/ims/ImsCallProfile.java
rename to telephony/java/android/telephony/ims/ImsCallProfile.java
index 693aaff..27e5f94 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 @@
*
* @hide
*/
-public class ImsCallProfile implements Parcelable {
+@SystemApi
+public final class ImsCallProfile implements Parcelable {
private static final String TAG = "ImsCallProfile";
/**
@@ -110,52 +112,92 @@
* 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 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 @@
* 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 @@
mMediaProfile = new ImsStreamMediaProfile();
}
+ /** @hide */
public ImsCallProfile(int serviceType, int callType) {
mServiceType = serviceType;
mCallType = callType;
@@ -366,8 +416,28 @@
}
};
+ 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 @@
}
/**
- * 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 @@
}
/**
+ * 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
similarity index 90%
rename from telephony/java/com/android/ims/internal/ImsCallSession.java
rename to telephony/java/android/telephony/ims/ImsCallSession.java
index 1736b80..c3d103f 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 @@
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 @@
* 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 @@
private boolean mClosed = false;
private Listener mListener;
+ /** @hide */
public ImsCallSession(IImsCallSession iSession) {
miSession = iSession;
@@ -462,6 +468,7 @@
}
}
+ /** @hide */
public ImsCallSession(IImsCallSession iSession, Listener listener) {
this(iSession);
setListener(listener);
@@ -470,15 +477,17 @@
/**
* 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 @@
* Gets the video call provider for the session.
*
* @return The video call provider.
+ * @hide
*/
public IImsVideoCallProvider getVideoCallProvider() {
if (mClosed) {
@@ -659,6 +669,7 @@
* 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 @@
* 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 @@
* Sends RTT Upgrade request
*
* @param to : expected profile
- * @throws CallStateException
*/
public void sendRttModifyRequest(ImsCallProfile to) {
if (mClosed) {
@@ -1021,7 +1030,6 @@
* Sends RTT Upgrade response
*
* @param response : response for upgrade
- * @throws CallStateException
*/
public void sendRttModifyResponse(boolean response) {
if (mClosed) {
@@ -1040,37 +1048,33 @@
* 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 @@
* 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 @@
/**
* 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 @@
/**
* 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 @@
* 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 @@
* 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 @@
}
@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 @@
* 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 @@
}
@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 @@
* 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 @@
* 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 @@
* {@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 @@
* 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 @@
* 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 @@
* 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 @@
/**
* 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 @@
* 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 0000000..a7f124a
--- /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
similarity index 95%
rename from telephony/java/com/android/ims/ImsConferenceState.aidl
rename to telephony/java/android/telephony/ims/ImsConferenceState.aidl
index 2fc029f..e2b371c 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
similarity index 95%
rename from telephony/java/com/android/ims/ImsConferenceState.java
rename to telephony/java/android/telephony/ims/ImsConferenceState.java
index 0afde88..66d2f8d 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 @@
*
* @hide
*/
-public class ImsConferenceState implements Parcelable {
+@SystemApi
+public final class ImsConferenceState implements Parcelable {
/**
* conference-info : user
*/
@@ -87,12 +89,13 @@
*/
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
similarity index 95%
rename from telephony/java/com/android/ims/ImsExternalCallState.aidl
rename to telephony/java/android/telephony/ims/ImsExternalCallState.aidl
index c208702..99d29356 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
similarity index 92%
rename from telephony/java/com/android/ims/ImsExternalCallState.java
rename to telephony/java/android/telephony/ims/ImsExternalCallState.java
index da26073..e82c115 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 @@
* 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 @@
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 @@
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
similarity index 95%
rename from telephony/java/com/android/ims/ImsReasonInfo.aidl
rename to telephony/java/android/telephony/ims/ImsReasonInfo.aidl
index 17e6d3a..604b323 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
similarity index 97%
rename from telephony/java/com/android/ims/ImsReasonInfo.java
rename to telephony/java/android/telephony/ims/ImsReasonInfo.java
index 83d9bd9..7b77491 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 @@
*
* @hide
*/
-public class ImsReasonInfo implements Parcelable {
+@SystemApi
+public final class ImsReasonInfo implements Parcelable {
/**
* Specific code of each types
@@ -418,27 +420,36 @@
// 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 @@
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 aaa0f08..2748cb5 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 @@
* ...
* <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 @@
* 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 @@
// call ImsFeature#onFeatureRemoved.
private final SparseArray<SparseArray<ImsFeature>> mFeaturesBySlot = new SparseArray<>();
+ private IImsServiceControllerListener mListener;
+
+
+ /**
+ * Listener that notifies the framework of ImsService changes.
+ * @hide
+ */
+ 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 @@
}
@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 @@
return mFeaturesBySlot.get(slotId);
}
- private IImsMMTelFeature createEmergencyMMTelFeatureInternal(int slotId,
+ private IImsMmTelFeature createMmTelFeatureInternal(int slotId,
IImsFeatureStatusCallback c) {
- MMTelFeature f = onCreateEmergencyMMTelImsFeature(slotId);
+ MmTelFeature f = createMmTelFeature(slotId);
if (f != null) {
- setupFeature(f, slotId, ImsFeature.EMERGENCY_MMTEL, c);
+ setupFeature(f, slotId, ImsFeature.FEATURE_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 {
+ 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 @@
}
}
+ 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) {
+ public ImsFeatureConfiguration querySupportedImsFeatures() {
+ // Return empty for base implementation
+ return new ImsFeatureConfiguration();
+ }
+
+ /**
+ * 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 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 MMTelFeature that will be used by the system for MMTel
- * functionality.
- * @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 MMTelFeature onCreateMMTelImsFeature(int slotId) {
+ public RcsFeature createRcsFeature(int slotId) {
return null;
}
/**
- * @return An implementation of RcsFeature that will be used by the system for RCS.
- * @hide
+ * 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 @Nullable RcsFeature onCreateRcsFeature(int slotId) {
- return null;
+ 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
similarity index 95%
rename from telephony/java/com/android/ims/ImsSsData.aidl
rename to telephony/java/android/telephony/ims/ImsSsData.aidl
index 33f8306..eff3a6b 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
similarity index 89%
rename from telephony/java/com/android/ims/ImsSsData.java
rename to telephony/java/android/telephony/ims/ImsSsData.java
index 7336c13..1ddf199 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 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 @@
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
similarity index 95%
rename from telephony/java/com/android/ims/ImsSsInfo.aidl
rename to telephony/java/android/telephony/ims/ImsSsInfo.aidl
index 0ac598b..66d4950 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
similarity index 81%
rename from telephony/java/com/android/ims/ImsSsInfo.java
rename to telephony/java/android/telephony/ims/ImsSsInfo.java
index 7acc3bf..1d1292f 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 @@
*
* @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 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 @@
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
similarity index 95%
rename from telephony/java/com/android/ims/ImsStreamMediaProfile.aidl
rename to telephony/java/android/telephony/ims/ImsStreamMediaProfile.aidl
index d648a35..ee321ae 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
similarity index 88%
rename from telephony/java/com/android/ims/ImsStreamMediaProfile.java
rename to telephony/java/android/telephony/ims/ImsStreamMediaProfile.java
index cfe37b5..243352b 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 @@
*
* @hide
*/
-public class ImsStreamMediaProfile implements Parcelable {
+@SystemApi
+public final class ImsStreamMediaProfile implements Parcelable {
private static final String TAG = "ImsStreamMediaProfile";
/**
@@ -79,18 +81,25 @@
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 @@
mRttMode = RTT_MODE_DISABLED;
}
+ /** @hide */
public ImsStreamMediaProfile(int audioQuality, int audioDirection,
int videoQuality, int videoDirection) {
mAudioQuality = audioQuality;
@@ -107,6 +117,7 @@
mVideoDirection = videoDirection;
}
+ /** @hide */
public ImsStreamMediaProfile(int rttMode) {
mRttMode = rttMode;
}
@@ -178,4 +189,23 @@
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
similarity index 95%
rename from telephony/java/com/android/ims/ImsSuppServiceNotification.aidl
rename to telephony/java/android/telephony/ims/ImsSuppServiceNotification.aidl
index 6b4479f..0552780 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
similarity index 76%
rename from telephony/java/com/android/ims/ImsSuppServiceNotification.java
rename to telephony/java/android/telephony/ims/ImsSuppServiceNotification.java
index faf7499..efaade8 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 @@
*
* @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 @@
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 0000000..d50a0f7
--- /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
similarity index 97%
rename from telephony/java/com/android/ims/internal/ImsVideoCallProvider.java
rename to telephony/java/android/telephony/ims/ImsVideoCallProvider.java
index 432dc39..b4f60b9 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.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 @@
/**
* 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
similarity index 94%
rename from telephony/java/android/telephony/ims/internal/aidl/IImsCallSessionListener.aidl
rename to telephony/java/android/telephony/ims/aidl/IImsCallSessionListener.aidl
index 2fb6744..f25b4b1 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
similarity index 95%
rename from telephony/java/android/telephony/ims/internal/aidl/IImsCapabilityCallback.aidl
rename to telephony/java/android/telephony/ims/aidl/IImsCapabilityCallback.aidl
index fd2eb24..c755703 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
similarity index 91%
rename from telephony/java/android/telephony/ims/internal/aidl/IImsConfig.aidl
rename to telephony/java/android/telephony/ims/aidl/IImsConfig.aidl
index 3d424a3..4433c1c 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
similarity index 94%
rename from telephony/java/android/telephony/ims/internal/aidl/IImsConfigCallback.aidl
rename to telephony/java/android/telephony/ims/aidl/IImsConfigCallback.aidl
index 52efd23..2b3f1ca 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
similarity index 66%
rename from telephony/java/android/telephony/ims/internal/aidl/IImsMmTelFeature.aidl
rename to telephony/java/android/telephony/ims/aidl/IImsMmTelFeature.aidl
index e226ada..b9a6b3c 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.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 @@
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
similarity index 86%
rename from telephony/java/android/telephony/ims/internal/aidl/IImsMmTelListener.aidl
rename to telephony/java/android/telephony/ims/aidl/IImsMmTelListener.aidl
index 43f5098..904e7ca 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 @@
* {@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
similarity index 94%
rename from telephony/java/android/telephony/ims/internal/aidl/IImsRcsFeature.aidl
rename to telephony/java/android/telephony/ims/aidl/IImsRcsFeature.aidl
index f6005b6..691cfba 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
similarity index 90%
rename from telephony/java/com/android/ims/internal/IImsRegistration.aidl
rename to telephony/java/android/telephony/ims/aidl/IImsRegistration.aidl
index 6de264e..4ae0a75a 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
similarity index 87%
rename from telephony/java/com/android/ims/internal/IImsRegistrationCallback.aidl
rename to telephony/java/android/telephony/ims/aidl/IImsRegistrationCallback.aidl
index 5f21167..4f37caa 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
similarity index 77%
rename from telephony/java/android/telephony/ims/internal/aidl/IImsServiceController.aidl
rename to telephony/java/android/telephony/ims/aidl/IImsServiceController.aidl
index 82a8525..86f8606 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 @@
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
similarity index 87%
rename from telephony/java/android/telephony/ims/internal/aidl/IImsServiceControllerListener.aidl
rename to telephony/java/android/telephony/ims/aidl/IImsServiceControllerListener.aidl
index 01cca2db..54f6120 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
similarity index 92%
rename from telephony/java/com/android/ims/internal/IImsSmsListener.aidl
rename to telephony/java/android/telephony/ims/aidl/IImsSmsListener.aidl
index 5a4b7e4..606df15 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 0000000..cf1efb3
--- /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 0000000..0a12cae
--- /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
similarity index 77%
rename from telephony/java/android/telephony/ims/feature/MMTelFeature.java
rename to telephony/java/android/telephony/ims/compat/feature/MMTelFeature.java
index 93c316f..d3d17f4 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.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 @@
}
@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 @@
@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 @@
@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 @@
@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 @@
*
* @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 @@
/**
* @return The Ut interface for the supplementary service configuration.
*/
- public IImsUt getUtInterface() {
+ public ImsUtImplBase getUtInterface() {
return null;
}
@@ -372,7 +333,7 @@
/**
* @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 @@
/**
* @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
similarity index 76%
rename from telephony/java/android/telephony/ims/internal/feature/RcsFeature.java
rename to telephony/java/android/telephony/ims/compat/feature/RcsFeature.java
index 8d1bd9d..228b330 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,9 +37,8 @@
}
@Override
- public void changeEnabledCapabilities(CapabilityChangeRequest request,
- CapabilityCallbackProxy c) {
- // Do nothing for base implementation.
+ public void onFeatureReady() {
+
}
@Override
@@ -46,12 +46,6 @@
}
- /**{@inheritDoc}*/
- @Override
- public void onFeatureReady() {
-
- }
-
@Override
public final IImsRcsFeature getBinder() {
return mImsRcsBinder;
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 0000000..00cb1e2
--- /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 0000000..2c325ba8
--- /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
similarity index 90%
rename from telephony/java/android/telephony/ims/stub/ImsUtListenerImplBase.java
rename to telephony/java/android/telephony/ims/compat/stub/ImsUtListenerImplBase.java
index daa74c8..b2aa080 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
similarity index 92%
rename from telephony/java/android/telephony/ims/internal/feature/CapabilityChangeRequest.aidl
rename to telephony/java/android/telephony/ims/feature/CapabilityChangeRequest.aidl
index f4ec0eb..e789bd5 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
similarity index 79%
rename from telephony/java/android/telephony/ims/internal/feature/CapabilityChangeRequest.java
rename to telephony/java/android/telephony/ims/feature/CapabilityChangeRequest.java
index 5dbf077..7c793a5 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 @@
* 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 @@
return getRadioTech() == that.getRadioTech();
}
+ /**
+ * @hide
+ */
@Override
public int hashCode() {
int result = getCapability();
@@ -59,10 +78,22 @@
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 @@
// Pair contains <radio tech, mCapability>
private final Set<CapabilityPair> mCapabilitiesToDisable;
+ /** @hide */
public CapabilityChangeRequest() {
mCapabilitiesToEnable = new ArraySet<>();
mCapabilitiesToDisable = new ArraySet<>();
@@ -130,6 +162,9 @@
}
}
+ /**
+ * @hide
+ */
protected CapabilityChangeRequest(Parcel in) {
int enableSize = in.readInt();
mCapabilitiesToEnable = new ArraySet<>(enableSize);
@@ -177,17 +212,24 @@
}
}
+ /**
+ * @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 d47cea30..bfdd453 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 @@
/**
* 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 @@
* 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 @@
* 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;
- }
-
- protected final void setFeatureState(@ImsState int state) {
- if (mState != state) {
- mState = state;
- notifyFeatureState(state);
+ synchronized (mLock) {
+ return mState;
}
}
- public void addImsFeatureStatusCallback(IImsFeatureStatusCallback c) {
- if (c == null) {
- return;
+ /**
+ * 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);
+ }
}
+ }
+
+ /**
+ * 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 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 @@
* 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 @@
}
/**
- * Called when the feature is ready to use.
+ * @hide
*/
- public abstract void onFeatureReady();
+ public final void addCapabilityCallback(IImsCapabilityCallback c) {
+ mCapabilityCallbacks.register(c);
+ }
/**
- * Called when the feature is being removed and must be cleaned up.
+ * @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 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 IInterface getBinder();
+ public abstract void onFeatureReady();
+
+ /**
+ * @return Binder instance that the framework will use to communicate with this feature.
+ * @hide
+ */
+ protected abstract IInterface getBinder();
}
diff --git a/telephony/java/android/telephony/ims/feature/MmTelFeature.java b/telephony/java/android/telephony/ims/feature/MmTelFeature.java
new file mode 100644
index 0000000..09267fc
--- /dev/null
+++ b/telephony/java/android/telephony/ims/feature/MmTelFeature.java
@@ -0,0 +1,672 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.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.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 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 android.telephony.ims.ImsCallSession;
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Base implementation for Voice and SMS (IR-92) and Video (IR-94) IMS support.
+ *
+ * Any class wishing to use MmTelFeature should extend this class and implement all methods that the
+ * service supports.
+ * @hide
+ */
+@SystemApi
+public class MmTelFeature extends ImsFeature {
+
+ private static final String LOG_TAG = "MmTelFeature";
+
+ private final IImsMmTelFeature mImsMMTelBinder = new IImsMmTelFeature.Stub() {
+
+ @Override
+ public void setListener(IImsMmTelListener l) throws RemoteException {
+ synchronized (mLock) {
+ MmTelFeature.this.setListener(l);
+ }
+ }
+
+ @Override
+ public int getFeatureState() throws RemoteException {
+ synchronized (mLock) {
+ try {
+ return MmTelFeature.this.getFeatureState();
+ } catch (Exception e) {
+ throw new RemoteException(e.getMessage());
+ }
+ }
+ }
+
+
+ @Override
+ public ImsCallProfile createCallProfile(int callSessionType, int callType)
+ throws RemoteException {
+ synchronized (mLock) {
+ 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 int shouldProcessCall(String[] numbers) {
+ synchronized (mLock) {
+ return MmTelFeature.this.shouldProcessCall(numbers);
+ }
+ }
+
+ @Override
+ public IImsUt getUtInterface() throws RemoteException {
+ synchronized (mLock) {
+ return MmTelFeature.this.getUtInterface();
+ }
+ }
+
+ @Override
+ public IImsEcbm getEcbmInterface() throws RemoteException {
+ synchronized (mLock) {
+ return MmTelFeature.this.getEcbmInterface();
+ }
+ }
+
+ @Override
+ public void setUiTtyMode(int uiTtyMode, Message onCompleteMessage) throws RemoteException {
+ synchronized (mLock) {
+ 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.getMultiEndpointInterface();
+ }
+ }
+
+ @Override
+ public int queryCapabilityStatus() throws RemoteException {
+ return MmTelFeature.this.queryCapabilityStatus().mCapabilities;
+ }
+
+ @Override
+ public void addCapabilityCallback(IImsCapabilityCallback c) {
+ MmTelFeature.this.addCapabilityCallback(c);
+ }
+
+ @Override
+ public void removeCapabilityCallback(IImsCapabilityCallback c) {
+ MmTelFeature.this.removeCapabilityCallback(c);
+ }
+
+ @Override
+ public void changeCapabilitiesConfiguration(CapabilityChangeRequest request,
+ IImsCapabilityCallback c) throws RemoteException {
+ MmTelFeature.this.requestChangeEnabledCapabilities(request, c);
+ }
+
+ @Override
+ public void queryCapabilityConfiguration(int capability, int radioTech,
+ 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 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()}.
+ *
+ * This MmTelFeature can then return the status of each of these capabilities (enabled or not)
+ * by sending a {@link #notifyCapabilitiesStatusChanged} callback to the framework. The current
+ * status can also be queried using {@link #queryCapabilityStatus()}.
+ */
+ public static class MmTelCapabilities extends Capabilities {
+
+ /**
+ * @hide
+ */
+ @VisibleForTesting
+ public MmTelCapabilities() {
+ super();
+ }
+
+ public MmTelCapabilities(Capabilities c) {
+ mCapabilities = c.mCapabilities;
+ }
+
+ public MmTelCapabilities(int capabilities) {
+ mCapabilities = capabilities;
+ }
+
+ @IntDef(flag = true,
+ value = {
+ CAPABILITY_TYPE_VOICE,
+ CAPABILITY_TYPE_VIDEO,
+ CAPABILITY_TYPE_UT,
+ CAPABILITY_TYPE_SMS
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface MmTelCapability {}
+
+ /**
+ * This MmTelFeature supports Voice calling (IR.92)
+ */
+ public static final int CAPABILITY_TYPE_VOICE = 1 << 0;
+
+ /**
+ * This MmTelFeature supports Video (IR.94)
+ */
+ public static final int CAPABILITY_TYPE_VIDEO = 1 << 1;
+
+ /**
+ * This MmTelFeature supports XCAP over Ut for supplementary services. (IR.92)
+ */
+ public static final int CAPABILITY_TYPE_UT = 1 << 2;
+
+ /**
+ * This MmTelFeature supports SMS (IR.92)
+ */
+ public static final int CAPABILITY_TYPE_SMS = 1 << 3;
+
+ @Override
+ public final void addCapabilities(@MmTelCapability int capabilities) {
+ super.addCapabilities(capabilities);
+ }
+
+ @Override
+ public final void removeCapabilities(@MmTelCapability int capability) {
+ super.removeCapabilities(capability);
+ }
+
+ @Override
+ 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 void onIncomingCall(IImsCallSession c, Bundle extras) {
+
+ }
+
+ /**
+ * Updates the Listener when the voice message count for IMS has changed.
+ * @param count an integer representing the new message count.
+ */
+ @Override
+ public void onVoiceMessageCountUpdate(int count) {
+
+ }
+ }
+
+ /**
+ * 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;
+
+ /**
+ * @param listener A {@link Listener} used when the MmTelFeature receives an incoming call and
+ * notifies the framework.
+ */
+ private void setListener(IImsMmTelListener listener) {
+ synchronized (mLock) {
+ mListener = listener;
+ }
+ if (mListener != null) {
+ onFeatureReady();
+ }
+ }
+
+ private void queryCapabilityConfigurationInternal(int capability, int radioTech,
+ IImsCapabilityCallback c) {
+ boolean enabled = queryCapabilityConfiguration(capability, radioTech);
+ try {
+ if (c != null) {
+ c.onQueryCapabilityConfiguration(capability, radioTech, enabled);
+ }
+ } catch (RemoteException e) {
+ Log.e(LOG_TAG, "queryCapabilityConfigurationInternal called on dead binder!");
+ }
+ }
+
+ /**
+ * The current capability status that this MmTelFeature has defined is available. This
+ * configuration will be used by the platform to figure out which capabilities are CURRENTLY
+ * available to be used.
+ *
+ * Should be a subset of the capabilities that are enabled by the framework in
+ * {@link #changeEnabledCapabilities}.
+ * @return A copy of the current MmTelFeature capability status.
+ */
+ @Override
+ public final MmTelCapabilities queryCapabilityStatus() {
+ return new MmTelCapabilities(super.queryCapabilityStatus());
+ }
+
+ /**
+ * Notify the framework that the status of the Capabilities has changed. Even though the
+ * MmTelFeature capability may be enabled by the framework, the status may be disabled due to
+ * the feature being unavailable from the network.
+ * @param c The current capability status of the MmTelFeature. If a capability is disabled, then
+ * the status of that capability is disabled. This can happen if the network does not currently
+ * support the capability that is enabled. A capability that is disabled by the framework (via
+ * {@link #changeEnabledCapabilities}) should also show the status as disabled.
+ */
+ public final void notifyCapabilitiesStatusChanged(MmTelCapabilities c) {
+ super.notifyCapabilitiesStatusChanged(c);
+ }
+
+ /**
+ * Notify the framework of an 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);
+ }
+ }
+ }
+
+ /**
+ *
+ * @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.");
+ }
+ try {
+ mListener.onVoiceMessageCountUpdate(count);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ }
+
+ /**
+ * Provides the MmTelFeature with the ability to return the framework Capability Configuration
+ * for a provided Capability. If the framework calls {@link #changeEnabledCapabilities} and
+ * includes a capability A to enable or disable, this method should return the correct enabled
+ * status for capability A.
+ * @param capability The capability that we are querying the configuration for.
+ * @return true if the capability is enabled, false otherwise.
+ */
+ public boolean queryCapabilityConfiguration(@MmTelCapabilities.MmTelCapability int capability,
+ @ImsRegistrationImplBase.ImsRegistrationTech int radioTech) {
+ // Base implementation - Override to provide functionality
+ return false;
+ }
+
+ /**
+ * The MmTelFeature should override this method to handle the enabling/disabling of
+ * MmTel Features, defined in {@link MmTelCapabilities.MmTelCapability}. The framework assumes
+ * the {@link CapabilityChangeRequest} was processed successfully. If a subset of capabilities
+ * could not be set to their new values,
+ * {@link CapabilityCallbackProxy#onChangeCapabilityConfigurationError} must be called
+ * individually for each capability whose processing resulted in an error.
+ *
+ * Enabling/Disabling a capability here indicates that the capability should be registered or
+ * deregistered (depending on the capability change) and become available or unavailable to
+ * the framework.
+ */
+ @Override
+ public void changeEnabledCapabilities(CapabilityChangeRequest request,
+ CapabilityCallbackProxy c) {
+ // Base implementation, no-op
+ }
+
+ /**
+ * Creates a {@link ImsCallProfile} from the service capabilities & IMS registration state.
+ *
+ * @param callSessionType a service type that is specified in {@link ImsCallProfile}
+ * {@link ImsCallProfile#SERVICE_TYPE_NONE}
+ * {@link ImsCallProfile#SERVICE_TYPE_NORMAL}
+ * {@link ImsCallProfile#SERVICE_TYPE_EMERGENCY}
+ * @param callType a call type that is specified in {@link ImsCallProfile}
+ * {@link ImsCallProfile#CALL_TYPE_VOICE}
+ * {@link ImsCallProfile#CALL_TYPE_VT}
+ * {@link ImsCallProfile#CALL_TYPE_VT_TX}
+ * {@link ImsCallProfile#CALL_TYPE_VT_RX}
+ * {@link ImsCallProfile#CALL_TYPE_VT_NODIR}
+ * {@link ImsCallProfile#CALL_TYPE_VS}
+ * {@link ImsCallProfile#CALL_TYPE_VS_TX}
+ * {@link ImsCallProfile#CALL_TYPE_VS_RX}
+ * @return a {@link ImsCallProfile} object
+ */
+ public ImsCallProfile createCallProfile(int callSessionType, int callType) {
+ // Base Implementation - Should be overridden
+ return null;
+ }
+
+ /**
+ * @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
+ */
+ public ImsCallSessionImplBase createCallSession(ImsCallProfile profile) {
+ // Base Implementation - Should be overridden
+ return null;
+ }
+
+ /**
+ * 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
+ return null;
+ }
+
+ /**
+ * @return The {@link ImsEcbmImplBase} Emergency call-back mode interface for emergency VoLTE
+ * calls that support it.
+ */
+ public ImsEcbmImplBase getEcbm() {
+ // Base Implementation - Should be overridden
+ return null;
+ }
+
+ /**
+ * @return The {@link ImsMultiEndpointImplBase} implementation for implementing Dialog event
+ * package processing for multi-endpoint.
+ */
+ public ImsMultiEndpointImplBase getMultiEndpoint() {
+ // Base Implementation - Should be overridden
+ return null;
+ }
+
+ /**
+ * Sets the current UI TTY mode for the MmTelFeature.
+ * @param mode An integer containing the new UI TTY Mode, can consist of
+ * {@link TelecomManager#TTY_MODE_OFF},
+ * {@link TelecomManager#TTY_MODE_FULL},
+ * {@link TelecomManager#TTY_MODE_HCO},
+ * {@link TelecomManager#TTY_MODE_VCO}
+ * @param onCompleteMessage A {@link Message} to be used when the mode has been set.
+ */
+ 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() {
+ // Base Implementation - Should be overridden
+ }
+
+ /**{@inheritDoc}*/
+ @Override
+ public void onFeatureReady() {
+ // Base Implementation - Should be overridden
+ }
+
+ /**
+ * @hide
+ */
+ @Override
+ public final IImsMmTelFeature getBinder() {
+ return mImsMMTelBinder;
+ }
+}
diff --git a/telephony/java/android/telephony/ims/feature/RcsFeature.java b/telephony/java/android/telephony/ims/feature/RcsFeature.java
index 40c5181..a637e16 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 @@
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 5d16dd5..0000000
--- 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 afaf332..0000000
--- 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 9f82ad2..0000000
--- 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/feature/MmTelFeature.java b/telephony/java/android/telephony/ims/internal/feature/MmTelFeature.java
deleted file mode 100644
index 9b576c7..0000000
--- a/telephony/java/android/telephony/ims/internal/feature/MmTelFeature.java
+++ /dev/null
@@ -1,437 +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.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.ImsEcbmImplBase;
-import android.telephony.ims.stub.ImsMultiEndpointImplBase;
-import android.telephony.ims.stub.ImsUtImplBase;
-import android.util.Log;
-
-import com.android.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 com.android.internal.annotations.VisibleForTesting;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-
-/**
- * Base implementation for Voice and SMS (IR-92) and Video (IR-94) IMS support.
- *
- * Any class wishing to use MmTelFeature should extend this class and implement all methods that the
- * service supports.
- * @hide
- */
-
-public class MmTelFeature extends ImsFeature {
-
- private static final String LOG_TAG = "MmTelFeature";
-
- private final IImsMmTelFeature mImsMMTelBinder = new IImsMmTelFeature.Stub() {
-
- @Override
- public void setListener(IImsMmTelListener l) throws RemoteException {
- synchronized (mLock) {
- MmTelFeature.this.setListener(l);
- }
- }
-
- @Override
- public int getFeatureState() throws RemoteException {
- synchronized (mLock) {
- return MmTelFeature.this.getFeatureState();
- }
- }
-
-
- @Override
- public ImsCallProfile createCallProfile(int callSessionType, int callType)
- throws RemoteException {
- synchronized (mLock) {
- return MmTelFeature.this.createCallProfile(callSessionType, callType);
- }
- }
-
- @Override
- public IImsCallSession createCallSession(ImsCallProfile profile,
- IImsCallSessionListener listener) throws RemoteException {
- synchronized (mLock) {
- ImsCallSession s = MmTelFeature.this.createCallSession(profile,
- new ImsCallSessionListener(listener));
- return s != null ? s.getSession() : null;
- }
- }
-
- @Override
- public IImsUt getUtInterface() throws RemoteException {
- synchronized (mLock) {
- return MmTelFeature.this.getUt();
- }
- }
-
- @Override
- public IImsEcbm getEcbmInterface() throws RemoteException {
- synchronized (mLock) {
- return MmTelFeature.this.getEcbm();
- }
- }
-
- @Override
- public void setUiTtyMode(int uiTtyMode, Message onCompleteMessage) throws RemoteException {
- synchronized (mLock) {
- MmTelFeature.this.setUiTtyMode(uiTtyMode, onCompleteMessage);
- }
- }
-
- @Override
- public IImsMultiEndpoint getMultiEndpointInterface() throws RemoteException {
- synchronized (mLock) {
- return MmTelFeature.this.getMultiEndpoint();
- }
- }
-
- @Override
- public int queryCapabilityStatus() throws RemoteException {
- return MmTelFeature.this.queryCapabilityStatus().mCapabilities;
- }
-
- @Override
- public void addCapabilityCallback(IImsCapabilityCallback c) {
- MmTelFeature.this.addCapabilityCallback(c);
- }
-
- @Override
- public void removeCapabilityCallback(IImsCapabilityCallback c) {
- MmTelFeature.this.removeCapabilityCallback(c);
- }
-
- @Override
- public void changeCapabilitiesConfiguration(CapabilityChangeRequest request,
- IImsCapabilityCallback c) throws RemoteException {
- MmTelFeature.this.requestChangeEnabledCapabilities(request, c);
- }
-
- @Override
- public void queryCapabilityConfiguration(int capability, int radioTech,
- IImsCapabilityCallback c) {
- queryCapabilityConfigurationInternal(capability, radioTech, c);
- }
- };
-
- /**
- * 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 of this MmTelFeature will be set by the framework and can be queried with
- * {@link #queryCapabilityStatus()}.
- *
- * This MmTelFeature can then return the status of each of these capabilities (enabled or not)
- * by sending a {@link #notifyCapabilitiesStatusChanged} callback to the framework. The current
- * status can also be queried using {@link #queryCapabilityStatus()}.
- */
- public static class MmTelCapabilities extends Capabilities {
-
- @VisibleForTesting
- public MmTelCapabilities() {
- super();
- }
-
- public MmTelCapabilities(Capabilities c) {
- mCapabilities = c.mCapabilities;
- }
-
- @IntDef(flag = true,
- value = {
- CAPABILITY_TYPE_VOICE,
- CAPABILITY_TYPE_VIDEO,
- CAPABILITY_TYPE_UT,
- CAPABILITY_TYPE_SMS
- })
- @Retention(RetentionPolicy.SOURCE)
- public @interface MmTelCapability {}
-
- /**
- * This MmTelFeature supports Voice calling (IR.92)
- */
- public static final int CAPABILITY_TYPE_VOICE = 1 << 0;
-
- /**
- * This MmTelFeature supports Video (IR.94)
- */
- public static final int CAPABILITY_TYPE_VIDEO = 1 << 1;
-
- /**
- * This MmTelFeature supports XCAP over Ut for supplementary services. (IR.92)
- */
- public static final int CAPABILITY_TYPE_UT = 1 << 2;
-
- /**
- * This MmTelFeature supports SMS (IR.92)
- */
- public static final int CAPABILITY_TYPE_SMS = 1 << 3;
-
- @Override
- public final void addCapabilities(@MmTelCapability int capabilities) {
- super.addCapabilities(capabilities);
- }
-
- @Override
- public final void removeCapabilities(@MmTelCapability int capability) {
- super.removeCapabilities(capability);
- }
-
- @Override
- public final boolean isCapable(@MmTelCapability int capabilities) {
- return super.isCapable(capabilities);
- }
- }
-
- /**
- * Listener that the framework implements for communication from the MmTelFeature.
- */
- public static class Listener extends IImsMmTelListener.Stub {
-
- @Override
- public final void onIncomingCall(IImsCallSession c) {
- onIncomingCall(new ImsCallSession(c));
- }
-
- /**
- * Updates the Listener when the voice message count for IMS has changed.
- * @param count an integer representing the new message count.
- */
- @Override
- 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) {
- }
- }
-
- // Lock for feature synchronization
- private final Object mLock = new Object();
- private IImsMmTelListener mListener;
-
- /**
- * @param listener A {@link Listener} used when the MmTelFeature receives an incoming call and
- * notifies the framework.
- */
- private void setListener(IImsMmTelListener listener) {
- synchronized (mLock) {
- mListener = listener;
- }
- }
-
- private void queryCapabilityConfigurationInternal(int capability, int radioTech,
- IImsCapabilityCallback c) {
- boolean enabled = queryCapabilityConfiguration(capability, radioTech);
- try {
- if (c != null) {
- c.onQueryCapabilityConfiguration(capability, radioTech, enabled);
- }
- } catch (RemoteException e) {
- Log.e(LOG_TAG, "queryCapabilityConfigurationInternal called on dead binder!");
- }
- }
-
- /**
- * The current capability status that this MmTelFeature has defined is available. This
- * configuration will be used by the platform to figure out which capabilities are CURRENTLY
- * available to be used.
- *
- * Should be a subset of the capabilities that are enabled by the framework in
- * {@link #changeEnabledCapabilities}.
- * @return A copy of the current MmTelFeature capability status.
- */
- @Override
- public final MmTelCapabilities queryCapabilityStatus() {
- return new MmTelCapabilities(super.queryCapabilityStatus());
- }
-
- /**
- * Notify the framework that the status of the Capabilities has changed. Even though the
- * MmTelFeature capability may be enabled by the framework, the status may be disabled due to
- * the feature being unavailable from the network.
- * @param c The current capability status of the MmTelFeature. If a capability is disabled, then
- * the status of that capability is disabled. This can happen if the network does not currently
- * 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) {
- super.notifyCapabilitiesStatusChanged(c);
- }
-
- /**
- * Notify the framework of an incoming call.
- * @param c The {@link ImsCallSession} of the new incoming call.
- *
- * @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 {
- synchronized (mLock) {
- if (mListener == null) {
- throw new IllegalStateException("Session is not available.");
- }
- mListener.onIncomingCall(c.getSession());
- }
- }
-
- /**
- * Provides the MmTelFeature with the ability to return the framework Capability Configuration
- * for a provided Capability. If the framework calls {@link #changeEnabledCapabilities} and
- * includes a capability A to enable or disable, this method should return the correct enabled
- * status for capability A.
- * @param capability The capability that we are querying the configuration for.
- * @return true if the capability is enabled, false otherwise.
- */
- public boolean queryCapabilityConfiguration(@MmTelCapabilities.MmTelCapability int capability,
- @ImsRegistrationImplBase.ImsRegistrationTech int radioTech) {
- // Base implementation - Override to provide functionality
- return false;
- }
-
- /**
- * The MmTelFeature should override this method to handle the enabling/disabling of
- * MmTel Features, defined in {@link MmTelCapabilities.MmTelCapability}. The framework assumes
- * the {@link CapabilityChangeRequest} was processed successfully. If a subset of capabilities
- * could not be set to their new values,
- * {@link CapabilityCallbackProxy#onChangeCapabilityConfigurationError} must be called
- * individually for each capability whose processing resulted in an error.
- *
- * Enabling/Disabling a capability here indicates that the capability should be registered or
- * deregistered (depending on the capability change) and become available or unavailable to
- * the framework.
- */
- @Override
- public void changeEnabledCapabilities(CapabilityChangeRequest request,
- CapabilityCallbackProxy c) {
- // Base implementation, no-op
- }
-
- /**
- * Creates a {@link ImsCallProfile} from the service capabilities & IMS registration state.
- *
- * @param callSessionType a service type that is specified in {@link ImsCallProfile}
- * {@link ImsCallProfile#SERVICE_TYPE_NONE}
- * {@link ImsCallProfile#SERVICE_TYPE_NORMAL}
- * {@link ImsCallProfile#SERVICE_TYPE_EMERGENCY}
- * @param callType a call type that is specified in {@link ImsCallProfile}
- * {@link ImsCallProfile#CALL_TYPE_VOICE}
- * {@link ImsCallProfile#CALL_TYPE_VT}
- * {@link ImsCallProfile#CALL_TYPE_VT_TX}
- * {@link ImsCallProfile#CALL_TYPE_VT_RX}
- * {@link ImsCallProfile#CALL_TYPE_VT_NODIR}
- * {@link ImsCallProfile#CALL_TYPE_VS}
- * {@link ImsCallProfile#CALL_TYPE_VS_TX}
- * {@link ImsCallProfile#CALL_TYPE_VS_RX}
- * @return a {@link ImsCallProfile} object
- */
- public ImsCallProfile createCallProfile(int callSessionType, int callType) {
- // Base Implementation - Should be overridden
- return 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) {
- // Base Implementation - Should be overridden
- return null;
- }
-
- /**
- * @return The Ut interface for the supplementary service configuration.
- */
- public ImsUtImplBase getUt() {
- // Base Implementation - Should be overridden
- return null;
- }
-
- /**
- * @return The Emergency call-back mode interface for emergency VoLTE calls that support it.
- */
- public ImsEcbmImplBase getEcbm() {
- // Base Implementation - Should be overridden
- return null;
- }
-
- /**
- * @return The Emergency call-back mode interface for emergency VoLTE calls that support it.
- */
- public ImsMultiEndpointImplBase getMultiEndpoint() {
- // Base Implementation - Should be overridden
- return null;
- }
-
- /**
- * Sets the current UI TTY mode for the MmTelFeature.
- * @param mode An integer containing the new UI TTY Mode, can consist of
- * {@link TelecomManager#TTY_MODE_OFF},
- * {@link TelecomManager#TTY_MODE_FULL},
- * {@link TelecomManager#TTY_MODE_HCO},
- * {@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) {
- // Base Implementation - Should be overridden
- }
-
- /**{@inheritDoc}*/
- @Override
- public void onFeatureRemoved() {
- // Base Implementation - Should be overridden
- }
-
- /**{@inheritDoc}*/
- @Override
- public void onFeatureReady() {
- // Base Implementation - Should be overridden
- }
-
- /**
- * @hide
- */
- @Override
- public final IImsMmTelFeature getBinder() {
- return mImsMMTelBinder;
- }
-}
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 33aec5d..0000000
--- 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 80b2f78..c6ca6fd 100644
--- a/telephony/java/android/telephony/ims/stub/ImsCallSessionImplBase.java
+++ b/telephony/java/android/telephony/ims/stub/ImsCallSessionImplBase.java
@@ -16,105 +16,259 @@
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() {
+ }
}
- /**
- * Gets the call ID of the session.
- *
- * @return the call ID
- */
- @Override
- public String getCallId() throws RemoteException {
- return null;
- }
+ // 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);
+ }
+ };
/**
- * Gets the call profile that this session is associated with
- *
- * @return the {@link ImsCallProfile} that this session is associated with
+ * @hide
*/
- @Override
- public ImsCallProfile getCallProfile() throws RemoteException {
- return null;
- }
-
- /**
- * Gets the local call profile that this session is associated with
- *
- * @return the local {@link ImsCallProfile} that this session is associated with
- */
- @Override
- public ImsCallProfile getLocalCallProfile() throws RemoteException {
- return null;
- }
-
- /**
- * Gets the remote call profile that this session is associated with
- *
- * @return the remote {@link ImsCallProfile} that this session is associated with
- */
- @Override
- public ImsCallProfile getRemoteCallProfile() throws RemoteException {
- return null;
- }
-
- /**
- * Gets the value associated with the specified property of this session.
- *
- * @return the string value associated with the specified property
- */
- @Override
- public String getProperty(String name) throws RemoteException {
- return null;
- }
-
- /**
- * Gets the session state.
- * The value returned must be one of the states in {@link ImsCallSession.State}.
- *
- * @return the session state
- */
- @Override
- public int getState() throws RemoteException {
- return ImsCallSession.State.INVALID;
- }
-
- /**
- * Checks if the session is in call.
- *
- * @return true if the session is in call, false otherwise
- */
- @Override
- public boolean isInCall() throws RemoteException {
- return false;
+ public final void setListener(IImsCallSessionListener listener) throws RemoteException {
+ setListener(new ImsCallSessionListener(listener));
}
/**
@@ -122,25 +276,87 @@
* 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
+ * @param listener {@link ImsCallSessionListener} used to notify the framework of updates
+ * to the ImsCallSession
+ */
+ public void setListener(ImsCallSessionListener listener) {
+ }
+
+ /**
+ * Closes the object. This {@link ImsCallSessionImplBase} is not usable after being closed.
*/
@Override
- public void setListener(IImsCallSessionListener listener) throws RemoteException {
+ public void close() {
+
+ }
+
+ /**
+ * @return A String containing the unique call ID of this {@link ImsCallSessionImplBase}.
+ */
+ public String getCallId() {
+ return null;
+ }
+
+ /**
+ * @return The {@link ImsCallProfile} that this {@link ImsCallSessionImplBase} is associated
+ * with.
+ */
+ public ImsCallProfile getCallProfile() {
+ return null;
+ }
+
+ /**
+ * @return The local {@link ImsCallProfile} that this {@link ImsCallSessionImplBase} is
+ * associated with.
+ */
+ public ImsCallProfile getLocalCallProfile() {
+ return null;
+ }
+
+ /**
+ * @return The remote {@link ImsCallProfile} that this {@link ImsCallSessionImplBase} is
+ * associated with.
+ */
+ public ImsCallProfile getRemoteCallProfile() {
+ return null;
+ }
+
+ /**
+ * @param name The String extra key.
+ * @return The string extra value associated with the specified property.
+ */
+ public String getProperty(String name) {
+ return null;
+ }
+
+ /**
+ * @return The {@link ImsCallSessionImplBase} state, defined in
+ * {@link ImsCallSessionImplBase.State}.
+ */
+ public int getState() {
+ return ImsCallSessionImplBase.State.INVALID;
+ }
+
+ /**
+ * @return true if the {@link ImsCallSessionImplBase} is in a call, false otherwise.
+ */
+ 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 @@
* @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 @@
* @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 @@
* @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 @@
* @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 @@
* @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 @@
* {@link ImsCallSession.Listener#callSessionMergeComplete},
* {@link ImsCallSession.Listener#callSessionMergeFailed}
*/
- @Override
- public void merge() throws RemoteException {
+ public void merge() {
}
/**
@@ -251,8 +457,7 @@
* @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 @@
* @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 @@
* @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 @@
* @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 @@
*
* @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 @@
*
* @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 @@
*
* @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 @@
* 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 @@
* 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 @@
* 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 6c18935..0000000
--- 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 1670e6b..dcd7ea7 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 @@
* 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 @@
*/
@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 @@
/**
* 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 @@
* 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,63 +165,21 @@
*
* @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);
- }
-
- /**
- * 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) {
@@ -360,32 +189,228 @@
}
}
- private void sendImsConfigChangedIntent(int item, int value) {
- sendImsConfigChangedIntent(item, Integer.toString(value));
+ private void notifyImsConfigChanged(int item, int value) throws RemoteException {
+ getImsConfigImpl().notifyConfigChanged(item, 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);
- }
+ private void notifyImsConfigChanged(int item, String value) throws RemoteException {
+ getImsConfigImpl().notifyConfigChanged(item, value);
}
- protected synchronized void updateCachedValue(int item, int value, boolean notifyChange) {
+ protected synchronized void updateCachedValue(int item, int value, boolean notifyChange)
+ throws RemoteException {
mProvisionedIntValue.put(item, value);
if (notifyChange) {
- sendImsConfigChangedIntent(item, value);
+ notifyImsConfigChanged(item, value);
}
}
- protected synchronized void updateCachedValue(
- int item, String value, boolean notifyChange) {
+ protected synchronized void updateCachedValue(int item, String value,
+ boolean notifyChange) throws RemoteException {
mProvisionedStringValue.put(item, value);
if (notifyChange) {
- sendImsConfigChangedIntent(item, value);
+ notifyImsConfigChanged(item, value);
}
}
}
+
+ /**
+ * Callback that the framework uses for receiving Configuration change updates.
+ * {@hide}
+ */
+ public static 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
+ }
+ }
+
+ /**
+ * 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 final RemoteCallbackList<IImsConfigCallback> mCallbacks = new RemoteCallbackList<>();
+ ImsConfigStub mImsConfigStub;
+
+ /**
+ * 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.");
+ }
+ });
+ }
+
+ 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.");
+ }
+ }
+
+ /**
+ * 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 89f95ff..06c35ea 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 @@
*
* @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;
+ }
- /**
- * Sets the listener.
- */
- @Override
- public void setListener(IImsEcbmListener listener) throws RemoteException {
+ @Override
+ public void exitEmergencyCallbackMode() {
+ ImsEcbmImplBase.this.exitEmergencyCallbackMode();
+ }
+ };
+ /** @hide */
+ public IImsEcbm getImsEcbm() {
+ return mImsEcbm;
}
/**
- * Requests Modem to come out of ECBM mode
+ * 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 exitEmergencyCallbackMode() throws RemoteException {
+ public void exitEmergencyCallbackMode() {
+ Log.d(TAG, "exitEmergencyCallbackMode() not implemented");
+ }
+ /**
+ * Notifies the framework when the device enters Emergency Callback Mode.
+ *
+ * @throws RuntimeException if the connection to the framework is not available.
+ */
+ 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
similarity index 93%
rename from telephony/java/android/telephony/ims/internal/stub/ImsFeatureConfiguration.aidl
rename to telephony/java/android/telephony/ims/stub/ImsFeatureConfiguration.aidl
index e890cf8..e2ae0e8 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
similarity index 77%
rename from telephony/java/android/telephony/ims/internal/stub/ImsFeatureConfiguration.java
rename to telephony/java/android/telephony/ims/stub/ImsFeatureConfiguration.java
index 244c957..98b67c3 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 @@
* 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 @@
}
/**
- * @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 @@
mFeatures.add(feature);
}
+ /** @hide */
protected ImsFeatureConfiguration(Parcel in) {
int[] features = in.createIntArray();
if (features != null) {
@@ -130,16 +141,23 @@
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 05da9da..ce2d89a 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 @@
*
* @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 42af083..4334d3a 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 @@
* 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 @@
// Locked on mLock, create unspecified disconnect cause.
private ImsReasonInfo mLastDisconnectCause = new ImsReasonInfo();
+ /**
+ * @hide
+ */
public final IImsRegistration getBinder() {
return mBinder;
}
@@ -171,8 +178,8 @@
/**
* 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 @@
/**
* 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 @@
});
}
+ /**
+ * 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 @@
});
}
+ /**
+ * 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 @@
}
}
+ /**
+ * @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
similarity index 97%
rename from telephony/java/android/telephony/ims/internal/stub/SmsImplBase.java
rename to telephony/java/android/telephony/ims/stub/ImsSmsImplBase.java
index 89acc80..0673a38 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 @@
* @hide
*/
@SystemApi
-public class SmsImplBase {
+public class ImsSmsImplBase {
private static final String LOG_TAG = "SmsImplBase";
/** @hide */
@@ -158,7 +157,7 @@
* @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 @@
}
/**
- * 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 054a8b2..fcd7faf 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 {
-public class ImsUtImplBase extends IImsUt.Stub {
+ 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);
+ }
+
+ @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
*/
- @Override
- public int queryCLIR() throws RemoteException {
+ 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.
+ */
+ 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
*/
- @Override
- public int updateCLIR(int clirMode) throws RemoteException {
+ 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.
+ */
+ 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 cd0c4b1..1dda6bf 100644
--- a/telephony/java/com/android/ims/ImsConfig.java
+++ b/telephony/java/com/android/ims/ImsConfig.java
@@ -19,8 +19,9 @@
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 @@
/**
* 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 @@
/**
* 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,84 +542,133 @@
}
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 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 the ImsService is unavailable.
+ */
+ 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.setConfigString(item, value);
+ } catch (RemoteException e) {
+ throw new ImsException("setConfig()", e,
+ ImsReasonInfo.CODE_LOCAL_SERVICE_UNAVAILABLE);
+ }
+ if (DBG) {
+ Rlog.d(TAG, "setConfig(): item = " + item +
" value = " + value + " ret = " + ret);
}
@@ -624,79 +676,32 @@
}
/**
- * 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.
+ * Adds a {@link ImsConfigImplBase.Callback} to the ImsService to notify when a Configuration
+ * item has changed.
*
- * @param item, as defined in com.android.ims.ImsConfig#ConfigConstants.
- * @param value in String format.
- * @return as defined in com.android.ims.ImsConfig#OperationStatusConstants
- *
- * @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 int setProvisionedStringValue(int item, String value)
- throws ImsException {
- int ret = OperationStatusConstants.UNKNOWN;
+ public void addConfigCallback(ImsConfigImplBase.Callback callback) throws ImsException {
+ if (DBG) Rlog.d(TAG, "addConfigCallback: " + callback);
try {
- ret = miConfig.setProvisionedStringValue(item, value);
+ miConfig.addImsConfigCallback(callback);
} catch (RemoteException e) {
- throw new ImsException("setProvisionedStringValue()", e,
- ImsReasonInfo.CODE_LOCAL_SERVICE_UNAVAILABLE);
- }
- if (DBG) {
- Rlog.d(TAG, "setProvisionedStringValue(): item = " + item +
- ", value =" + value);
- }
-
- return ret;
- }
-
- /**
- * Gets the value for IMS feature item for specified network type.
- *
- * @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.
- */
- public void getFeatureValue(int feature, int network,
- ImsConfigListener listener) throws ImsException {
- if (DBG) {
- Rlog.d(TAG, "getFeatureValue: feature = " + feature + ", network =" + network +
- ", listener =" + listener);
- }
- try {
- miConfig.getFeatureValue(feature, network, listener);
- } catch (RemoteException e) {
- throw new ImsException("getFeatureValue()", 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 0e8bad7..f35e886 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 14c184a..c9d4405 100644
--- a/telephony/java/com/android/ims/ImsUtInterface.java
+++ b/telephony/java/com/android/ims/ImsUtInterface.java
@@ -18,6 +18,8 @@
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 c6fc5e5..203e6cf 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 748092d..a8e8b7dd 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 1621967..b3d8139 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 41b1042..b83b130 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 10c7f3e..5151192 100644
--- a/telephony/java/com/android/ims/internal/IImsMMTelFeature.aidl
+++ b/telephony/java/com/android/ims/internal/IImsMMTelFeature.aidl
@@ -18,14 +18,12 @@
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 @@
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 @@
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 15f8726..2212109 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 406d22d..c3cc6fb 100644
--- a/telephony/java/com/android/ims/internal/IImsService.aidl
+++ b/telephony/java/com/android/ims/internal/IImsService.aidl
@@ -18,7 +18,7 @@
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 7ac25ac..857089f 100644
--- a/telephony/java/com/android/ims/internal/IImsServiceController.aidl
+++ b/telephony/java/com/android/ims/internal/IImsServiceController.aidl
@@ -18,7 +18,6 @@
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 @@
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 1bc0369..a603cd3 100644
--- a/telephony/java/com/android/ims/internal/IImsUtListener.aidl
+++ b/telephony/java/com/android/ims/internal/IImsUtListener.aidl
@@ -18,11 +18,11 @@
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 80958c0..f323a0c 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.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 @@
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 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 @@
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 f8a040d..0ed0820 100644
--- a/telephony/java/com/android/internal/telephony/IPhoneSubInfo.aidl
+++ b/telephony/java/com/android/internal/telephony/IPhoneSubInfo.aidl
@@ -202,14 +202,6 @@
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 dfb3c34..2b4c059 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.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 @@
* 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 @@
* @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 @@
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);
/**
- * Get IImsMMTelFeature binder from ImsResolver that corresponds to the subId and MMTel feature
- * as well as registering the MMTelFeature for callbacks using the IImsServiceFeatureCallback
+ * 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
* 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 @@
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 ccf57b0..62fed61 100644
--- a/test-base/Android.bp
+++ b/test-base/Android.bp
@@ -83,28 +83,3 @@
"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 0000000..d2af8a9
--- /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 0000000..b8c5326
--- /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 229a6ac..706f636 100644
--- a/test-runner/Android.mk
+++ b/test-runner/Android.mk
@@ -117,23 +117,5 @@
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 994f3cc..9130e7d 100644
--- a/tests/net/Android.mk
+++ b/tests/net/Android.mk
@@ -39,6 +39,7 @@
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 2ac73db..56a075b 100644
--- a/tests/net/java/com/android/server/NetworkManagementServiceTest.java
+++ b/tests/net/java/com/android/server/NetworkManagementServiceTest.java
@@ -99,8 +99,12 @@
@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 9f2cb92..8359fe2 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.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 @@
}
@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 @@
}
@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 @@
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 069360e..df483b2 100644
--- a/tools/aapt2/Resources.proto
+++ b/tools/aapt2/Resources.proto
@@ -318,7 +318,7 @@
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 81bc2c8..f1eb952 100644
--- a/tools/aapt2/format/proto/ProtoDeserialize.cpp
+++ b/tools/aapt2/format/proto/ProtoDeserialize.cpp
@@ -792,9 +792,9 @@
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 e9622f5..1d00852 100644
--- a/tools/aapt2/format/proto/ProtoSerialize.cpp
+++ b/tools/aapt2/format/proto/ProtoSerialize.cpp
@@ -460,7 +460,7 @@
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 2a2ff0c..309bc80 100644
--- a/wifi/java/android/net/wifi/IWifiManager.aidl
+++ b/wifi/java/android/net/wifi/IWifiManager.aidl
@@ -158,9 +158,6 @@
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 13b7c1a..897b1ea 100644
--- a/wifi/java/android/net/wifi/WifiManager.java
+++ b/wifi/java/android/net/wifi/WifiManager.java
@@ -3532,31 +3532,6 @@
}
/**
- * 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
diff --git a/wifi/java/android/net/wifi/aware/DiscoverySession.java b/wifi/java/android/net/wifi/aware/DiscoverySession.java
index 9f73622..699f54c 100644
--- a/wifi/java/android/net/wifi/aware/DiscoverySession.java
+++ b/wifi/java/android/net/wifi/aware/DiscoverySession.java
@@ -22,6 +22,8 @@
import android.net.NetworkSpecifier;
import android.util.Log;
+import com.android.internal.annotations.VisibleForTesting;
+
import dalvik.system.CloseGuard;
import java.lang.ref.WeakReference;
@@ -142,6 +144,34 @@
}
/**
+ * Access the client ID of the Aware session.
+ *
+ * Note: internal visibility for testing.
+ *
+ * @return The internal client ID.
+ *
+ * @hide
+ */
+ @VisibleForTesting
+ public int getClientId() {
+ return mClientId;
+ }
+
+ /**
+ * Access the discovery session ID of the Aware session.
+ *
+ * Note: internal visibility for testing.
+ *
+ * @return The internal discovery session ID.
+ *
+ * @hide
+ */
+ @VisibleForTesting
+ public int getSessionId() {
+ return mSessionId;
+ }
+
+ /**
* Sends a message to the specified destination. Aware messages are transmitted in the context
* of a discovery session - executed subsequent to a publish/subscribe
* {@link DiscoverySessionCallback#onServiceDiscovered(PeerHandle,
@@ -246,8 +276,7 @@
* or
* {@link DiscoverySessionCallback#onMessageReceived(PeerHandle, byte[])}.
* On a RESPONDER this value is used to gate the acceptance of a connection
- * request from only that peer. A RESPONDER may specify a {@code null} -
- * indicating that it will accept connection requests from any device.
+ * request from only that peer.
*
* @return A {@link NetworkSpecifier} to be used to construct
* {@link android.net.NetworkRequest.Builder#setNetworkSpecifier(NetworkSpecifier)} to pass to
@@ -255,7 +284,7 @@
* android.net.ConnectivityManager.NetworkCallback)}
* [or other varieties of that API].
*/
- public NetworkSpecifier createNetworkSpecifierOpen(@Nullable PeerHandle peerHandle) {
+ public NetworkSpecifier createNetworkSpecifierOpen(@NonNull PeerHandle peerHandle) {
if (mTerminated) {
Log.w(TAG, "createNetworkSpecifierOpen: called on terminated session");
return null;
@@ -295,8 +324,7 @@
* byte[], java.util.List)} or
* {@link DiscoverySessionCallback#onMessageReceived(PeerHandle,
* byte[])}. On a RESPONDER this value is used to gate the acceptance of a connection request
- * from only that peer. A RESPONDER may specify a {@code null} - indicating
- * that it will accept connection requests from any device.
+ * from only that peer.
* @param passphrase The passphrase to be used to encrypt the link. The PMK is generated from
* the passphrase. Use the
* {@link #createNetworkSpecifierOpen(PeerHandle)} API to
@@ -309,7 +337,7 @@
* [or other varieties of that API].
*/
public NetworkSpecifier createNetworkSpecifierPassphrase(
- @Nullable PeerHandle peerHandle, @NonNull String passphrase) {
+ @NonNull PeerHandle peerHandle, @NonNull String passphrase) {
if (!WifiAwareUtils.validatePassphrase(passphrase)) {
throw new IllegalArgumentException("Passphrase must meet length requirements");
}
@@ -354,8 +382,7 @@
* byte[], java.util.List)} or
* {@link DiscoverySessionCallback#onMessageReceived(PeerHandle,
* byte[])}. On a RESPONDER this value is used to gate the acceptance of a connection request
- * from only that peer. A RESPONDER may specify a null - indicating that
- * it will accept connection requests from any device.
+ * from only that peer.
* @param pmk A PMK (pairwise master key, see IEEE 802.11i) specifying the key to use for
* encrypting the data-path. Use the
* {@link #createNetworkSpecifierPassphrase(PeerHandle, String)} to specify a
@@ -371,7 +398,7 @@
* @hide
*/
@SystemApi
- public NetworkSpecifier createNetworkSpecifierPmk(@Nullable PeerHandle peerHandle,
+ public NetworkSpecifier createNetworkSpecifierPmk(@NonNull PeerHandle peerHandle,
@NonNull byte[] pmk) {
if (!WifiAwareUtils.validatePmk(pmk)) {
throw new IllegalArgumentException("PMK must 32 bytes");
diff --git a/wifi/java/android/net/wifi/aware/WifiAwareManager.java b/wifi/java/android/net/wifi/aware/WifiAwareManager.java
index 2f0c316..06a5c2e 100644
--- a/wifi/java/android/net/wifi/aware/WifiAwareManager.java
+++ b/wifi/java/android/net/wifi/aware/WifiAwareManager.java
@@ -406,7 +406,7 @@
/** @hide */
public NetworkSpecifier createNetworkSpecifier(int clientId, int role, int sessionId,
- PeerHandle peerHandle, @Nullable byte[] pmk, @Nullable String passphrase) {
+ @NonNull PeerHandle peerHandle, @Nullable byte[] pmk, @Nullable String passphrase) {
if (VDBG) {
Log.v(TAG, "createNetworkSpecifier: role=" + role + ", sessionId=" + sessionId
+ ", peerHandle=" + ((peerHandle == null) ? peerHandle : peerHandle.peerId)
@@ -420,12 +420,9 @@
"createNetworkSpecifier: Invalid 'role' argument when creating a network "
+ "specifier");
}
- if (role == WIFI_AWARE_DATA_PATH_ROLE_INITIATOR) {
- if (peerHandle == null) {
- throw new IllegalArgumentException(
- "createNetworkSpecifier: Invalid peer handle (value of null) - not "
- + "permitted on INITIATOR");
- }
+ if (peerHandle == null) {
+ throw new IllegalArgumentException(
+ "createNetworkSpecifier: Invalid peer handle - cannot be null");
}
return new WifiAwareNetworkSpecifier(
@@ -443,7 +440,7 @@
/** @hide */
public NetworkSpecifier createNetworkSpecifier(int clientId, @DataPathRole int role,
- @Nullable byte[] peer, @Nullable byte[] pmk, @Nullable String passphrase) {
+ @NonNull byte[] peer, @Nullable byte[] pmk, @Nullable String passphrase) {
if (VDBG) {
Log.v(TAG, "createNetworkSpecifier: role=" + role
+ ", pmk=" + ((pmk == null) ? "null" : "non-null")
@@ -456,11 +453,9 @@
"createNetworkSpecifier: Invalid 'role' argument when creating a network "
+ "specifier");
}
- if (role == WIFI_AWARE_DATA_PATH_ROLE_INITIATOR) {
- if (peer == null) {
- throw new IllegalArgumentException("createNetworkSpecifier: Invalid peer MAC "
- + "address - null not permitted on INITIATOR");
- }
+ if (peer == null) {
+ throw new IllegalArgumentException(
+ "createNetworkSpecifier: Invalid peer MAC - cannot be null");
}
if (peer != null && peer.length != 6) {
throw new IllegalArgumentException("createNetworkSpecifier: Invalid peer MAC address");
diff --git a/wifi/java/android/net/wifi/aware/WifiAwareSession.java b/wifi/java/android/net/wifi/aware/WifiAwareSession.java
index f26b9f5..3219653 100644
--- a/wifi/java/android/net/wifi/aware/WifiAwareSession.java
+++ b/wifi/java/android/net/wifi/aware/WifiAwareSession.java
@@ -25,6 +25,8 @@
import android.os.Looper;
import android.util.Log;
+import com.android.internal.annotations.VisibleForTesting;
+
import dalvik.system.CloseGuard;
import java.lang.ref.WeakReference;
@@ -97,6 +99,20 @@
}
/**
+ * Access the client ID of the Aware session.
+ *
+ * Note: internal visibility for testing.
+ *
+ * @return The internal client ID.
+ *
+ * @hide
+ */
+ @VisibleForTesting
+ public int getClientId() {
+ return mClientId;
+ }
+
+ /**
* Issue a request to the Aware service to create a new Aware publish discovery session, using
* the specified {@code publishConfig} configuration. The results of the publish operation
* are routed to the callbacks of {@link DiscoverySessionCallback}:
@@ -207,8 +223,7 @@
* {@link WifiAwareManager#WIFI_AWARE_DATA_PATH_ROLE_RESPONDER}
* @param peer The MAC address of the peer's Aware discovery interface. On a RESPONDER this
* value is used to gate the acceptance of a connection request from only that
- * peer. A RESPONDER may specify a {@code null} - indicating that it will accept
- * connection requests from any device.
+ * peer.
*
* @return A {@link NetworkSpecifier} to be used to construct
* {@link android.net.NetworkRequest.Builder#setNetworkSpecifier(NetworkSpecifier)} to pass to
@@ -217,7 +232,7 @@
* [or other varieties of that API].
*/
public NetworkSpecifier createNetworkSpecifierOpen(
- @WifiAwareManager.DataPathRole int role, @Nullable byte[] peer) {
+ @WifiAwareManager.DataPathRole int role, @NonNull byte[] peer) {
WifiAwareManager mgr = mMgr.get();
if (mgr == null) {
Log.e(TAG, "createNetworkSpecifierOpen: called post GC on WifiAwareManager");
@@ -246,8 +261,7 @@
* {@link WifiAwareManager#WIFI_AWARE_DATA_PATH_ROLE_RESPONDER}
* @param peer The MAC address of the peer's Aware discovery interface. On a RESPONDER this
* value is used to gate the acceptance of a connection request from only that
- * peer. A RESPONDER may specify a {@code null} - indicating that it will accept
- * connection requests from any device.
+ * peer.
* @param passphrase The passphrase to be used to encrypt the link. The PMK is generated from
* the passphrase. Use {@link #createNetworkSpecifierOpen(int, byte[])} to
* specify an open (unencrypted) link.
@@ -259,7 +273,7 @@
* [or other varieties of that API].
*/
public NetworkSpecifier createNetworkSpecifierPassphrase(
- @WifiAwareManager.DataPathRole int role, @Nullable byte[] peer,
+ @WifiAwareManager.DataPathRole int role, @NonNull byte[] peer,
@NonNull String passphrase) {
WifiAwareManager mgr = mMgr.get();
if (mgr == null) {
@@ -293,8 +307,7 @@
* {@link WifiAwareManager#WIFI_AWARE_DATA_PATH_ROLE_RESPONDER}
* @param peer The MAC address of the peer's Aware discovery interface. On a RESPONDER this
* value is used to gate the acceptance of a connection request from only that
- * peer. A RESPONDER may specify a null - indicating that it will accept
- * connection requests from any device.
+ * peer.
* @param pmk A PMK (pairwise master key, see IEEE 802.11i) specifying the key to use for
* encrypting the data-path. Use the
* {@link #createNetworkSpecifierPassphrase(int, byte[], String)} to specify a
@@ -311,7 +324,7 @@
*/
@SystemApi
public NetworkSpecifier createNetworkSpecifierPmk(
- @WifiAwareManager.DataPathRole int role, @Nullable byte[] peer, @NonNull byte[] pmk) {
+ @WifiAwareManager.DataPathRole int role, @NonNull byte[] peer, @NonNull byte[] pmk) {
WifiAwareManager mgr = mMgr.get();
if (mgr == null) {
Log.e(TAG, "createNetworkSpecifierPmk: called post GC on WifiAwareManager");
diff --git a/wifi/tests/src/android/net/wifi/aware/WifiAwareManagerTest.java b/wifi/tests/src/android/net/wifi/aware/WifiAwareManagerTest.java
index b0a048d..84e3ed9 100644
--- a/wifi/tests/src/android/net/wifi/aware/WifiAwareManagerTest.java
+++ b/wifi/tests/src/android/net/wifi/aware/WifiAwareManagerTest.java
@@ -1022,7 +1022,7 @@
*/
@Test(expected = IllegalArgumentException.class)
public void testNetworkSpecifierWithClientNullPmk() throws Exception {
- executeNetworkSpecifierWithClient(true, null, null);
+ executeNetworkSpecifierWithClient(new PeerHandle(123412), true, null, null);
}
/**
@@ -1030,7 +1030,7 @@
*/
@Test(expected = IllegalArgumentException.class)
public void testNetworkSpecifierWithClientIncorrectLengthPmk() throws Exception {
- executeNetworkSpecifierWithClient(true, "012".getBytes(), null);
+ executeNetworkSpecifierWithClient(new PeerHandle(123412), true, "012".getBytes(), null);
}
/**
@@ -1038,7 +1038,7 @@
*/
@Test(expected = IllegalArgumentException.class)
public void testNetworkSpecifierWithClientNullPassphrase() throws Exception {
- executeNetworkSpecifierWithClient(false, null, null);
+ executeNetworkSpecifierWithClient(new PeerHandle(123412), false, null, null);
}
/**
@@ -1046,7 +1046,7 @@
*/
@Test(expected = IllegalArgumentException.class)
public void testNetworkSpecifierWithClientShortPassphrase() throws Exception {
- executeNetworkSpecifierWithClient(false, null, "012");
+ executeNetworkSpecifierWithClient(new PeerHandle(123412), false, null, "012");
}
/**
@@ -1054,15 +1054,23 @@
*/
@Test(expected = IllegalArgumentException.class)
public void testNetworkSpecifierWithClientLongPassphrase() throws Exception {
- executeNetworkSpecifierWithClient(false, null,
+ executeNetworkSpecifierWithClient(new PeerHandle(123412), false, null,
"0123456789012345678901234567890123456789012345678901234567890123456789");
}
- private void executeNetworkSpecifierWithClient(boolean doPmk, byte[] pmk, String passphrase)
- throws Exception {
+ /**
+ * Validate that a null PeerHandle triggers an exception.
+ */
+ @Test(expected = IllegalArgumentException.class)
+ public void testNetworkSpecifierWithClientNullPeer() throws Exception {
+ executeNetworkSpecifierWithClient(null, false, null,
+ "0123456789012345678901234567890123456789012345678901234567890123456789");
+ }
+
+ private void executeNetworkSpecifierWithClient(PeerHandle peerHandle, boolean doPmk, byte[] pmk,
+ String passphrase) throws Exception {
final int clientId = 4565;
final int sessionId = 123;
- final PeerHandle peerHandle = new PeerHandle(123412);
final ConfigRequest configRequest = new ConfigRequest.Builder().build();
final PublishConfig publishConfig = new PublishConfig.Builder().build();
@@ -1108,7 +1116,8 @@
*/
@Test(expected = IllegalArgumentException.class)
public void testNetworkSpecifierDirectNullPmk() throws Exception {
- executeNetworkSpecifierDirect(true, null, null);
+ executeNetworkSpecifierDirect(HexEncoding.decode("000102030405".toCharArray(), false), true,
+ null, null);
}
/**
@@ -1116,7 +1125,8 @@
*/
@Test(expected = IllegalArgumentException.class)
public void testNetworkSpecifierDirectIncorrectLengthPmk() throws Exception {
- executeNetworkSpecifierDirect(true, "012".getBytes(), null);
+ executeNetworkSpecifierDirect(HexEncoding.decode("000102030405".toCharArray(), false), true,
+ "012".getBytes(), null);
}
/**
@@ -1124,7 +1134,8 @@
*/
@Test(expected = IllegalArgumentException.class)
public void testNetworkSpecifierDirectNullPassphrase() throws Exception {
- executeNetworkSpecifierDirect(false, null, null);
+ executeNetworkSpecifierDirect(HexEncoding.decode("000102030405".toCharArray(), false),
+ false, null, null);
}
/**
@@ -1132,7 +1143,8 @@
*/
@Test(expected = IllegalArgumentException.class)
public void testNetworkSpecifierDirectShortPassphrase() throws Exception {
- executeNetworkSpecifierDirect(false, null, "012");
+ executeNetworkSpecifierDirect(HexEncoding.decode("000102030405".toCharArray(), false),
+ false, null, "012");
}
/**
@@ -1140,14 +1152,22 @@
*/
@Test(expected = IllegalArgumentException.class)
public void testNetworkSpecifierDirectLongPassphrase() throws Exception {
- executeNetworkSpecifierDirect(false, null,
+ executeNetworkSpecifierDirect(HexEncoding.decode("000102030405".toCharArray(), false),
+ false, null,
"0123456789012345678901234567890123456789012345678901234567890123456789");
}
- private void executeNetworkSpecifierDirect(boolean doPmk, byte[] pmk, String passphrase)
- throws Exception {
+ /**
+ * Validate that a null peer MAC triggers an exception.
+ */
+ @Test(expected = IllegalArgumentException.class)
+ public void testNetworkSpecifierDirectNullPeer() throws Exception {
+ executeNetworkSpecifierDirect(null, false, null, null);
+ }
+
+ private void executeNetworkSpecifierDirect(byte[] someMac, boolean doPmk, byte[] pmk,
+ String passphrase) throws Exception {
final int clientId = 134;
- final byte[] someMac = HexEncoding.decode("000102030405".toCharArray(), false);
final int role = WifiAwareManager.WIFI_AWARE_DATA_PATH_ROLE_INITIATOR;
final ConfigRequest configRequest = new ConfigRequest.Builder().build();