Merge "[Magnifier - 14] Follow finger instead of cursor"
diff --git a/Android.bp b/Android.bp
index 82a972a..2685ac3 100644
--- a/Android.bp
+++ b/Android.bp
@@ -471,8 +471,6 @@
"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/IImsRegistration.aidl",
- "telephony/java/android/telephony/ims/internal/aidl/IImsRegistrationCallback.aidl",
"telephony/java/android/telephony/ims/internal/aidl/IImsServiceController.aidl",
"telephony/java/android/telephony/ims/internal/aidl/IImsServiceControllerListener.aidl",
"telephony/java/android/telephony/ims/internal/aidl/IImsSmsListener.aidl",
@@ -492,6 +490,8 @@
"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",
@@ -519,7 +519,9 @@
"telephony/java/com/android/internal/telephony/ITelephony.aidl",
"telephony/java/com/android/internal/telephony/ITelephonyRegistry.aidl",
"telephony/java/com/android/internal/telephony/IWapPushManager.aidl",
+ "telephony/java/com/android/internal/telephony/euicc/IEuiccCardController.aidl",
"telephony/java/com/android/internal/telephony/euicc/IEuiccController.aidl",
+ "telephony/java/com/android/internal/telephony/euicc/IGetAllProfilesCallback.aidl",
"wifi/java/android/net/wifi/IWifiManager.aidl",
"wifi/java/android/net/wifi/aware/IWifiAwareDiscoverySessionCallback.aidl",
"wifi/java/android/net/wifi/aware/IWifiAwareEventCallback.aidl",
diff --git a/apct-tests/perftests/core/AndroidManifest.xml b/apct-tests/perftests/core/AndroidManifest.xml
index 4889941..bd0b944 100644
--- a/apct-tests/perftests/core/AndroidManifest.xml
+++ b/apct-tests/perftests/core/AndroidManifest.xml
@@ -2,6 +2,9 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.perftests.core">
+ <permission android:name="com.android.perftests.core.TestPermission" />
+ <uses-permission android:name="com.android.perftests.core.TestPermission" />
+
<uses-permission
android:name="android.permission.GET_ACCOUNTS" />
diff --git a/apct-tests/perftests/core/src/android/os/PermissionTest.java b/apct-tests/perftests/core/src/android/os/PermissionTest.java
new file mode 100644
index 0000000..d292e7d
--- /dev/null
+++ b/apct-tests/perftests/core/src/android/os/PermissionTest.java
@@ -0,0 +1,66 @@
+/*
+ * 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.os;
+
+import static android.content.pm.PackageManager.PERMISSION_DENIED;
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+
+import android.content.Context;
+import android.perftests.utils.BenchmarkState;
+import android.perftests.utils.PerfStatusReporter;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.LargeTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Assert;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@LargeTest
+public class PermissionTest {
+ private static final String PERMISSION_HAS_NAME = "com.android.perftests.core.TestPermission";
+ private static final String PERMISSION_DOESNT_HAVE_NAME =
+ "com.android.perftests.core.TestBadPermission";
+ private static final String THIS_PACKAGE_NAME = "com.android.perftests.core";
+
+ @Rule
+ public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
+
+ @Test
+ public void testHasPermission() {
+ final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ final Context context = InstrumentationRegistry.getTargetContext();
+ while (state.keepRunning()) {
+ int ret = context.getPackageManager().checkPermission(PERMISSION_HAS_NAME,
+ THIS_PACKAGE_NAME);
+ }
+ }
+
+ @Test
+ public void testDoesntHavePermission() {
+ final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ final Context context = InstrumentationRegistry.getTargetContext();
+
+ while (state.keepRunning()) {
+ int ret = context.getPackageManager().checkPermission(PERMISSION_DOESNT_HAVE_NAME,
+ THIS_PACKAGE_NAME);
+ }
+ }
+
+}
diff --git a/apct-tests/perftests/core/src/android/text/StaticLayoutPerfTest.java b/apct-tests/perftests/core/src/android/text/StaticLayoutPerfTest.java
index 5653a03..93a0fc3 100644
--- a/apct-tests/perftests/core/src/android/text/StaticLayoutPerfTest.java
+++ b/apct-tests/perftests/core/src/android/text/StaticLayoutPerfTest.java
@@ -190,7 +190,7 @@
final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
state.pauseTiming();
- final PremeasuredText text = PremeasuredText.build(
+ final MeasuredText text = MeasuredText.build(
generateRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT), PAINT, LTR);
state.resumeTiming();
@@ -206,7 +206,7 @@
final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
state.pauseTiming();
- final PremeasuredText text = PremeasuredText.build(
+ final MeasuredText text = MeasuredText.build(
generateRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT), PAINT, LTR);
state.resumeTiming();
@@ -222,7 +222,7 @@
final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
state.pauseTiming();
- final PremeasuredText text = PremeasuredText.build(
+ final MeasuredText text = MeasuredText.build(
generateRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT), PAINT, LTR);
state.resumeTiming();
@@ -238,7 +238,7 @@
final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
state.pauseTiming();
- final PremeasuredText text = PremeasuredText.build(
+ final MeasuredText text = MeasuredText.build(
generateRandomParagraph(WORD_LENGTH, NO_STYLE_TEXT), PAINT, LTR);
state.resumeTiming();
@@ -254,7 +254,7 @@
final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
state.pauseTiming();
- final PremeasuredText text = PremeasuredText.build(
+ final MeasuredText text = MeasuredText.build(
generateRandomParagraph(WORD_LENGTH, STYLE_TEXT), PAINT, LTR);
state.resumeTiming();
diff --git a/apct-tests/perftests/utils/src/android/perftests/utils/BenchmarkState.java b/apct-tests/perftests/utils/src/android/perftests/utils/BenchmarkState.java
index bb9dc4a..da17818 100644
--- a/apct-tests/perftests/utils/src/android/perftests/utils/BenchmarkState.java
+++ b/apct-tests/perftests/utils/src/android/perftests/utils/BenchmarkState.java
@@ -25,7 +25,6 @@
import java.io.File;
import java.util.ArrayList;
-import java.util.Collections;
import java.util.concurrent.TimeUnit;
/**
@@ -78,10 +77,7 @@
// Statistics. These values will be filled when the benchmark has finished.
// The computation needs double precision, but long int is fine for final reporting.
- private long mMedian = 0;
- private double mMean = 0.0;
- private double mStandardDeviation = 0.0;
- private long mMin = 0;
+ private Stats mStats;
// Individual duration in nano seconds.
private ArrayList<Long> mResults = new ArrayList<>();
@@ -90,36 +86,6 @@
return TimeUnit.MILLISECONDS.toNanos(ms);
}
- /**
- * Calculates statistics.
- */
- private void calculateSatistics() {
- final int size = mResults.size();
- if (size <= 1) {
- throw new IllegalStateException("At least two results are necessary.");
- }
-
- Collections.sort(mResults);
- mMedian = size % 2 == 0 ? (mResults.get(size / 2) + mResults.get(size / 2 + 1)) / 2 :
- mResults.get(size / 2);
-
- mMin = mResults.get(0);
- for (int i = 0; i < size; ++i) {
- long result = mResults.get(i);
- mMean += result;
- if (result < mMin) {
- mMin = result;
- }
- }
- mMean /= (double) size;
-
- for (int i = 0; i < size; ++i) {
- final double tmp = mResults.get(i) - mMean;
- mStandardDeviation += tmp * tmp;
- }
- mStandardDeviation = Math.sqrt(mStandardDeviation / (double) (size - 1));
- }
-
// Stops the benchmark timer.
// This method can be called only when the timer is running.
public void pauseTiming() {
@@ -173,7 +139,7 @@
if (ENABLE_PROFILING) {
Debug.stopMethodTracing();
}
- calculateSatistics();
+ mStats = new Stats(mResults);
mState = FINISHED;
return false;
}
@@ -224,28 +190,28 @@
if (mState != FINISHED) {
throw new IllegalStateException("The benchmark hasn't finished");
}
- return (long) mMean;
+ return (long) mStats.getMean();
}
private long median() {
if (mState != FINISHED) {
throw new IllegalStateException("The benchmark hasn't finished");
}
- return mMedian;
+ return mStats.getMedian();
}
private long min() {
if (mState != FINISHED) {
throw new IllegalStateException("The benchmark hasn't finished");
}
- return mMin;
+ return mStats.getMin();
}
private long standardDeviation() {
if (mState != FINISHED) {
throw new IllegalStateException("The benchmark hasn't finished");
}
- return (long) mStandardDeviation;
+ return (long) mStats.getStandardDeviation();
}
private String summaryLine() {
diff --git a/apct-tests/perftests/utils/src/android/perftests/utils/ManualBenchmarkState.java b/apct-tests/perftests/utils/src/android/perftests/utils/ManualBenchmarkState.java
new file mode 100644
index 0000000..2c84db1
--- /dev/null
+++ b/apct-tests/perftests/utils/src/android/perftests/utils/ManualBenchmarkState.java
@@ -0,0 +1,157 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.perftests.utils;
+
+import android.app.Activity;
+import android.app.Instrumentation;
+import android.os.Bundle;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Provides a benchmark framework.
+ *
+ * This differs from BenchmarkState in that rather than the class measuring the the elapsed time,
+ * the test passes in the elapsed time.
+ *
+ * Example usage:
+ *
+ * public void sampleMethod() {
+ * ManualBenchmarkState state = new ManualBenchmarkState();
+ *
+ * int[] src = new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
+ * long elapsedTime = 0;
+ * while (state.keepRunning(elapsedTime)) {
+ * long startTime = System.nanoTime();
+ * int[] dest = new int[src.length];
+ * System.arraycopy(src, 0, dest, 0, src.length);
+ * elapsedTime = System.nanoTime() - startTime;
+ * }
+ * System.out.println(state.summaryLine());
+ * }
+ *
+ * Or use the PerfManualStatusReporter TestRule.
+ *
+ * Make sure that the overhead of checking the clock does not noticeably affect the results.
+ */
+public final class ManualBenchmarkState {
+ private static final String TAG = ManualBenchmarkState.class.getSimpleName();
+
+ // TODO: Tune these values.
+ // warm-up for duration
+ private static final long WARMUP_DURATION_NS = TimeUnit.SECONDS.toNanos(5);
+ // minimum iterations to warm-up for
+ private static final int WARMUP_MIN_ITERATIONS = 8;
+
+ // target testing for duration
+ private static final long TARGET_TEST_DURATION_NS = TimeUnit.SECONDS.toNanos(16);
+ private static final int MAX_TEST_ITERATIONS = 1000000;
+ private static final int MIN_TEST_ITERATIONS = 10;
+
+ private static final int NOT_STARTED = 0; // The benchmark has not started yet.
+ private static final int WARMUP = 1; // The benchmark is warming up.
+ private static final int RUNNING = 2; // The benchmark is running.
+ private static final int FINISHED = 3; // The benchmark has stopped.
+
+ private int mState = NOT_STARTED; // Current benchmark state.
+
+ private long mWarmupStartTime = 0;
+ private int mWarmupIterations = 0;
+
+ private int mMaxIterations = 0;
+
+ // Individual duration in nano seconds.
+ private ArrayList<Long> mResults = new ArrayList<>();
+
+ // Statistics. These values will be filled when the benchmark has finished.
+ // The computation needs double precision, but long int is fine for final reporting.
+ private Stats mStats;
+
+ private void beginBenchmark(long warmupDuration, int iterations) {
+ mMaxIterations = (int) (TARGET_TEST_DURATION_NS / (warmupDuration / iterations));
+ mMaxIterations = Math.min(MAX_TEST_ITERATIONS,
+ Math.max(mMaxIterations, MIN_TEST_ITERATIONS));
+ mState = RUNNING;
+ }
+
+ /**
+ * Judges whether the benchmark needs more samples.
+ *
+ * For the usage, see class comment.
+ */
+ public boolean keepRunning(long duration) {
+ if (duration < 0) {
+ throw new RuntimeException("duration is negative: " + duration);
+ }
+ switch (mState) {
+ case NOT_STARTED:
+ mState = WARMUP;
+ mWarmupStartTime = System.nanoTime();
+ return true;
+ case WARMUP: {
+ final long timeSinceStartingWarmup = System.nanoTime() - mWarmupStartTime;
+ ++mWarmupIterations;
+ if (mWarmupIterations >= WARMUP_MIN_ITERATIONS
+ && timeSinceStartingWarmup >= WARMUP_DURATION_NS) {
+ beginBenchmark(timeSinceStartingWarmup, mWarmupIterations);
+ }
+ return true;
+ }
+ case RUNNING: {
+ mResults.add(duration);
+ final boolean keepRunning = mResults.size() < mMaxIterations;
+ if (!keepRunning) {
+ mStats = new Stats(mResults);
+ mState = FINISHED;
+ }
+ return keepRunning;
+ }
+ case FINISHED:
+ throw new IllegalStateException("The benchmark has finished.");
+ default:
+ throw new IllegalStateException("The benchmark is in an unknown state.");
+ }
+ }
+
+ private String summaryLine() {
+ final StringBuilder sb = new StringBuilder();
+ sb.append("Summary: ");
+ sb.append("median=").append(mStats.getMedian()).append("ns, ");
+ sb.append("mean=").append(mStats.getMean()).append("ns, ");
+ sb.append("min=").append(mStats.getMin()).append("ns, ");
+ sb.append("max=").append(mStats.getMax()).append("ns, ");
+ sb.append("sigma=").append(mStats.getStandardDeviation()).append(", ");
+ sb.append("iteration=").append(mResults.size()).append(", ");
+ sb.append("values=").append(mResults.toString());
+ return sb.toString();
+ }
+
+ public void sendFullStatusReport(Instrumentation instrumentation, String key) {
+ if (mState != FINISHED) {
+ throw new IllegalStateException("The benchmark hasn't finished");
+ }
+ Log.i(TAG, key + summaryLine());
+ final Bundle status = new Bundle();
+ status.putLong(key + "_median", mStats.getMedian());
+ status.putLong(key + "_mean", (long) mStats.getMean());
+ status.putLong(key + "_stddev", (long) mStats.getStandardDeviation());
+ instrumentation.sendStatus(Activity.RESULT_OK, status);
+ }
+}
+
diff --git a/apct-tests/perftests/utils/src/android/perftests/utils/PerfManualStatusReporter.java b/apct-tests/perftests/utils/src/android/perftests/utils/PerfManualStatusReporter.java
new file mode 100644
index 0000000..0de6f1d
--- /dev/null
+++ b/apct-tests/perftests/utils/src/android/perftests/utils/PerfManualStatusReporter.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.perftests.utils;
+
+import android.support.test.InstrumentationRegistry;
+
+import org.junit.rules.TestRule;
+import org.junit.runner.Description;
+import org.junit.runners.model.Statement;
+
+/**
+ * Use this rule to make sure we report the status after the test success.
+ *
+ * <code>
+ *
+ * @Rule public PerfManualStatusReporter mPerfStatusReporter = new PerfManualStatusReporter();
+ * @Test public void functionName() {
+ * ManualBenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ *
+ * int[] src = new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
+ * long elapsedTime = 0;
+ * while (state.keepRunning(elapsedTime)) {
+ * long startTime = System.nanoTime();
+ * int[] dest = new int[src.length];
+ * System.arraycopy(src, 0, dest, 0, src.length);
+ * elapsedTime = System.nanoTime() - startTime;
+ * }
+ * }
+ * </code>
+ *
+ * When test succeeded, the status report will use the key as
+ * "functionName_*"
+ */
+
+public class PerfManualStatusReporter implements TestRule {
+ private final ManualBenchmarkState mState;
+
+ public PerfManualStatusReporter() {
+ mState = new ManualBenchmarkState();
+ }
+
+ public ManualBenchmarkState getBenchmarkState() {
+ return mState;
+ }
+
+ @Override
+ public Statement apply(Statement base, Description description) {
+ return new Statement() {
+ @Override
+ public void evaluate() throws Throwable {
+ base.evaluate();
+
+ mState.sendFullStatusReport(InstrumentationRegistry.getInstrumentation(),
+ description.getMethodName());
+ }
+ };
+ }
+}
+
diff --git a/apct-tests/perftests/utils/src/android/perftests/utils/Stats.java b/apct-tests/perftests/utils/src/android/perftests/utils/Stats.java
new file mode 100644
index 0000000..acc44a8
--- /dev/null
+++ b/apct-tests/perftests/utils/src/android/perftests/utils/Stats.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.perftests.utils;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+public class Stats {
+ private long mMedian, mMin, mMax;
+ private double mMean, mStandardDeviation;
+
+ /* Calculate stats in constructor. */
+ public Stats(List<Long> values) {
+ // make a copy since we're modifying it
+ values = new ArrayList<>(values);
+ final int size = values.size();
+ if (size < 2) {
+ throw new IllegalArgumentException("At least two results are necessary.");
+ }
+
+ Collections.sort(values);
+
+ mMedian = size % 2 == 0 ? (values.get(size / 2) + values.get(size / 2 - 1)) / 2 :
+ values.get(size / 2);
+
+ mMin = values.get(0);
+ mMax = values.get(values.size() - 1);
+
+ for (int i = 0; i < size; ++i) {
+ long result = values.get(i);
+ mMean += result;
+ }
+ mMean /= (double) size;
+
+ for (int i = 0; i < size; ++i) {
+ final double tmp = values.get(i) - mMean;
+ mStandardDeviation += tmp * tmp;
+ }
+ mStandardDeviation = Math.sqrt(mStandardDeviation / (double) (size - 1));
+ }
+
+ public double getMean() {
+ return mMean;
+ }
+
+ public long getMedian() {
+ return mMedian;
+ }
+
+ public long getMax() {
+ return mMax;
+ }
+
+ public long getMin() {
+ return mMin;
+ }
+
+ public double getStandardDeviation() {
+ return mStandardDeviation;
+ }
+}
diff --git a/api/current.txt b/api/current.txt
index 484ca48..77241da 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -212,6 +212,7 @@
field public static final int accessibilityFeedbackType = 16843650; // 0x1010382
field public static final int accessibilityFlags = 16843652; // 0x1010384
field public static final int accessibilityLiveRegion = 16843758; // 0x10103ee
+ field public static final int accessibilityPaneTitle = 16844156; // 0x101057c
field public static final int accessibilityTraversalAfter = 16843986; // 0x10104d2
field public static final int accessibilityTraversalBefore = 16843985; // 0x10104d1
field public static final int accountPreferences = 16843423; // 0x101029f
@@ -5290,8 +5291,19 @@
method public android.os.Bundle getExtras();
method public android.graphics.drawable.Icon getIcon();
method public android.app.RemoteInput[] getRemoteInputs();
+ method public int getSemanticAction();
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator<android.app.Notification.Action> CREATOR;
+ field public static final int SEMANTIC_ACTION_ARCHIVE = 5; // 0x5
+ field public static final int SEMANTIC_ACTION_DELETE = 4; // 0x4
+ field public static final int SEMANTIC_ACTION_MARK_AS_READ = 2; // 0x2
+ field public static final int SEMANTIC_ACTION_MARK_AS_UNREAD = 3; // 0x3
+ field public static final int SEMANTIC_ACTION_MUTE = 6; // 0x6
+ field public static final int SEMANTIC_ACTION_NONE = 0; // 0x0
+ field public static final int SEMANTIC_ACTION_REPLY = 1; // 0x1
+ field public static final int SEMANTIC_ACTION_THUMBS_DOWN = 9; // 0x9
+ field public static final int SEMANTIC_ACTION_THUMBS_UP = 8; // 0x8
+ field public static final int SEMANTIC_ACTION_UNMUTE = 7; // 0x7
field public android.app.PendingIntent actionIntent;
field public deprecated int icon;
field public java.lang.CharSequence title;
@@ -5307,6 +5319,7 @@
method public android.app.Notification.Action.Builder extend(android.app.Notification.Action.Extender);
method public android.os.Bundle getExtras();
method public android.app.Notification.Action.Builder setAllowGeneratedReplies(boolean);
+ method public android.app.Notification.Action.Builder setSemanticAction(int);
}
public static abstract interface Notification.Action.Extender {
@@ -6531,6 +6544,7 @@
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 boolean switchUser(android.content.ComponentName, android.os.UserHandle);
method public void transferOwnership(android.content.ComponentName, android.content.ComponentName, android.os.PersistableBundle);
@@ -6643,7 +6657,6 @@
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 START_USER_IN_BACKGROUND = 8; // 0x8
field public static final int WIPE_EXTERNAL_STORAGE = 1; // 0x1
field public static final int WIPE_RESET_PROTECTION_DATA = 2; // 0x2
}
@@ -20869,7 +20882,6 @@
method public int getMaxWidth();
method public java.lang.CharSequence getTextForImeAction(int);
method public android.app.Dialog getWindow();
- method public void hideSoftInputFromInputMethod(int);
method public void hideStatusIcon();
method public void hideWindow();
method public boolean isExtractViewShown();
@@ -20917,6 +20929,7 @@
method public void onWindowHidden();
method public void onWindowShown();
method public void requestHideSelf(int);
+ method public void requestShowSelf(int);
method public boolean sendDefaultEditorAction(boolean);
method public void sendDownUpKeyEvents(int);
method public void sendKeyChar(char);
@@ -20929,7 +20942,6 @@
method public void setInputMethodAndSubtype(java.lang.String, android.view.inputmethod.InputMethodSubtype);
method public void setInputView(android.view.View);
method public boolean shouldOfferSwitchingToNextInputMethod();
- method public void showSoftInputFromInputMethod(int);
method public void showStatusIcon(int);
method public void showWindow(boolean);
method public void switchInputMethod(java.lang.String);
@@ -26327,10 +26339,10 @@
}
public final class MacAddress implements android.os.Parcelable {
- method public int addressType();
method public int describeContents();
method public static android.net.MacAddress fromBytes(byte[]);
method public static android.net.MacAddress fromString(java.lang.String);
+ method public int getAddressType();
method public boolean isLocallyAssigned();
method public byte[] toByteArray();
method public java.lang.String toOuiString();
@@ -26340,7 +26352,6 @@
field public static final int TYPE_BROADCAST = 3; // 0x3
field public static final int TYPE_MULTICAST = 2; // 0x2
field public static final int TYPE_UNICAST = 1; // 0x1
- field public static final int TYPE_UNKNOWN = 0; // 0x0
}
public class MailTo {
@@ -32120,6 +32131,7 @@
}
public final class PowerManager {
+ method public int getLocationPowerSaveMode();
method public boolean isDeviceIdleMode();
method public boolean isIgnoringBatteryOptimizations(java.lang.String);
method public boolean isInteractive();
@@ -32133,6 +32145,10 @@
field public static final java.lang.String ACTION_DEVICE_IDLE_MODE_CHANGED = "android.os.action.DEVICE_IDLE_MODE_CHANGED";
field public static final java.lang.String ACTION_POWER_SAVE_MODE_CHANGED = "android.os.action.POWER_SAVE_MODE_CHANGED";
field public static final deprecated int FULL_WAKE_LOCK = 26; // 0x1a
+ field public static final int LOCATION_MODE_ALL_DISABLED_WHEN_SCREEN_OFF = 2; // 0x2
+ field public static final int LOCATION_MODE_FOREGROUND_ONLY = 3; // 0x3
+ field public static final int LOCATION_MODE_GPS_DISABLED_WHEN_SCREEN_OFF = 1; // 0x1
+ field public static final int LOCATION_MODE_NO_CHANGE = 0; // 0x0
field public static final int ON_AFTER_RELEASE = 536870912; // 0x20000000
field public static final int PARTIAL_WAKE_LOCK = 1; // 0x1
field public static final int PROXIMITY_SCREEN_OFF_WAKE_LOCK = 32; // 0x20
@@ -32420,22 +32436,24 @@
method public boolean isUserRunningOrStopping(android.os.UserHandle);
method public boolean isUserUnlocked();
method public boolean isUserUnlocked(android.os.UserHandle);
+ method public boolean requestQuietModeEnabled(boolean, android.os.UserHandle);
method public deprecated boolean setRestrictionsChallenge(java.lang.String);
method public deprecated void setUserRestriction(java.lang.String, boolean);
method public deprecated void setUserRestrictions(android.os.Bundle);
method public deprecated void setUserRestrictions(android.os.Bundle, android.os.UserHandle);
method public static boolean supportsMultipleUsers();
- method public boolean trySetQuietModeEnabled(boolean, android.os.UserHandle);
field public static final java.lang.String ALLOW_PARENT_PROFILE_APP_LINKING = "allow_parent_profile_app_linking";
field public static final java.lang.String DISALLOW_ADD_MANAGED_PROFILE = "no_add_managed_profile";
field public static final java.lang.String DISALLOW_ADD_USER = "no_add_user";
field public static final java.lang.String DISALLOW_ADJUST_VOLUME = "no_adjust_volume";
field public static final java.lang.String DISALLOW_AIRPLANE_MODE = "no_airplane_mode";
+ field public static final java.lang.String DISALLOW_AMBIENT_DISPLAY = "no_ambient_display";
field public static final java.lang.String DISALLOW_APPS_CONTROL = "no_control_apps";
field public static final java.lang.String DISALLOW_AUTOFILL = "no_autofill";
field public static final java.lang.String DISALLOW_BLUETOOTH = "no_bluetooth";
field public static final java.lang.String DISALLOW_BLUETOOTH_SHARING = "no_bluetooth_sharing";
field public static final java.lang.String DISALLOW_CONFIG_BLUETOOTH = "no_config_bluetooth";
+ field public static final java.lang.String DISALLOW_CONFIG_BRIGHTNESS = "no_config_brightness";
field public static final java.lang.String DISALLOW_CONFIG_CELL_BROADCASTS = "no_config_cell_broadcasts";
field public static final java.lang.String DISALLOW_CONFIG_CREDENTIALS = "no_config_credentials";
field public static final java.lang.String DISALLOW_CONFIG_DATE_TIME = "no_config_date_time";
@@ -32463,6 +32481,7 @@
field public static final java.lang.String DISALLOW_SAFE_BOOT = "no_safe_boot";
field public static final java.lang.String DISALLOW_SET_USER_ICON = "no_set_user_icon";
field public static final java.lang.String DISALLOW_SET_WALLPAPER = "no_set_wallpaper";
+ field public static final java.lang.String DISALLOW_SHARE_INTO_MANAGED_PROFILE = "no_sharing_into_profile";
field public static final java.lang.String DISALLOW_SHARE_LOCATION = "no_share_location";
field public static final java.lang.String DISALLOW_SMS = "no_sms";
field public static final java.lang.String DISALLOW_SYSTEM_ERROR_DIALOGS = "no_system_error_dialogs";
@@ -41126,6 +41145,7 @@
method public void onServiceStateChanged(android.telephony.ServiceState);
method public deprecated void onSignalStrengthChanged(int);
method public void onSignalStrengthsChanged(android.telephony.SignalStrength);
+ method public void onUserMobileDataStateChanged(boolean);
field public static final int LISTEN_CALL_FORWARDING_INDICATOR = 8; // 0x8
field public static final int LISTEN_CALL_STATE = 32; // 0x20
field public static final int LISTEN_CELL_INFO = 1024; // 0x400
@@ -41137,6 +41157,7 @@
field public static final int LISTEN_SERVICE_STATE = 1; // 0x1
field public static final deprecated int LISTEN_SIGNAL_STRENGTH = 2; // 0x2
field public static final int LISTEN_SIGNAL_STRENGTHS = 256; // 0x100
+ field public static final int LISTEN_USER_MOBILE_DATA_STATE = 524288; // 0x80000
}
public final class RadioAccessSpecifier implements android.os.Parcelable {
@@ -42273,20 +42294,9 @@
method public boolean isAllowed(char);
}
- public abstract interface NoCopySpan {
- }
-
- public static class NoCopySpan.Concrete implements android.text.NoCopySpan {
- ctor public NoCopySpan.Concrete();
- }
-
- public abstract interface ParcelableSpan implements android.os.Parcelable {
- method public abstract int getSpanTypeId();
- }
-
- public class PremeasuredText implements android.text.Spanned {
- method public static android.text.PremeasuredText build(java.lang.CharSequence, android.text.TextPaint, android.text.TextDirectionHeuristic);
- method public static android.text.PremeasuredText build(java.lang.CharSequence, android.text.TextPaint, android.text.TextDirectionHeuristic, int, int);
+ public class MeasuredText implements android.text.Spanned {
+ method public static android.text.MeasuredText build(java.lang.CharSequence, android.text.TextPaint, android.text.TextDirectionHeuristic);
+ method public static android.text.MeasuredText build(java.lang.CharSequence, android.text.TextPaint, android.text.TextDirectionHeuristic, int, int);
method public char charAt(int);
method public int getEnd();
method public android.text.TextPaint getPaint();
@@ -42305,6 +42315,17 @@
method public java.lang.CharSequence subSequence(int, int);
}
+ public abstract interface NoCopySpan {
+ }
+
+ public static class NoCopySpan.Concrete implements android.text.NoCopySpan {
+ ctor public NoCopySpan.Concrete();
+ }
+
+ public abstract interface ParcelableSpan implements android.os.Parcelable {
+ method public abstract int getSpanTypeId();
+ }
+
public class Selection {
method public static boolean extendDown(android.text.Spannable, android.text.Layout);
method public static boolean extendLeft(android.text.Spannable, android.text.Layout);
@@ -45245,6 +45266,7 @@
field public static final int KEYCODE_8 = 15; // 0xf
field public static final int KEYCODE_9 = 16; // 0x10
field public static final int KEYCODE_A = 29; // 0x1d
+ field public static final int KEYCODE_ALL_APPS = 284; // 0x11c
field public static final int KEYCODE_ALT_LEFT = 57; // 0x39
field public static final int KEYCODE_ALT_RIGHT = 58; // 0x3a
field public static final int KEYCODE_APOSTROPHE = 75; // 0x4b
@@ -46290,6 +46312,7 @@
method public java.lang.CharSequence getAccessibilityClassName();
method public int getAccessibilityLiveRegion();
method public android.view.accessibility.AccessibilityNodeProvider getAccessibilityNodeProvider();
+ method public java.lang.CharSequence getAccessibilityPaneTitle();
method public int getAccessibilityTraversalAfter();
method public int getAccessibilityTraversalBefore();
method public float getAlpha();
@@ -46613,6 +46636,7 @@
method public void sendAccessibilityEventUnchecked(android.view.accessibility.AccessibilityEvent);
method public void setAccessibilityDelegate(android.view.View.AccessibilityDelegate);
method public void setAccessibilityLiveRegion(int);
+ method public void setAccessibilityPaneTitle(java.lang.CharSequence);
method public void setAccessibilityTraversalAfter(int);
method public void setAccessibilityTraversalBefore(int);
method public void setActivated(boolean);
@@ -48010,6 +48034,7 @@
method public void setPackageName(java.lang.CharSequence);
method public void writeToParcel(android.os.Parcel, int);
field public static final int CONTENT_CHANGE_TYPE_CONTENT_DESCRIPTION = 4; // 0x4
+ field public static final int CONTENT_CHANGE_TYPE_PANE_TITLE = 8; // 0x8
field public static final int CONTENT_CHANGE_TYPE_SUBTREE = 1; // 0x1
field public static final int CONTENT_CHANGE_TYPE_TEXT = 2; // 0x2
field public static final int CONTENT_CHANGE_TYPE_UNDEFINED = 0; // 0x0
@@ -48120,6 +48145,7 @@
method public int getMaxTextLength();
method public int getMovementGranularities();
method public java.lang.CharSequence getPackageName();
+ method public java.lang.CharSequence getPaneTitle();
method public android.view.accessibility.AccessibilityNodeInfo getParent();
method public android.view.accessibility.AccessibilityNodeInfo.RangeInfo getRangeInfo();
method public java.lang.CharSequence getText();
@@ -48197,6 +48223,7 @@
method public void setMovementGranularities(int);
method public void setMultiLine(boolean);
method public void setPackageName(java.lang.CharSequence);
+ method public void setPaneTitle(java.lang.CharSequence);
method public void setParent(android.view.View);
method public void setParent(android.view.View, int);
method public void setPassword(boolean);
@@ -49233,14 +49260,13 @@
method public android.graphics.drawable.Drawable getSecondaryIcon(int);
method public android.content.Intent getSecondaryIntent(int);
method public java.lang.CharSequence getSecondaryLabel(int);
- method public android.view.View.OnClickListener getSecondaryOnClickListener(int);
method public java.lang.String getSignature();
method public java.lang.String getText();
}
public static final class TextClassification.Builder {
ctor public TextClassification.Builder();
- method public android.view.textclassifier.TextClassification.Builder addSecondaryAction(android.content.Intent, java.lang.String, android.graphics.drawable.Drawable, android.view.View.OnClickListener);
+ method public android.view.textclassifier.TextClassification.Builder addSecondaryAction(android.content.Intent, java.lang.String, android.graphics.drawable.Drawable);
method public android.view.textclassifier.TextClassification build();
method public android.view.textclassifier.TextClassification.Builder clearSecondaryActions();
method public android.view.textclassifier.TextClassification.Builder setEntityType(java.lang.String, float);
@@ -49248,15 +49274,18 @@
method public android.view.textclassifier.TextClassification.Builder setIntent(android.content.Intent);
method public android.view.textclassifier.TextClassification.Builder setLabel(java.lang.String);
method public android.view.textclassifier.TextClassification.Builder setOnClickListener(android.view.View.OnClickListener);
- method public android.view.textclassifier.TextClassification.Builder setPrimaryAction(android.content.Intent, java.lang.String, android.graphics.drawable.Drawable, android.view.View.OnClickListener);
+ method public android.view.textclassifier.TextClassification.Builder setPrimaryAction(android.content.Intent, java.lang.String, android.graphics.drawable.Drawable);
method public android.view.textclassifier.TextClassification.Builder setSignature(java.lang.String);
method public android.view.textclassifier.TextClassification.Builder setText(java.lang.String);
}
- public static final class TextClassification.Options {
+ public static final class TextClassification.Options implements android.os.Parcelable {
ctor public TextClassification.Options();
+ method public int describeContents();
method public android.os.LocaleList getDefaultLocales();
method public android.view.textclassifier.TextClassification.Options setDefaultLocales(android.os.LocaleList);
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.view.textclassifier.TextClassification.Options> CREATOR;
}
public final class TextClassificationManager {
@@ -49286,16 +49315,22 @@
field public static final java.lang.String TYPE_URL = "url";
}
- public static final class TextClassifier.EntityConfig {
+ public static final class TextClassifier.EntityConfig implements android.os.Parcelable {
ctor public TextClassifier.EntityConfig(int);
+ method public int describeContents();
method public android.view.textclassifier.TextClassifier.EntityConfig excludeEntities(java.lang.String...);
method public java.util.List<java.lang.String> getEntities(android.view.textclassifier.TextClassifier);
method public android.view.textclassifier.TextClassifier.EntityConfig includeEntities(java.lang.String...);
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.view.textclassifier.TextClassifier.EntityConfig> CREATOR;
}
- public final class TextLinks {
+ 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 android.os.Parcelable.Creator<android.view.textclassifier.TextLinks> CREATOR;
}
public static final class TextLinks.Builder {
@@ -49304,21 +49339,27 @@
method public android.view.textclassifier.TextLinks build();
}
- public static final class TextLinks.Options {
+ public static final class TextLinks.Options implements android.os.Parcelable {
ctor public TextLinks.Options();
+ method public int describeContents();
method public android.os.LocaleList getDefaultLocales();
method public android.view.textclassifier.TextClassifier.EntityConfig getEntityConfig();
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 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 {
+ 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();
method public java.lang.String getEntity(int);
method public int getEntityCount();
method public int getStart();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.view.textclassifier.TextLinks.TextLink> CREATOR;
}
public final class TextSelection {
@@ -49337,10 +49378,13 @@
method public android.view.textclassifier.TextSelection.Builder setSignature(java.lang.String);
}
- public static final class TextSelection.Options {
+ public static final class TextSelection.Options implements android.os.Parcelable {
ctor public TextSelection.Options();
+ method public int describeContents();
method public android.os.LocaleList getDefaultLocales();
method public android.view.textclassifier.TextSelection.Options setDefaultLocales(android.os.LocaleList);
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.view.textclassifier.TextSelection.Options> CREATOR;
}
}
diff --git a/api/removed.txt b/api/removed.txt
index 2aab223..77088e5 100644
--- a/api/removed.txt
+++ b/api/removed.txt
@@ -265,6 +265,7 @@
method public android.graphics.drawable.Drawable getBadgedDrawableForUser(android.graphics.drawable.Drawable, android.os.UserHandle, android.graphics.Rect, int);
method public android.graphics.drawable.Drawable getBadgedIconForUser(android.graphics.drawable.Drawable, android.os.UserHandle);
method public java.lang.CharSequence getBadgedLabelForUser(java.lang.CharSequence, android.os.UserHandle);
+ method public deprecated boolean trySetQuietModeEnabled(boolean, android.os.UserHandle);
}
}
diff --git a/api/system-current.txt b/api/system-current.txt
index 74c457d..002c2bd 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -33,6 +33,7 @@
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";
field public static final java.lang.String BRICK = "android.permission.BRICK";
+ field public static final java.lang.String BRIGHTNESS_SLIDER_USAGE = "android.permission.BRIGHTNESS_SLIDER_USAGE";
field public static final deprecated java.lang.String BROADCAST_NETWORK_PRIVILEGED = "android.permission.BROADCAST_NETWORK_PRIVILEGED";
field public static final java.lang.String CALL_PRIVILEGED = "android.permission.CALL_PRIVILEGED";
field public static final java.lang.String CAMERA_DISABLE_TRANSMIT_LED = "android.permission.CAMERA_DISABLE_TRANSMIT_LED";
@@ -46,6 +47,7 @@
field public static final java.lang.String CHANGE_CONFIGURATION = "android.permission.CHANGE_CONFIGURATION";
field public static final java.lang.String CHANGE_DEVICE_IDLE_TEMP_WHITELIST = "android.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST";
field public static final java.lang.String CLEAR_APP_USER_DATA = "android.permission.CLEAR_APP_USER_DATA";
+ field public static final java.lang.String CONFIGURE_DISPLAY_BRIGHTNESS = "android.permission.CONFIGURE_DISPLAY_BRIGHTNESS";
field public static final java.lang.String CONNECTIVITY_INTERNAL = "android.permission.CONNECTIVITY_INTERNAL";
field public static final java.lang.String CONNECTIVITY_USE_RESTRICTED_NETWORKS = "android.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS";
field public static final java.lang.String CONTROL_INCALL_EXPERIENCE = "android.permission.CONTROL_INCALL_EXPERIENCE";
@@ -153,6 +155,7 @@
field public static final java.lang.String SET_ALWAYS_FINISH = "android.permission.SET_ALWAYS_FINISH";
field public static final java.lang.String SET_ANIMATION_SCALE = "android.permission.SET_ANIMATION_SCALE";
field public static final java.lang.String SET_DEBUG_APP = "android.permission.SET_DEBUG_APP";
+ field public static final java.lang.String SET_HARMFUL_APP_WARNINGS = "android.permission.SET_HARMFUL_APP_WARNINGS";
field public static final java.lang.String SET_MEDIA_KEY_LISTENER = "android.permission.SET_MEDIA_KEY_LISTENER";
field public static final java.lang.String SET_ORIENTATION = "android.permission.SET_ORIENTATION";
field public static final java.lang.String SET_POINTER_SPEED = "android.permission.SET_POINTER_SPEED";
@@ -161,6 +164,7 @@
field public static final java.lang.String SET_TIME = "android.permission.SET_TIME";
field public static final java.lang.String SET_VOLUME_KEY_LONG_PRESS_LISTENER = "android.permission.SET_VOLUME_KEY_LONG_PRESS_LISTENER";
field public static final java.lang.String SET_WALLPAPER_COMPONENT = "android.permission.SET_WALLPAPER_COMPONENT";
+ field public static final java.lang.String SHOW_KEYGUARD_MESSAGE = "android.permission.SHOW_KEYGUARD_MESSAGE";
field public static final java.lang.String SHUTDOWN = "android.permission.SHUTDOWN";
field public static final java.lang.String SIGNAL_PERSISTENT_PROCESSES = "android.permission.SIGNAL_PERSISTENT_PROCESSES";
field public static final java.lang.String STATUS_BAR = "android.permission.STATUS_BAR";
@@ -190,10 +194,6 @@
field public static final int isVrOnly = 16844152; // 0x1010578
field public static final int requiredSystemPropertyName = 16844133; // 0x1010565
field public static final int requiredSystemPropertyValue = 16844134; // 0x1010566
- field public static final int searchKeyphrase = 16843871; // 0x101045f
- field public static final int searchKeyphraseId = 16843870; // 0x101045e
- field public static final int searchKeyphraseRecognitionFlags = 16843942; // 0x10104a6
- field public static final int searchKeyphraseSupportedLocales = 16843872; // 0x1010460
}
public static final class R.raw {
@@ -317,6 +317,7 @@
public class KeyguardManager {
method public android.content.Intent createConfirmFactoryResetCredentialIntent(java.lang.CharSequence, java.lang.CharSequence, java.lang.CharSequence);
+ method public void requestDismissKeyguard(android.app.Activity, java.lang.CharSequence, android.app.KeyguardManager.KeyguardDismissCallback);
}
public class Notification implements android.os.Parcelable {
@@ -688,6 +689,9 @@
method public boolean isEncrypted();
method public boolean removeBond();
method public boolean setPhonebookAccessPermission(int);
+ field public static final int ACCESS_ALLOWED = 1; // 0x1
+ field public static final int ACCESS_REJECTED = 2; // 0x2
+ field public static final int ACCESS_UNKNOWN = 0; // 0x0
}
public final class BluetoothHeadset implements android.bluetooth.BluetoothProfile {
@@ -696,6 +700,11 @@
method public boolean setPriority(android.bluetooth.BluetoothDevice, int);
}
+ public abstract interface BluetoothProfile {
+ field public static final int PRIORITY_OFF = 0; // 0x0
+ field public static final int PRIORITY_ON = 100; // 0x64
+ }
+
}
package android.bluetooth.le {
@@ -900,6 +909,7 @@
method public abstract java.util.List<android.content.IntentFilter> getAllIntentFilters(java.lang.String);
method public android.content.pm.dex.ArtManager getArtManager();
method public abstract java.lang.String getDefaultBrowserPackageNameAsUser(int);
+ method public java.lang.CharSequence getHarmfulAppWarning(java.lang.String);
method public abstract java.util.List<android.content.pm.PackageInfo> getInstalledPackagesAsUser(int, int);
method public abstract android.graphics.drawable.Drawable getInstantAppIcon(java.lang.String);
method public abstract android.content.ComponentName getInstantAppInstallerComponent();
@@ -916,6 +926,7 @@
method public abstract void removeOnPermissionsChangeListener(android.content.pm.PackageManager.OnPermissionsChangedListener);
method public abstract void revokeRuntimePermission(java.lang.String, java.lang.String, android.os.UserHandle);
method public abstract boolean setDefaultBrowserPackageNameAsUser(java.lang.String, int);
+ method public void setHarmfulAppWarning(java.lang.String, java.lang.CharSequence);
method public abstract void setUpdateAvailable(java.lang.String, boolean);
method public abstract boolean updateIntentVerificationStatusAsUser(java.lang.String, int, int);
method public abstract void updatePermissionFlags(java.lang.String, java.lang.String, int, int, android.os.UserHandle);
@@ -1083,8 +1094,38 @@
package android.hardware.display {
+ public final class BrightnessChangeEvent implements android.os.Parcelable {
+ method public int describeContents();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.hardware.display.BrightnessChangeEvent> CREATOR;
+ field public final float batteryLevel;
+ field public final float brightness;
+ field public final int colorTemperature;
+ field public final float lastBrightness;
+ field public final long[] luxTimestamps;
+ field public final float[] luxValues;
+ field public final boolean nightMode;
+ field public final java.lang.String packageName;
+ field public final long timeStamp;
+ }
+
+ public final class BrightnessConfiguration implements android.os.Parcelable {
+ method public int describeContents();
+ method public android.util.Pair<float[], float[]> getCurve();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.hardware.display.BrightnessConfiguration> CREATOR;
+ }
+
+ public static class BrightnessConfiguration.Builder {
+ ctor public BrightnessConfiguration.Builder();
+ method public android.hardware.display.BrightnessConfiguration build();
+ method public android.hardware.display.BrightnessConfiguration.Builder setCurve(float[], float[]);
+ }
+
public final class DisplayManager {
+ method public java.util.List<android.hardware.display.BrightnessChangeEvent> getBrightnessEvents();
method public android.graphics.Point getStableDisplaySize();
+ method public void setBrightnessConfiguration(android.hardware.display.BrightnessConfiguration);
}
}
@@ -1692,6 +1733,39 @@
package android.hardware.radio {
+ public final class ProgramList implements java.lang.AutoCloseable {
+ method public void addOnCompleteListener(java.util.concurrent.Executor, android.hardware.radio.ProgramList.OnCompleteListener);
+ method public void addOnCompleteListener(android.hardware.radio.ProgramList.OnCompleteListener);
+ method public void close();
+ method public android.hardware.radio.RadioManager.ProgramInfo get(android.hardware.radio.ProgramSelector.Identifier);
+ method public void registerListCallback(java.util.concurrent.Executor, android.hardware.radio.ProgramList.ListCallback);
+ method public void registerListCallback(android.hardware.radio.ProgramList.ListCallback);
+ method public void removeOnCompleteListener(android.hardware.radio.ProgramList.OnCompleteListener);
+ method public java.util.List<android.hardware.radio.RadioManager.ProgramInfo> toList();
+ method public void unregisterListCallback(android.hardware.radio.ProgramList.ListCallback);
+ }
+
+ public static final class ProgramList.Filter implements android.os.Parcelable {
+ ctor public ProgramList.Filter(java.util.Set<java.lang.Integer>, java.util.Set<android.hardware.radio.ProgramSelector.Identifier>, boolean, boolean);
+ method public boolean areCategoriesIncluded();
+ method public boolean areModificationsExcluded();
+ method public int describeContents();
+ method public java.util.Set<java.lang.Integer> getIdentifierTypes();
+ method public java.util.Set<android.hardware.radio.ProgramSelector.Identifier> getIdentifiers();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.hardware.radio.ProgramList.Filter> CREATOR;
+ }
+
+ public static abstract class ProgramList.ListCallback {
+ ctor public ProgramList.ListCallback();
+ method public void onItemChanged(android.hardware.radio.ProgramSelector.Identifier);
+ method public void onItemRemoved(android.hardware.radio.ProgramSelector.Identifier);
+ }
+
+ public static abstract interface ProgramList.OnCompleteListener {
+ method public abstract void onComplete();
+ }
+
public final class ProgramSelector implements android.os.Parcelable {
ctor public ProgramSelector(int, android.hardware.radio.ProgramSelector.Identifier, android.hardware.radio.ProgramSelector.Identifier[], long[]);
method public static android.hardware.radio.ProgramSelector createAmFmSelector(int, int);
@@ -1715,6 +1789,7 @@
field public static final int IDENTIFIER_TYPE_DRMO_SERVICE_ID = 9; // 0x9
field public static final int IDENTIFIER_TYPE_HD_STATION_ID_EXT = 3; // 0x3
field public static final int IDENTIFIER_TYPE_HD_SUBCHANNEL = 4; // 0x4
+ field public static final int IDENTIFIER_TYPE_INVALID = 0; // 0x0
field public static final int IDENTIFIER_TYPE_RDS_PI = 2; // 0x2
field public static final int IDENTIFIER_TYPE_SXM_CHANNEL = 13; // 0xd
field public static final int IDENTIFIER_TYPE_SXM_SERVICE_ID = 12; // 0xc
@@ -1726,6 +1801,7 @@
field public static final int PROGRAM_TYPE_DRMO = 6; // 0x6
field public static final int PROGRAM_TYPE_FM = 2; // 0x2
field public static final int PROGRAM_TYPE_FM_HD = 4; // 0x4
+ field public static final int PROGRAM_TYPE_INVALID = 0; // 0x0
field public static final int PROGRAM_TYPE_SXM = 7; // 0x7
field public static final int PROGRAM_TYPE_VENDOR_END = 1999; // 0x7cf
field public static final int PROGRAM_TYPE_VENDOR_START = 1000; // 0x3e8
@@ -1944,10 +2020,11 @@
method public abstract void cancelAnnouncement();
method public abstract void close();
method public abstract int getConfiguration(android.hardware.radio.RadioManager.BandConfig[]);
+ method public android.hardware.radio.ProgramList getDynamicProgramList(android.hardware.radio.ProgramList.Filter);
method public abstract boolean getMute();
method public java.util.Map<java.lang.String, java.lang.String> getParameters(java.util.List<java.lang.String>);
method public abstract int getProgramInformation(android.hardware.radio.RadioManager.ProgramInfo[]);
- method public abstract java.util.List<android.hardware.radio.RadioManager.ProgramInfo> getProgramList(java.util.Map<java.lang.String, java.lang.String>);
+ method public abstract deprecated java.util.List<android.hardware.radio.RadioManager.ProgramInfo> getProgramList(java.util.Map<java.lang.String, java.lang.String>);
method public abstract boolean hasControl();
method public abstract deprecated boolean isAnalogForced();
method public abstract boolean isAntennaConnected();
@@ -4386,6 +4463,8 @@
method public int[] supplyPukReportResult(java.lang.String, java.lang.String);
method public void toggleRadioOnOff();
method public void updateServiceLocation();
+ method public void setSimPowerState(int);
+ method public void setSimPowerStateForSlot(int, int);
field public static final int CARRIER_PRIVILEGE_STATUS_ERROR_LOADING_RULES = -2; // 0xfffffffe
field public static final int CARRIER_PRIVILEGE_STATUS_HAS_ACCESS = 1; // 0x1
field public static final int CARRIER_PRIVILEGE_STATUS_NO_ACCESS = 0; // 0x0
@@ -4409,11 +4488,11 @@
package android.telephony.data {
public final class DataCallResponse implements android.os.Parcelable {
- ctor public DataCallResponse(int, int, int, int, java.lang.String, java.lang.String, java.util.List<android.telephony.data.InterfaceAddress>, java.util.List<java.net.InetAddress>, java.util.List<java.net.InetAddress>, java.util.List<java.lang.String>, int);
+ ctor public DataCallResponse(int, int, int, int, java.lang.String, java.lang.String, java.util.List<android.net.LinkAddress>, java.util.List<java.net.InetAddress>, java.util.List<java.net.InetAddress>, java.util.List<java.lang.String>, int);
ctor public DataCallResponse(android.os.Parcel);
method public int describeContents();
method public int getActive();
- method public java.util.List<android.telephony.data.InterfaceAddress> getAddresses();
+ method public java.util.List<android.net.LinkAddress> getAddresses();
method public int getCallId();
method public java.util.List<java.net.InetAddress> getDnses();
method public java.util.List<java.net.InetAddress> getGateways();
@@ -4456,17 +4535,6 @@
field public static final int TYPE_COMMON = 0; // 0x0
}
- public final class InterfaceAddress implements android.os.Parcelable {
- ctor public InterfaceAddress(java.net.InetAddress, int);
- ctor public InterfaceAddress(java.lang.String, int) throws java.net.UnknownHostException;
- ctor public InterfaceAddress(android.os.Parcel);
- method public int describeContents();
- method public java.net.InetAddress getAddress();
- method public int getNetworkPrefixLength();
- method public void writeToParcel(android.os.Parcel, int);
- field public static final android.os.Parcelable.Creator<android.telephony.data.InterfaceAddress> CREATOR;
- }
-
}
package android.telephony.ims {
diff --git a/api/system-removed.txt b/api/system-removed.txt
index a3bf576..b63703d 100644
--- a/api/system-removed.txt
+++ b/api/system-removed.txt
@@ -126,25 +126,6 @@
field public boolean untrusted;
}
- public class WifiConnectionStatistics implements android.os.Parcelable {
- ctor public WifiConnectionStatistics();
- ctor public WifiConnectionStatistics(android.net.wifi.WifiConnectionStatistics);
- method public int describeContents();
- method public void incrementOrAddUntrusted(java.lang.String, int, int);
- method public void writeToParcel(android.os.Parcel, int);
- field public static final android.os.Parcelable.Creator<android.net.wifi.WifiConnectionStatistics> CREATOR;
- field public int num24GhzConnected;
- field public int num5GhzConnected;
- field public int numAutoJoinAttempt;
- field public int numAutoRoamAttempt;
- field public int numWifiManagerJoinAttempt;
- field public java.util.HashMap<java.lang.String, android.net.wifi.WifiNetworkConnectionStatistics> untrustedNetworkHistory;
- }
-
- public class WifiManager {
- method public android.net.wifi.WifiConnectionStatistics getConnectionStatistics();
- }
-
}
package android.os {
diff --git a/api/test-current.txt b/api/test-current.txt
index b16e700..6941731 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -156,6 +156,7 @@
method public boolean isDeviceManaged();
field public static final java.lang.String ACCOUNT_FEATURE_DEVICE_OR_PROFILE_OWNER_ALLOWED = "android.account.DEVICE_OR_PROFILE_OWNER_ALLOWED";
field public static final java.lang.String ACCOUNT_FEATURE_DEVICE_OR_PROFILE_OWNER_DISALLOWED = "android.account.DEVICE_OR_PROFILE_OWNER_DISALLOWED";
+ field public static final java.lang.String ACTION_DATA_SHARING_RESTRICTION_APPLIED = "android.app.action.DATA_SHARING_RESTRICTION_APPLIED";
field public static final java.lang.String EXTRA_RESTRICTION = "android.app.extra.RESTRICTION";
}
@@ -294,6 +295,43 @@
}
+package android.hardware.display {
+
+ public final class BrightnessChangeEvent implements android.os.Parcelable {
+ method public int describeContents();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.hardware.display.BrightnessChangeEvent> CREATOR;
+ field public final float batteryLevel;
+ field public final float brightness;
+ field public final int colorTemperature;
+ field public final float lastBrightness;
+ field public final long[] luxTimestamps;
+ field public final float[] luxValues;
+ field public final boolean nightMode;
+ field public final java.lang.String packageName;
+ field public final long timeStamp;
+ }
+
+ public final class BrightnessConfiguration implements android.os.Parcelable {
+ method public int describeContents();
+ method public android.util.Pair<float[], float[]> getCurve();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.hardware.display.BrightnessConfiguration> CREATOR;
+ }
+
+ public static class BrightnessConfiguration.Builder {
+ ctor public BrightnessConfiguration.Builder();
+ method public android.hardware.display.BrightnessConfiguration build();
+ method public android.hardware.display.BrightnessConfiguration.Builder setCurve(float[], float[]);
+ }
+
+ public final class DisplayManager {
+ method public java.util.List<android.hardware.display.BrightnessChangeEvent> getBrightnessEvents();
+ method public void setBrightnessConfiguration(android.hardware.display.BrightnessConfiguration);
+ }
+
+}
+
package android.location {
public final class GnssClock implements android.os.Parcelable {
@@ -483,6 +521,7 @@
}
public static final class Settings.Global extends android.provider.Settings.NameValueTable {
+ field public static final java.lang.String LOW_POWER_MODE = "low_power";
field public static final java.lang.String USE_OPEN_WIFI_PACKAGE = "use_open_wifi_package";
}
diff --git a/cmds/statsd/Android.mk b/cmds/statsd/Android.mk
index ffe652f..2ebbba6 100644
--- a/cmds/statsd/Android.mk
+++ b/cmds/statsd/Android.mk
@@ -186,7 +186,8 @@
tests/statsd_test_util.cpp \
tests/e2e/WakelockDuration_e2e_test.cpp \
tests/e2e/MetricConditionLink_e2e_test.cpp \
- tests/e2e/Attribution_e2e_test.cpp
+ tests/e2e/Attribution_e2e_test.cpp \
+ tests/e2e/GaugeMetric_e2e_test.cpp
LOCAL_STATIC_LIBRARIES := \
$(statsd_common_static_libraries) \
diff --git a/cmds/statsd/src/StatsLogProcessor.cpp b/cmds/statsd/src/StatsLogProcessor.cpp
index 1cfec32..9d6d8a1 100644
--- a/cmds/statsd/src/StatsLogProcessor.cpp
+++ b/cmds/statsd/src/StatsLogProcessor.cpp
@@ -301,7 +301,6 @@
}
void StatsLogProcessor::WriteDataToDisk() {
- mkdir(STATS_DATA_DIR, S_IRWXU);
std::lock_guard<std::mutex> lock(mMetricsMutex);
for (auto& pair : mMetricsManagers) {
const ConfigKey& key = pair.first;
diff --git a/cmds/statsd/src/StatsLogProcessor.h b/cmds/statsd/src/StatsLogProcessor.h
index 09e10a1..301e3a5 100644
--- a/cmds/statsd/src/StatsLogProcessor.h
+++ b/cmds/statsd/src/StatsLogProcessor.h
@@ -99,6 +99,7 @@
FRIEND_TEST(WakelockDurationE2eTest, TestAggregatedPredicateDimensions);
FRIEND_TEST(MetricConditionLinkE2eTest, TestMultiplePredicatesAndLinks);
FRIEND_TEST(AttributionE2eTest, TestAttributionMatchAndSlice);
+ FRIEND_TEST(GaugeMetricE2eTest, TestMultipleFieldsForPushedEvent);
};
diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto
index 7f77ef7..4a7f0c4 100644
--- a/cmds/statsd/src/atoms.proto
+++ b/cmds/statsd/src/atoms.proto
@@ -102,7 +102,6 @@
CpuTimePerUidFreq cpu_time_per_uid_freq = 10010;
WifiActivityEnergyInfo wifi_activity_energy_info = 10011;
ModemActivityInfo modem_activity_info = 10012;
- AttributionChainDummyAtom attribution_chain_dummy_atom = 100000;
}
}
@@ -147,11 +146,6 @@
* *****************************************************************************
*/
-message AttributionChainDummyAtom {
- repeated AttributionNode attribution_node = 1;
- optional int32 value = 2;
-}
-
/**
* Logs when the screen state changes.
*
@@ -218,8 +212,7 @@
* frameworks/base/core/java/com/android/internal/os/BatteryStatsImpl.java
*/
message BleScanStateChanged {
- // TODO: Add attribution instead of uid.
- optional int32 uid = 1;
+ repeated AttributionNode attribution_node = 1;
enum State {
OFF = 0;
@@ -236,8 +229,7 @@
*/
// TODO: Consider changing to tracking per-scanner-id (log from AppScanStats).
message BleUnoptimizedScanStateChanged {
- // TODO: Add attribution instead of uid.
- optional int32 uid = 1;
+ repeated AttributionNode attribution_node = 1;
enum State {
OFF = 0;
@@ -254,8 +246,7 @@
*/
// TODO: Consider changing to tracking per-scanner-id (log from AppScanStats).
message BleScanResultReceived {
- // TODO: Add attribution instead of uid.
- optional int32 uid = 1;
+ repeated AttributionNode attribution_node = 1;
// Number of ble scan results returned.
optional int32 num_of_results = 2;
@@ -268,8 +259,7 @@
* frameworks/base/core/java/com/android/internal/os/BatteryStatsImpl.java
*/
message SensorStateChanged {
- // TODO: Add attribution instead of uid.
- optional int32 uid = 1;
+ repeated AttributionNode attribution_node = 1;
// TODO: Is there a way to get the actual name of the sensor?
// The id (int) of the sensor.
@@ -290,8 +280,7 @@
* frameworks/base/core/java/com/android/internal/os/BatteryStatsImpl.java
*/
message GpsScanStateChanged {
- // TODO: Add attribution instead of uid.
- optional int32 uid = 1;
+ repeated AttributionNode attribution_node = 1;
enum State {
OFF = 0;
@@ -308,8 +297,7 @@
* frameworks/base/core/java/com/android/internal/os/BatteryStatsImpl.java
*/
message SyncStateChanged {
- // TODO: Add attribution instead of uid.
- optional int32 uid = 1;
+ repeated AttributionNode attribution_node = 1;
// Name of the sync (as named in the app)
optional string name = 2;
@@ -328,8 +316,7 @@
* frameworks/base/core/java/com/android/internal/os/BatteryStatsImpl.java
*/
message ScheduledJobStateChanged {
- // TODO: Add attribution instead of uid.
- optional int32 uid = 1;
+ repeated AttributionNode attribution_node = 1;
// Name of the job (as named in the app)
optional string name = 2;
@@ -350,8 +337,7 @@
* frameworks/base/core/java/com/android/internal/os/BatteryStatsImpl.java
*/
message AudioStateChanged {
- // TODO: Add attribution instead of uid.
- optional int32 uid = 1;
+ repeated AttributionNode attribution_node = 1;
enum State {
OFF = 0;
@@ -367,8 +353,7 @@
* frameworks/base/core/java/com/android/internal/os/BatteryStatsImpl.java
*/
message MediaCodecActivityChanged {
- // TODO: Add attribution instead of uid.
- optional int32 uid = 1;
+ repeated AttributionNode attribution_node = 1;
enum State {
OFF = 0;
@@ -384,8 +369,7 @@
* frameworks/base/core/java/com/android/internal/os/BatteryStatsImpl.java
*/
message FlashlightStateChanged {
- // TODO: Add attribution instead of uid.
- optional int32 uid = 1;
+ repeated AttributionNode attribution_node = 1;
enum State {
OFF = 0;
@@ -401,8 +385,7 @@
* frameworks/base/core/java/com/android/internal/os/BatteryStatsImpl.java
*/
message CameraStateChanged {
- // TODO: Add attribution instead of uid.
- optional int32 uid = 1;
+ repeated AttributionNode attribution_node = 1;
enum State {
OFF = 0;
@@ -447,8 +430,7 @@
* frameworks/base/core/java/com/android/internal/os/BatteryStatsImpl.java
*/
message LongPartialWakelockStateChanged {
- // TODO: Add attribution instead of uid?
- optional int32 uid = 1;
+ repeated AttributionNode attribution_node = 1;
// The wakelock tag (Called tag in the Java API, sometimes name elsewhere).
optional string tag = 2;
@@ -605,8 +587,7 @@
* frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java
*/
message WakeupAlarmOccurred {
- // TODO: Add attribution instead of uid?
- optional int32 uid = 1;
+ repeated AttributionNode attribution_node = 1;
// Name of the wakeup alarm.
optional string tag = 2;
@@ -673,8 +654,7 @@
* frameworks/base/core/java/com/android/internal/os/BatteryStatsImpl.java
*/
message WifiLockStateChanged {
- // TODO: Add attribution instead of uid.
- optional int32 uid = 1;
+ repeated AttributionNode attribution_node = 1;
enum State {
OFF = 0;
@@ -708,8 +688,7 @@
* frameworks/base/core/java/com/android/internal/os/BatteryStatsImpl.java
*/
message WifiScanStateChanged {
- // TODO: Add attribution instead of uid.
- optional int32 uid = 1;
+ repeated AttributionNode attribution_node = 1;
enum State {
OFF = 0;
@@ -1109,14 +1088,10 @@
/**
* Pulls Cpu time per frequency.
- * Note: this should be pulled for gauge metric only, without condition.
- * The puller keeps internal state of last values. It should not be pulled by
- * different metrics.
- * The pulled data is delta of cpu time from last pull, calculated as
- * following:
- * if current time is larger than last value, take delta between the two.
- * if current time is smaller than last value, there must be a cpu
- * hotplug event, and the current time is taken as delta.
+ * Pulls the time the cpu spend on the frequency index. Frequency index
+ * starts from highest to lowest. The value should be monotonically
+ * increasing since boot. However, if there is a cpu
+ * hotplug event, the value would be reset as well.
*/
message CpuTimePerFreq {
optional uint32 cluster = 1;
diff --git a/cmds/statsd/src/config/ConfigManager.cpp b/cmds/statsd/src/config/ConfigManager.cpp
index 184f69e..554ff8a8 100644
--- a/cmds/statsd/src/config/ConfigManager.cpp
+++ b/cmds/statsd/src/config/ConfigManager.cpp
@@ -181,8 +181,6 @@
}
void ConfigManager::update_saved_configs(const ConfigKey& key, const StatsdConfig& config) {
- mkdir(STATS_SERVICE_DIR, S_IRWXU);
-
// If there is a pre-existing config with same key we should first delete it.
remove_saved_configs(key);
diff --git a/cmds/statsd/src/dimension.h b/cmds/statsd/src/dimension.h
index 845c138..d0f96a2 100644
--- a/cmds/statsd/src/dimension.h
+++ b/cmds/statsd/src/dimension.h
@@ -32,6 +32,10 @@
const DimensionsValue* getSingleLeafValue(const DimensionsValue* value);
DimensionsValue getSingleLeafValue(const DimensionsValue& value);
+// Appends the leaf node to the parent tree.
+void appendLeafNodeToParent(const Field& field, const DimensionsValue& value,
+ DimensionsValue* parentValue);
+
// 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
// "ANY" locations, it will return multiple trees.
diff --git a/cmds/statsd/src/metrics/GaugeMetricProducer.cpp b/cmds/statsd/src/metrics/GaugeMetricProducer.cpp
index 1a4888c..ae47bd8 100644
--- a/cmds/statsd/src/metrics/GaugeMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/GaugeMetricProducer.cpp
@@ -114,6 +114,9 @@
void GaugeMetricProducer::onDumpReportLocked(const uint64_t dumpTimeNs, StatsLogReport* report) {
flushIfNeededLocked(dumpTimeNs);
+ ProtoOutputStream pbOutput;
+ onDumpReportLocked(dumpTimeNs, &pbOutput);
+ parseProtoOutputStream(pbOutput, report);
}
void GaugeMetricProducer::onDumpReportLocked(const uint64_t dumpTimeNs,
diff --git a/cmds/statsd/src/metrics/MetricsManager.h b/cmds/statsd/src/metrics/MetricsManager.h
index 08b0981..a0239fc 100644
--- a/cmds/statsd/src/metrics/MetricsManager.h
+++ b/cmds/statsd/src/metrics/MetricsManager.h
@@ -141,6 +141,7 @@
FRIEND_TEST(WakelockDurationE2eTest, TestAggregatedPredicateDimensions);
FRIEND_TEST(MetricConditionLinkE2eTest, TestMultiplePredicatesAndLinks);
FRIEND_TEST(AttributionE2eTest, TestAttributionMatchAndSlice);
+ FRIEND_TEST(GaugeMetricE2eTest, TestMultipleFieldsForPushedEvent);
};
} // namespace statsd
diff --git a/cmds/statsd/src/stats_log_util.h b/cmds/statsd/src/stats_log_util.h
index 09a43f5..cee9200 100644
--- a/cmds/statsd/src/stats_log_util.h
+++ b/cmds/statsd/src/stats_log_util.h
@@ -43,6 +43,19 @@
// Helper function to write PulledAtomStats to ProtoOutputStream
void writePullerStatsToStream(const std::pair<int, StatsdStats::PulledAtomStats>& pair,
util::ProtoOutputStream* protoOutput);
+
+template<class T>
+bool parseProtoOutputStream(util::ProtoOutputStream& protoOutput, T* message) {
+ std::string pbBytes;
+ auto iter = protoOutput.data();
+ while (iter.readBuffer() != NULL) {
+ size_t toRead = iter.currentToRead();
+ pbBytes.append(reinterpret_cast<const char*>(iter.readBuffer()), toRead);
+ iter.rp()->move(toRead);
+ }
+ return message->ParseFromArray(pbBytes.c_str(), pbBytes.size());
+}
+
} // namespace statsd
} // namespace os
} // namespace android
\ No newline at end of file
diff --git a/cmds/statsd/statsd.rc b/cmds/statsd/statsd.rc
index faccd61..c260ae1 100644
--- a/cmds/statsd/statsd.rc
+++ b/cmds/statsd/statsd.rc
@@ -14,3 +14,10 @@
service statsd /system/bin/statsd
class main
+ user statsd
+ group statsd log
+
+on post-fs-data
+ # Create directory for statsd
+ mkdir /data/misc/stats-data/ 0770 statsd statsd
+ mkdir /data/misc/stats-service/ 0770 statsd statsd
diff --git a/cmds/statsd/tests/e2e/GaugeMetric_e2e_test.cpp b/cmds/statsd/tests/e2e/GaugeMetric_e2e_test.cpp
new file mode 100644
index 0000000..10a6c36
--- /dev/null
+++ b/cmds/statsd/tests/e2e/GaugeMetric_e2e_test.cpp
@@ -0,0 +1,193 @@
+// 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.
+
+#include <gtest/gtest.h>
+
+#include "src/StatsLogProcessor.h"
+#include "src/stats_log_util.h"
+#include "tests/statsd_test_util.h"
+
+#include <vector>
+
+namespace android {
+namespace os {
+namespace statsd {
+
+#ifdef __ANDROID__
+
+namespace {
+
+StatsdConfig CreateStatsdConfigForPushedEvent() {
+ StatsdConfig config;
+ *config.add_atom_matcher() = CreateMoveToBackgroundAtomMatcher();
+ *config.add_atom_matcher() = CreateMoveToForegroundAtomMatcher();
+
+ auto atomMatcher = CreateSimpleAtomMatcher("", android::util::APP_START_CHANGED);
+ *config.add_atom_matcher() = atomMatcher;
+
+ auto isInBackgroundPredicate = CreateIsInBackgroundPredicate();
+ *isInBackgroundPredicate.mutable_simple_predicate()->mutable_dimensions() =
+ CreateDimensions(android::util::ACTIVITY_FOREGROUND_STATE_CHANGED, {1 /* uid field */ });
+ *config.add_predicate() = isInBackgroundPredicate;
+
+ auto gaugeMetric = config.add_gauge_metric();
+ gaugeMetric->set_id(123456);
+ gaugeMetric->set_what(atomMatcher.id());
+ gaugeMetric->set_condition(isInBackgroundPredicate.id());
+ gaugeMetric->mutable_gauge_fields_filter()->set_include_all(false);
+ auto fieldMatcher = gaugeMetric->mutable_gauge_fields_filter()->mutable_fields();
+ fieldMatcher->set_field(android::util::APP_START_CHANGED);
+ fieldMatcher->add_child()->set_field(3); // type (enum)
+ fieldMatcher->add_child()->set_field(4); // activity_name(str)
+ fieldMatcher->add_child()->set_field(7); // activity_start_msec(int64)
+ *gaugeMetric->mutable_dimensions() =
+ CreateDimensions(android::util::APP_START_CHANGED, {1 /* uid field */ });
+ gaugeMetric->set_bucket(ONE_MINUTE);
+
+ auto links = gaugeMetric->add_links();
+ links->set_condition(isInBackgroundPredicate.id());
+ auto dimensionWhat = links->mutable_dimensions_in_what();
+ dimensionWhat->set_field(android::util::APP_START_CHANGED);
+ dimensionWhat->add_child()->set_field(1); // uid field.
+ auto dimensionCondition = links->mutable_dimensions_in_condition();
+ dimensionCondition->set_field(android::util::ACTIVITY_FOREGROUND_STATE_CHANGED);
+ dimensionCondition->add_child()->set_field(1); // uid field.
+ return config;
+}
+
+std::unique_ptr<LogEvent> CreateAppStartChangedEvent(
+ const int uid, const string& pkg_name, AppStartChanged::TransitionType type,
+ const string& activity_name, const string& calling_pkg_name, const bool is_instant_app,
+ int64_t activity_start_msec, uint64_t timestampNs) {
+ auto logEvent = std::make_unique<LogEvent>(
+ android::util::APP_START_CHANGED, timestampNs);
+ logEvent->write(uid);
+ logEvent->write(pkg_name);
+ logEvent->write(type);
+ logEvent->write(activity_name);
+ logEvent->write(calling_pkg_name);
+ logEvent->write(is_instant_app);
+ logEvent->write(activity_start_msec);
+ logEvent->init();
+ return logEvent;
+}
+
+} // namespace
+
+TEST(GaugeMetricE2eTest, TestMultipleFieldsForPushedEvent) {
+ auto config = CreateStatsdConfigForPushedEvent();
+ int64_t bucketStartTimeNs = 10000000000;
+ int64_t bucketSizeNs =
+ TimeUnitToBucketSizeInMillis(config.gauge_metric(0).bucket()) * 1000000;
+
+ ConfigKey cfgKey;
+ auto processor = CreateStatsLogProcessor(bucketStartTimeNs / NS_PER_SEC, config, cfgKey);
+ EXPECT_EQ(processor->mMetricsManagers.size(), 1u);
+ EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid());
+
+ int appUid1 = 123;
+ int appUid2 = 456;
+ std::vector<std::unique_ptr<LogEvent>> events;
+ events.push_back(CreateMoveToBackgroundEvent(appUid1, bucketStartTimeNs + 15));
+ events.push_back(CreateMoveToForegroundEvent(appUid1, bucketStartTimeNs + bucketSizeNs + 250));
+ events.push_back(CreateMoveToBackgroundEvent(appUid1, bucketStartTimeNs + bucketSizeNs + 350));
+ events.push_back(CreateMoveToForegroundEvent(
+ appUid1, bucketStartTimeNs + 2 * bucketSizeNs + 100));
+
+
+ events.push_back(CreateAppStartChangedEvent(
+ appUid1, "app1", AppStartChanged::WARM, "activity_name1", "calling_pkg_name1",
+ true /*is_instant_app*/, 101 /*activity_start_msec*/, bucketStartTimeNs + 10));
+ events.push_back(CreateAppStartChangedEvent(
+ appUid1, "app1", AppStartChanged::HOT, "activity_name2", "calling_pkg_name2",
+ true /*is_instant_app*/, 102 /*activity_start_msec*/, bucketStartTimeNs + 20));
+ events.push_back(CreateAppStartChangedEvent(
+ appUid1, "app1", AppStartChanged::COLD, "activity_name3", "calling_pkg_name3",
+ true /*is_instant_app*/, 103 /*activity_start_msec*/, bucketStartTimeNs + 30));
+ events.push_back(CreateAppStartChangedEvent(
+ appUid1, "app1", AppStartChanged::WARM, "activity_name4", "calling_pkg_name4",
+ true /*is_instant_app*/, 104 /*activity_start_msec*/,
+ bucketStartTimeNs + bucketSizeNs + 30));
+ events.push_back(CreateAppStartChangedEvent(
+ appUid1, "app1", AppStartChanged::COLD, "activity_name5", "calling_pkg_name5",
+ true /*is_instant_app*/, 105 /*activity_start_msec*/,
+ bucketStartTimeNs + 2 * bucketSizeNs));
+ events.push_back(CreateAppStartChangedEvent(
+ appUid1, "app1", AppStartChanged::HOT, "activity_name6", "calling_pkg_name6",
+ false /*is_instant_app*/, 106 /*activity_start_msec*/,
+ bucketStartTimeNs + 2 * bucketSizeNs + 10));
+
+ events.push_back(CreateMoveToBackgroundEvent(appUid2, bucketStartTimeNs + bucketSizeNs + 10));
+ events.push_back(CreateAppStartChangedEvent(
+ appUid2, "app2", AppStartChanged::COLD, "activity_name7", "calling_pkg_name7",
+ true /*is_instant_app*/, 201 /*activity_start_msec*/,
+ bucketStartTimeNs + 2 * bucketSizeNs + 10));
+
+ sortLogEventsByTimestamp(&events);
+
+ for (const auto& event : events) {
+ processor->OnLogEvent(event.get());
+ }
+ ConfigMetricsReportList reports;
+ processor->onDumpReport(cfgKey, bucketStartTimeNs + 3 * bucketSizeNs, &reports);
+ EXPECT_EQ(reports.reports_size(), 1);
+ EXPECT_EQ(reports.reports(0).metrics_size(), 1);
+ StatsLogReport::GaugeMetricDataWrapper gaugeMetrics;
+ sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).gauge_metrics(), &gaugeMetrics);
+ EXPECT_EQ(gaugeMetrics.data_size(), 2);
+
+ auto data = gaugeMetrics.data(0);
+ EXPECT_EQ(data.dimension().field(), android::util::APP_START_CHANGED);
+ EXPECT_EQ(data.dimension().value_tuple().dimensions_value_size(), 1);
+ EXPECT_EQ(data.dimension().value_tuple().dimensions_value(0).field(), 1 /* uid field */);
+ EXPECT_EQ(data.dimension().value_tuple().dimensions_value(0).value_int(), appUid1);
+ EXPECT_EQ(data.bucket_info_size(), 3);
+ 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(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(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);
+
+ data = gaugeMetrics.data(1);
+ EXPECT_EQ(data.dimension().field(), android::util::APP_START_CHANGED);
+ EXPECT_EQ(data.dimension().value_tuple().dimensions_value_size(), 1);
+ EXPECT_EQ(data.dimension().value_tuple().dimensions_value(0).field(), 1 /* uid field */);
+ EXPECT_EQ(data.dimension().value_tuple().dimensions_value(0).value_int(), appUid2);
+ EXPECT_EQ(data.bucket_info_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);
+}
+
+#else
+GTEST_LOG_(INFO) << "This test does nothing.\n";
+#endif
+
+} // namespace statsd
+} // namespace os
+} // namespace android
\ No newline at end of file
diff --git a/cmds/statsd/tests/e2e/WakelockDuration_e2e_test.cpp b/cmds/statsd/tests/e2e/WakelockDuration_e2e_test.cpp
index 2783356..fcdaafc 100644
--- a/cmds/statsd/tests/e2e/WakelockDuration_e2e_test.cpp
+++ b/cmds/statsd/tests/e2e/WakelockDuration_e2e_test.cpp
@@ -26,6 +26,8 @@
#ifdef __ANDROID__
+namespace {
+
StatsdConfig CreateStatsdConfig(DurationMetric::AggregationType aggregationType) {
StatsdConfig config;
*config.add_atom_matcher() = CreateScreenTurnedOnAtomMatcher();
@@ -56,6 +58,8 @@
return config;
}
+} // namespace
+
TEST(WakelockDurationE2eTest, TestAggregatedPredicateDimensions) {
ConfigKey cfgKey;
for (auto aggregationType : { DurationMetric::SUM, DurationMetric::MAX_SPARSE }) {
@@ -94,6 +98,7 @@
auto releaseEvent2 = CreateReleaseWakelockEvent(
attributions2, "wl2", bucketStartTimeNs + 2 * bucketSizeNs - 15);
+
std::vector<std::unique_ptr<LogEvent>> events;
events.push_back(std::move(screenTurnedOnEvent));
@@ -119,18 +124,8 @@
auto data = reports.reports(0).metrics(0).duration_metrics().data(0);
// Validate dimension value.
- EXPECT_EQ(data.dimension().field(),
- android::util::WAKELOCK_STATE_CHANGED);
- EXPECT_EQ(data.dimension().value_tuple().dimensions_value_size(), 1);
- // Attribution field.
- EXPECT_EQ(data.dimension().value_tuple().dimensions_value(0).field(), 1);
- // Uid only.
- EXPECT_EQ(data.dimension().value_tuple().dimensions_value(0)
- .value_tuple().dimensions_value_size(), 1);
- EXPECT_EQ(data.dimension().value_tuple().dimensions_value(0)
- .value_tuple().dimensions_value(0).field(), 1);
- EXPECT_EQ(data.dimension().value_tuple().dimensions_value(0)
- .value_tuple().dimensions_value(0).value_int(), 111);
+ ValidateAttributionUidDimension(
+ data.dimension(), android::util::WAKELOCK_STATE_CHANGED, 111);
// Validate bucket info.
EXPECT_EQ(reports.reports(0).metrics(0).duration_metrics().data(0).bucket_info_size(), 1);
data = reports.reports(0).metrics(0).duration_metrics().data(0);
@@ -147,18 +142,8 @@
EXPECT_EQ(reports.reports(0).metrics(0).duration_metrics().data(0).bucket_info_size(), 2);
data = reports.reports(0).metrics(0).duration_metrics().data(0);
// Validate dimension value.
- EXPECT_EQ(data.dimension().field(),
- android::util::WAKELOCK_STATE_CHANGED);
- EXPECT_EQ(data.dimension().value_tuple().dimensions_value_size(), 1);
- // Attribution field.
- EXPECT_EQ(data.dimension().value_tuple().dimensions_value(0).field(), 1);
- // Uid only.
- EXPECT_EQ(data.dimension().value_tuple().dimensions_value(0)
- .value_tuple().dimensions_value_size(), 1);
- EXPECT_EQ(data.dimension().value_tuple().dimensions_value(0)
- .value_tuple().dimensions_value(0).field(), 1);
- EXPECT_EQ(data.dimension().value_tuple().dimensions_value(0)
- .value_tuple().dimensions_value(0).value_int(), 111);
+ ValidateAttributionUidDimension(
+ data.dimension(), android::util::WAKELOCK_STATE_CHANGED, 111);
// Two output buckets.
// The wakelock holding interval in the 1st bucket starts from the screen off event and to
// the end of the 1st bucket.
@@ -167,6 +152,32 @@
// The wakelock holding interval in the 2nd bucket starts at the beginning of the bucket and
// ends at the second screen on event.
EXPECT_EQ((unsigned long long)data.bucket_info(1).duration_nanos(), 500UL);
+
+ events.clear();
+ events.push_back(CreateScreenStateChangedEvent(
+ ScreenStateChanged::STATE_OFF, bucketStartTimeNs + 2 * bucketSizeNs + 90));
+ events.push_back(CreateAcquireWakelockEvent(
+ attributions1, "wl3", bucketStartTimeNs + 2 * bucketSizeNs + 100));
+ events.push_back(CreateReleaseWakelockEvent(
+ attributions1, "wl3", bucketStartTimeNs + 5 * bucketSizeNs + 100));
+ sortLogEventsByTimestamp(&events);
+ for (const auto& event : events) {
+ processor->OnLogEvent(event.get());
+ }
+ reports.Clear();
+ processor->onDumpReport(cfgKey, bucketStartTimeNs + 6 * bucketSizeNs + 1, &reports);
+ EXPECT_EQ(reports.reports_size(), 1);
+ EXPECT_EQ(reports.reports(0).metrics_size(), 1);
+ EXPECT_EQ(reports.reports(0).metrics(0).duration_metrics().data_size(), 1);
+ EXPECT_EQ(reports.reports(0).metrics(0).duration_metrics().data(0).bucket_info_size(), 6);
+ data = reports.reports(0).metrics(0).duration_metrics().data(0);
+ ValidateAttributionUidDimension(
+ data.dimension(), android::util::WAKELOCK_STATE_CHANGED, 111);
+ // The last wakelock holding spans 4 buckets.
+ EXPECT_EQ((unsigned long long)data.bucket_info(2).duration_nanos(), bucketSizeNs - 100);
+ EXPECT_EQ((unsigned long long)data.bucket_info(3).duration_nanos(), bucketSizeNs);
+ EXPECT_EQ((unsigned long long)data.bucket_info(4).duration_nanos(), bucketSizeNs);
+ EXPECT_EQ((unsigned long long)data.bucket_info(5).duration_nanos(), 100UL);
}
}
diff --git a/cmds/statsd/tests/statsd_test_util.cpp b/cmds/statsd/tests/statsd_test_util.cpp
index e788235..718b2e1 100644
--- a/cmds/statsd/tests/statsd_test_util.cpp
+++ b/cmds/statsd/tests/statsd_test_util.cpp
@@ -19,6 +19,14 @@
namespace os {
namespace statsd {
+AtomMatcher CreateSimpleAtomMatcher(const string& name, int atomId) {
+ AtomMatcher atom_matcher;
+ atom_matcher.set_id(StringToId(name));
+ auto simple_atom_matcher = atom_matcher.mutable_simple_atom_matcher();
+ simple_atom_matcher->set_atom_id(atomId);
+ return atom_matcher;
+}
+
AtomMatcher CreateWakelockStateChangedAtomMatcher(const string& name,
WakelockStateChanged::State state) {
AtomMatcher atom_matcher;
diff --git a/cmds/statsd/tests/statsd_test_util.h b/cmds/statsd/tests/statsd_test_util.h
index 1bbbd9a..7eb93b9 100644
--- a/cmds/statsd/tests/statsd_test_util.h
+++ b/cmds/statsd/tests/statsd_test_util.h
@@ -23,6 +23,9 @@
namespace os {
namespace statsd {
+// Create AtomMatcher proto to simply match a specific atom type.
+AtomMatcher CreateSimpleAtomMatcher(const string& name, int atomId);
+
// Create AtomMatcher proto for acquiring wakelock.
AtomMatcher CreateAcquireWakelockAtomMatcher();
diff --git a/core/java/Android.bp b/core/java/Android.bp
index b43cf27..afa08e6 100644
--- a/core/java/Android.bp
+++ b/core/java/Android.bp
@@ -3,6 +3,11 @@
srcs: ["android/security/keymaster/IKeyAttestationApplicationIdProvider.aidl"],
}
+filegroup {
+ name: "IDropBoxManagerService.aidl",
+ srcs: ["com/android/internal/os/IDropBoxManagerService.aidl"],
+}
+
// only used by key_store_service
cc_library_shared {
name: "libkeystore_aidl",
diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java
index 60a5a11..972ffcb 100644
--- a/core/java/android/app/ActivityManagerInternal.java
+++ b/core/java/android/app/ActivityManagerInternal.java
@@ -319,4 +319,9 @@
}
public abstract void registerScreenObserver(ScreenObserver observer);
+
+ /**
+ * Returns if more users can be started without stopping currently running users.
+ */
+ public abstract boolean canStartMoreUsers();
}
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index de346f3..e610ac4 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -1768,7 +1768,14 @@
(String[]) ((SomeArgs) msg.obj).arg2);
break;
case EXECUTE_TRANSACTION:
- mTransactionExecutor.execute(((ClientTransaction) msg.obj));
+ final ClientTransaction transaction = (ClientTransaction) msg.obj;
+ mTransactionExecutor.execute(transaction);
+ if (isSystem()) {
+ // Client transactions inside system process are recycled on the client side
+ // instead of ClientLifecycleManager to avoid being cleared before this
+ // message is handled.
+ transaction.recycle();
+ }
break;
}
Object obj = msg.obj;
diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java
index 82f2bac..4048e65 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -2738,6 +2738,24 @@
}
@Override
+ public CharSequence getHarmfulAppWarning(String packageName) {
+ try {
+ return mPM.getHarmfulAppWarning(packageName, mContext.getUserId());
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ }
+ }
+
+ @Override
+ public void setHarmfulAppWarning(String packageName, CharSequence warning) {
+ try {
+ mPM.setHarmfulAppWarning(packageName, warning, mContext.getUserId());
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ }
+ }
+
+ @Override
public ArtManager getArtManager() {
synchronized (mLock) {
if (mArtManager == null) {
diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl
index a9e633f..696899f 100644
--- a/core/java/android/app/IActivityManager.aidl
+++ b/core/java/android/app/IActivityManager.aidl
@@ -620,8 +620,9 @@
boolean updateDisplayOverrideConfiguration(in Configuration values, int displayId);
void moveStackToDisplay(int stackId, int displayId);
boolean requestAutofillData(in IAssistDataReceiver receiver, in Bundle receiverExtras,
- in IBinder activityToken, int flags);
- void dismissKeyguard(in IBinder token, in IKeyguardDismissCallback callback);
+ in IBinder activityToken, int flags);
+ void dismissKeyguard(in IBinder token, in IKeyguardDismissCallback callback,
+ in CharSequence message);
int restartUserInBackground(int userId);
/** Cancels the window transitions for the given task. */
diff --git a/core/java/android/app/KeyguardManager.java b/core/java/android/app/KeyguardManager.java
index af536f6..024dbcb 100644
--- a/core/java/android/app/KeyguardManager.java
+++ b/core/java/android/app/KeyguardManager.java
@@ -477,6 +477,39 @@
*/
public void requestDismissKeyguard(@NonNull Activity activity,
@Nullable KeyguardDismissCallback callback) {
+ requestDismissKeyguard(activity, null /* message */, callback);
+ }
+
+ /**
+ * If the device is currently locked (see {@link #isKeyguardLocked()}, requests the Keyguard to
+ * be dismissed.
+ * <p>
+ * If the Keyguard is not secure or the device is currently in a trusted state, calling this
+ * method will immediately dismiss the Keyguard without any user interaction.
+ * <p>
+ * If the Keyguard is secure and the device is not in a trusted state, this will bring up the
+ * UI so the user can enter their credentials.
+ * <p>
+ * If the value set for the {@link Activity} attr {@link android.R.attr#turnScreenOn} is true,
+ * the screen will turn on when the keyguard is dismissed.
+ *
+ * @param activity The activity requesting the dismissal. The activity must be either visible
+ * by using {@link LayoutParams#FLAG_SHOW_WHEN_LOCKED} or must be in a state in
+ * which it would be visible if Keyguard would not be hiding it. If that's not
+ * the case, the request will fail immediately and
+ * {@link KeyguardDismissCallback#onDismissError} will be invoked.
+ * @param message A message that will be shown in the keyguard explaining why the user
+ * would want to dismiss it.
+ * @param callback The callback to be called if the request to dismiss Keyguard was successful
+ * or {@code null} if the caller isn't interested in knowing the result. The
+ * callback will not be invoked if the activity was destroyed before the
+ * callback was received.
+ * @hide
+ */
+ @RequiresPermission(Manifest.permission.SHOW_KEYGUARD_MESSAGE)
+ @SystemApi
+ public void requestDismissKeyguard(@NonNull Activity activity, @Nullable CharSequence message,
+ @Nullable KeyguardDismissCallback callback) {
try {
mAm.dismissKeyguard(activity.getActivityToken(), new IKeyguardDismissCallback.Stub() {
@Override
@@ -499,7 +532,7 @@
activity.mHandler.post(callback::onDismissCancelled);
}
}
- });
+ }, message);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index a383604..75dc571 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -1268,10 +1268,67 @@
*/
private static final String EXTRA_DATA_ONLY_INPUTS = "android.extra.DATA_ONLY_INPUTS";
+ /**
+ * {@link }: No semantic action defined.
+ */
+ public static final int SEMANTIC_ACTION_NONE = 0;
+
+ /**
+ * {@code SemanticAction}: Reply to a conversation, chat, group, or wherever replies
+ * may be appropriate.
+ */
+ public static final int SEMANTIC_ACTION_REPLY = 1;
+
+ /**
+ * {@code SemanticAction}: Mark content as read.
+ */
+ public static final int SEMANTIC_ACTION_MARK_AS_READ = 2;
+
+ /**
+ * {@code SemanticAction}: Mark content as unread.
+ */
+ public static final int SEMANTIC_ACTION_MARK_AS_UNREAD = 3;
+
+ /**
+ * {@code SemanticAction}: Delete the content associated with the notification. This
+ * could mean deleting an email, message, etc.
+ */
+ public static final int SEMANTIC_ACTION_DELETE = 4;
+
+ /**
+ * {@code SemanticAction}: Archive the content associated with the notification. This
+ * could mean archiving an email, message, etc.
+ */
+ public static final int SEMANTIC_ACTION_ARCHIVE = 5;
+
+ /**
+ * {@code SemanticAction}: Mute the content associated with the notification. This could
+ * mean silencing a conversation or currently playing media.
+ */
+ public static final int SEMANTIC_ACTION_MUTE = 6;
+
+ /**
+ * {@code SemanticAction}: Unmute the content associated with the notification. This could
+ * mean un-silencing a conversation or currently playing media.
+ */
+ public static final int SEMANTIC_ACTION_UNMUTE = 7;
+
+ /**
+ * {@code SemanticAction}: Mark content with a thumbs up.
+ */
+ public static final int SEMANTIC_ACTION_THUMBS_UP = 8;
+
+ /**
+ * {@code SemanticAction}: Mark content with a thumbs down.
+ */
+ public static final int SEMANTIC_ACTION_THUMBS_DOWN = 9;
+
+
private final Bundle mExtras;
private Icon mIcon;
private final RemoteInput[] mRemoteInputs;
private boolean mAllowGeneratedReplies = true;
+ private final @SemanticAction int mSemanticAction;
/**
* Small icon representing the action.
@@ -1306,6 +1363,7 @@
mExtras = Bundle.setDefusable(in.readBundle(), true);
mRemoteInputs = in.createTypedArray(RemoteInput.CREATOR);
mAllowGeneratedReplies = in.readInt() == 1;
+ mSemanticAction = in.readInt();
}
/**
@@ -1313,12 +1371,14 @@
*/
@Deprecated
public Action(int icon, CharSequence title, PendingIntent intent) {
- this(Icon.createWithResource("", icon), title, intent, new Bundle(), null, true);
+ this(Icon.createWithResource("", icon), title, intent, new Bundle(), null, true,
+ SEMANTIC_ACTION_NONE);
}
/** Keep in sync with {@link Notification.Action.Builder#Builder(Action)}! */
private Action(Icon icon, CharSequence title, PendingIntent intent, Bundle extras,
- RemoteInput[] remoteInputs, boolean allowGeneratedReplies) {
+ RemoteInput[] remoteInputs, boolean allowGeneratedReplies,
+ @SemanticAction int semanticAction) {
this.mIcon = icon;
if (icon != null && icon.getType() == Icon.TYPE_RESOURCE) {
this.icon = icon.getResId();
@@ -1328,6 +1388,7 @@
this.mExtras = extras != null ? extras : new Bundle();
this.mRemoteInputs = remoteInputs;
this.mAllowGeneratedReplies = allowGeneratedReplies;
+ this.mSemanticAction = semanticAction;
}
/**
@@ -1366,6 +1427,15 @@
}
/**
+ * Returns the {@code SemanticAction} associated with this {@link Action}. A
+ * {@code SemanticAction} denotes what an {@link Action}'s {@link PendingIntent} will do
+ * (eg. reply, mark as read, delete, etc).
+ */
+ public @SemanticAction int getSemanticAction() {
+ return mSemanticAction;
+ }
+
+ /**
* Get the list of inputs to be collected from the user that ONLY accept data when this
* action is sent. These remote inputs are guaranteed to return true on a call to
* {@link RemoteInput#isDataOnly}.
@@ -1389,6 +1459,7 @@
private boolean mAllowGeneratedReplies = true;
private final Bundle mExtras;
private ArrayList<RemoteInput> mRemoteInputs;
+ private @SemanticAction int mSemanticAction;
/**
* Construct a new builder for {@link Action} object.
@@ -1408,7 +1479,7 @@
* @param intent the {@link PendingIntent} to fire when users trigger this action
*/
public Builder(Icon icon, CharSequence title, PendingIntent intent) {
- this(icon, title, intent, new Bundle(), null, true);
+ this(icon, title, intent, new Bundle(), null, true, SEMANTIC_ACTION_NONE);
}
/**
@@ -1419,11 +1490,12 @@
public Builder(Action action) {
this(action.getIcon(), action.title, action.actionIntent,
new Bundle(action.mExtras), action.getRemoteInputs(),
- action.getAllowGeneratedReplies());
+ action.getAllowGeneratedReplies(), action.getSemanticAction());
}
private Builder(Icon icon, CharSequence title, PendingIntent intent, Bundle extras,
- RemoteInput[] remoteInputs, boolean allowGeneratedReplies) {
+ RemoteInput[] remoteInputs, boolean allowGeneratedReplies,
+ @SemanticAction int semanticAction) {
mIcon = icon;
mTitle = title;
mIntent = intent;
@@ -1433,6 +1505,7 @@
Collections.addAll(mRemoteInputs, remoteInputs);
}
mAllowGeneratedReplies = allowGeneratedReplies;
+ mSemanticAction = semanticAction;
}
/**
@@ -1488,6 +1561,19 @@
}
/**
+ * Sets the {@code SemanticAction} for this {@link Action}. A
+ * {@code SemanticAction} denotes what an {@link Action}'s
+ * {@link PendingIntent} will do (eg. reply, mark as read, delete, etc).
+ * @param semanticAction a SemanticAction defined within {@link Action} with
+ * {@code SEMANTIC_ACTION_} prefixes
+ * @return this object for method chaining
+ */
+ public Builder setSemanticAction(@SemanticAction int semanticAction) {
+ mSemanticAction = semanticAction;
+ return this;
+ }
+
+ /**
* Apply an extender to this action builder. Extenders may be used to add
* metadata or change options on this builder.
*/
@@ -1528,7 +1614,7 @@
RemoteInput[] textInputsArr = textInputs.isEmpty()
? null : textInputs.toArray(new RemoteInput[textInputs.size()]);
return new Action(mIcon, mTitle, mIntent, mExtras, textInputsArr,
- mAllowGeneratedReplies);
+ mAllowGeneratedReplies, mSemanticAction);
}
}
@@ -1540,12 +1626,15 @@
actionIntent, // safe to alias
mExtras == null ? new Bundle() : new Bundle(mExtras),
getRemoteInputs(),
- getAllowGeneratedReplies());
+ getAllowGeneratedReplies(),
+ getSemanticAction());
}
+
@Override
public int describeContents() {
return 0;
}
+
@Override
public void writeToParcel(Parcel out, int flags) {
final Icon ic = getIcon();
@@ -1565,7 +1654,9 @@
out.writeBundle(mExtras);
out.writeTypedArray(mRemoteInputs, flags);
out.writeInt(mAllowGeneratedReplies ? 1 : 0);
+ out.writeInt(mSemanticAction);
}
+
public static final Parcelable.Creator<Action> CREATOR =
new Parcelable.Creator<Action>() {
public Action createFromParcel(Parcel in) {
@@ -1827,6 +1918,29 @@
return (mFlags & FLAG_HINT_DISPLAY_INLINE) != 0;
}
}
+
+ /**
+ * Provides meaning to an {@link Action} that hints at what the associated
+ * {@link PendingIntent} will do. For example, an {@link Action} with a
+ * {@link PendingIntent} that replies to a text message notification may have the
+ * {@link #SEMANTIC_ACTION_REPLY} {@code SemanticAction} set within it.
+ *
+ * @hide
+ */
+ @IntDef(prefix = { "SEMANTIC_ACTION_" }, value = {
+ SEMANTIC_ACTION_NONE,
+ SEMANTIC_ACTION_REPLY,
+ SEMANTIC_ACTION_MARK_AS_READ,
+ SEMANTIC_ACTION_MARK_AS_UNREAD,
+ SEMANTIC_ACTION_DELETE,
+ SEMANTIC_ACTION_ARCHIVE,
+ SEMANTIC_ACTION_MUTE,
+ SEMANTIC_ACTION_UNMUTE,
+ SEMANTIC_ACTION_THUMBS_UP,
+ SEMANTIC_ACTION_THUMBS_DOWN
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface SemanticAction {}
}
/**
diff --git a/core/java/android/app/SharedPreferencesImpl.java b/core/java/android/app/SharedPreferencesImpl.java
index 8c47598..6ac15a5 100644
--- a/core/java/android/app/SharedPreferencesImpl.java
+++ b/core/java/android/app/SharedPreferencesImpl.java
@@ -71,6 +71,8 @@
@GuardedBy("mLock")
private Map<String, Object> mMap;
+ @GuardedBy("mLock")
+ private Throwable mThrowable;
@GuardedBy("mLock")
private int mDiskWritesInFlight = 0;
@@ -107,6 +109,7 @@
mMode = mode;
mLoaded = false;
mMap = null;
+ mThrowable = null;
startLoadFromDisk();
}
@@ -139,13 +142,14 @@
Map<String, Object> map = null;
StructStat stat = null;
+ Throwable thrown = null;
try {
stat = Os.stat(mFile.getPath());
if (mFile.canRead()) {
BufferedInputStream str = null;
try {
str = new BufferedInputStream(
- new FileInputStream(mFile), 16*1024);
+ new FileInputStream(mFile), 16 * 1024);
map = (Map<String, Object>) XmlUtils.readMapXml(str);
} catch (Exception e) {
Log.w(TAG, "Cannot read " + mFile.getAbsolutePath(), e);
@@ -154,19 +158,36 @@
}
}
} catch (ErrnoException e) {
- /* ignore */
+ // An errno exception means the stat failed. Treat as empty/non-existing by
+ // ignoring.
+ } catch (Throwable t) {
+ thrown = t;
}
synchronized (mLock) {
mLoaded = true;
- if (map != null) {
- mMap = map;
- mStatTimestamp = stat.st_mtim;
- mStatSize = stat.st_size;
- } else {
- mMap = new HashMap<>();
+ mThrowable = thrown;
+
+ // It's important that we always signal waiters, even if we'll make
+ // them fail with an exception. The try-finally is pretty wide, but
+ // better safe than sorry.
+ try {
+ if (thrown == null) {
+ if (map != null) {
+ mMap = map;
+ mStatTimestamp = stat.st_mtim;
+ mStatSize = stat.st_size;
+ } else {
+ mMap = new HashMap<>();
+ }
+ }
+ // In case of a thrown exception, we retain the old map. That allows
+ // any open editors to commit and store updates.
+ } catch (Throwable t) {
+ mThrowable = t;
+ } finally {
+ mLock.notifyAll();
}
- mLock.notifyAll();
}
}
@@ -226,6 +247,7 @@
}
}
+ @GuardedBy("mLock")
private void awaitLoadedLocked() {
if (!mLoaded) {
// Raise an explicit StrictMode onReadFromDisk for this
@@ -239,6 +261,9 @@
} catch (InterruptedException unused) {
}
}
+ if (mThrowable != null) {
+ throw new IllegalStateException(mThrowable);
+ }
}
@Override
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index 6eb4783..6eafcc4 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -136,6 +136,7 @@
import android.telephony.CarrierConfigManager;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
+import android.telephony.euicc.EuiccCardManager;
import android.telephony.euicc.EuiccManager;
import android.util.Log;
import android.util.StatsManager;
@@ -519,6 +520,13 @@
return new EuiccManager(ctx.getOuterContext());
}});
+ registerService(Context.EUICC_CARD_SERVICE, EuiccCardManager.class,
+ new CachedServiceFetcher<EuiccCardManager>() {
+ @Override
+ public EuiccCardManager createService(ContextImpl ctx) {
+ return new EuiccCardManager(ctx.getOuterContext());
+ }});
+
registerService(Context.UI_MODE_SERVICE, UiModeManager.class,
new CachedServiceFetcher<UiModeManager>() {
@Override
diff --git a/core/java/android/app/UiAutomation.java b/core/java/android/app/UiAutomation.java
index 8f01685..ba39740 100644
--- a/core/java/android/app/UiAutomation.java
+++ b/core/java/android/app/UiAutomation.java
@@ -24,7 +24,6 @@
import android.annotation.NonNull;
import android.annotation.TestApi;
import android.graphics.Bitmap;
-import android.graphics.Canvas;
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.Region;
@@ -47,10 +46,14 @@
import android.view.accessibility.AccessibilityNodeInfo;
import android.view.accessibility.AccessibilityWindowInfo;
import android.view.accessibility.IAccessibilityInteractionConnection;
+
+import com.android.internal.util.CollectionUtils;
+
import libcore.io.IoUtils;
import java.io.IOException;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.List;
import java.util.concurrent.TimeoutException;
@@ -580,6 +583,8 @@
// Execute the command *without* the lock being held.
command.run();
+ List<AccessibilityEvent> eventsReceived = Collections.emptyList();
+
// Acquire the lock and wait for the event.
try {
// Wait for the event.
@@ -600,14 +605,14 @@
if (filter.accept(event)) {
return event;
}
- event.recycle();
+ eventsReceived = CollectionUtils.add(eventsReceived, event);
}
// Check if timed out and if not wait.
final long elapsedTimeMillis = SystemClock.uptimeMillis() - startTimeMillis;
final long remainingTimeMillis = timeoutMillis - elapsedTimeMillis;
if (remainingTimeMillis <= 0) {
throw new TimeoutException("Expected event not received within: "
- + timeoutMillis + " ms.");
+ + timeoutMillis + " ms, among " + eventsReceived);
}
synchronized (mLock) {
if (mEventQueue.isEmpty()) {
@@ -620,6 +625,10 @@
}
}
} finally {
+ for (int i = 0; i < CollectionUtils.size(eventsReceived); i++) {
+ AccessibilityEvent event = eventsReceived.get(i);
+ event.recycle();
+ }
synchronized (mLock) {
mWaitingForEventDelivery = false;
mEventQueue.clear();
diff --git a/core/java/android/app/admin/ConnectEvent.java b/core/java/android/app/admin/ConnectEvent.java
index f06a925..d511c57 100644
--- a/core/java/android/app/admin/ConnectEvent.java
+++ b/core/java/android/app/admin/ConnectEvent.java
@@ -68,7 +68,7 @@
@Override
public String toString() {
- return String.format("ConnectEvent(%s, %d, %d, %s)", mIpAddress, mPort, mTimestamp,
+ return String.format("ConnectEvent(%d, %s, %d, %d, %s)", mId, mIpAddress, mPort, mTimestamp,
mPackageName);
}
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index ab85fdc..e334aab 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -1254,6 +1254,26 @@
= "android.app.action.SYSTEM_UPDATE_POLICY_CHANGED";
/**
+ * Broadcast action to notify ManagedProvisioning that
+ * {@link UserManager#DISALLOW_SHARE_INTO_MANAGED_PROFILE} restriction has changed.
+ * @hide
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_DATA_SHARING_RESTRICTION_CHANGED =
+ "android.app.action.DATA_SHARING_RESTRICTION_CHANGED";
+
+ /**
+ * Broadcast action from ManagedProvisioning to notify that the latest change to
+ * {@link UserManager#DISALLOW_SHARE_INTO_MANAGED_PROFILE} restriction has been successfully
+ * applied (cross profile intent filters updated). Only usesd for CTS tests.
+ * @hide
+ */
+ @TestApi
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_DATA_SHARING_RESTRICTION_APPLIED =
+ "android.app.action.DATA_SHARING_RESTRICTION_APPLIED";
+
+ /**
* Permission policy to prompt user for new permission requests for runtime permissions.
* Already granted or denied permissions are not affected by this.
*/
@@ -6057,6 +6077,13 @@
* Called by a profile owner of a managed profile to remove the cross-profile intent filters
* that go from the managed profile to the parent, or from the parent to the managed profile.
* Only removes those that have been set by the profile owner.
+ * <p>
+ * <em>Note</em>: A list of default cross profile intent filters are set up by the system when
+ * the profile is created, some of them ensure the proper functioning of the profile, while
+ * others enable sharing of data from the parent to the managed profile for user convenience.
+ * These default intent filters are not cleared when this API is called. If the default cross
+ * profile data sharing is not desired, they can be disabled with
+ * {@link UserManager#DISALLOW_SHARE_INTO_MANAGED_PROFILE}.
*
* @param admin Which {@link DeviceAdminReceiver} this request is associated with.
* @throws SecurityException if {@code admin} is not a device or profile owner.
@@ -6479,12 +6506,6 @@
public static final int MAKE_USER_DEMO = 0x0004;
/**
- * Flag used by {@link #createAndManageUser} to specify that the newly created user should be
- * started in the background as part of the user creation.
- */
- public static final int START_USER_IN_BACKGROUND = 0x0008;
-
- /**
* Flag used by {@link #createAndManageUser} to specify that the newly created user should skip
* the disabling of system apps during provisioning.
*/
@@ -6497,7 +6518,6 @@
SKIP_SETUP_WIZARD,
MAKE_USER_EPHEMERAL,
MAKE_USER_DEMO,
- START_USER_IN_BACKGROUND,
LEAVE_ALL_SYSTEM_APPS_ENABLED
})
@Retention(RetentionPolicy.SOURCE)
@@ -6526,7 +6546,8 @@
* IllegalArgumentException is thrown.
* @param adminExtras Extras that will be passed to onEnable of the admin receiver on the new
* user.
- * @param flags {@link #SKIP_SETUP_WIZARD} is supported.
+ * @param flags {@link #SKIP_SETUP_WIZARD}, {@link #MAKE_USER_EPHEMERAL} and
+ * {@link #LEAVE_ALL_SYSTEM_APPS_ENABLED} are supported.
* @see UserHandle
* @return the {@link android.os.UserHandle} object for the created user, or {@code null} if the
* user could not be created.
@@ -6545,8 +6566,8 @@
}
/**
- * Called by a device owner to remove a user and all associated data. The primary user can not
- * be removed.
+ * Called by a device owner to remove a user/profile and all associated data. The primary user
+ * can not be removed.
*
* @param admin Which {@link DeviceAdminReceiver} this request is associated with.
* @param userHandle the user to remove.
@@ -6563,14 +6584,14 @@
}
/**
- * Called by a device owner to switch the specified user to the foreground.
- * <p> This cannot be used to switch to a managed profile.
+ * Called by a device owner to switch the specified secondary user to the foreground.
*
* @param admin Which {@link DeviceAdminReceiver} this request is associated with.
* @param userHandle the user to switch to; null will switch to primary.
* @return {@code true} if the switch was successful, {@code false} otherwise.
* @throws SecurityException if {@code admin} is not a device owner.
* @see Intent#ACTION_USER_FOREGROUND
+ * @see #getSecondaryUsers(ComponentName)
*/
public boolean switchUser(@NonNull ComponentName admin, @Nullable UserHandle userHandle) {
throwIfParentInstance("switchUser");
@@ -6582,13 +6603,32 @@
}
/**
+ * 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.
+ * @throws SecurityException if {@code admin} is not a device owner.
+ * @see #getSecondaryUsers(ComponentName)
+ */
+ public boolean startUserInBackground(
+ @NonNull ComponentName admin, @NonNull UserHandle userHandle) {
+ throwIfParentInstance("startUserInBackground");
+ try {
+ return mService.startUserInBackground(admin, userHandle);
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Called by a device owner to stop the specified secondary user.
- * <p> This cannot be used to stop the primary user or a managed profile.
*
* @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.
* @throws SecurityException if {@code admin} is not a device owner.
+ * @see #getSecondaryUsers(ComponentName)
*/
public boolean stopUser(@NonNull ComponentName admin, @NonNull UserHandle userHandle) {
throwIfParentInstance("stopUser");
@@ -6600,14 +6640,13 @@
}
/**
- * Called by a profile owner that is affiliated with the device to stop the calling user
- * and switch back to primary.
- * <p> This has no effect when called on a managed profile.
+ * Called by a profile owner of secondary user that is affiliated with the device to stop the
+ * 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.
* @throws SecurityException if {@code admin} is not a profile owner affiliated with the device.
- * @see #isAffiliatedUser
+ * @see #getSecondaryUsers(ComponentName)
*/
public boolean logoutUser(@NonNull ComponentName admin) {
throwIfParentInstance("logoutUser");
@@ -6619,17 +6658,18 @@
}
/**
- * Called by a device owner to list all secondary users on the device, excluding managed
- * profiles.
+ * Called by a device owner to list all secondary users on the device. Managed profiles are not
+ * considered as secondary users.
* <p> Used for various user management APIs, including {@link #switchUser}, {@link #removeUser}
* and {@link #stopUser}.
*
* @param admin Which {@link DeviceAdminReceiver} this request is associated with.
* @return list of other {@link UserHandle}s on the device.
* @throws SecurityException if {@code admin} is not a device owner.
- * @see #switchUser
- * @see #removeUser
- * @see #stopUser
+ * @see #removeUser(ComponentName, UserHandle)
+ * @see #switchUser(ComponentName, UserHandle)
+ * @see #startUserInBackground(ComponentName, UserHandle)
+ * @see #stopUser(ComponentName, UserHandle)
*/
public List<UserHandle> getSecondaryUsers(@NonNull ComponentName admin) {
throwIfParentInstance("getSecondaryUsers");
diff --git a/core/java/android/app/admin/DnsEvent.java b/core/java/android/app/admin/DnsEvent.java
index 4ddf13e..a2d704b 100644
--- a/core/java/android/app/admin/DnsEvent.java
+++ b/core/java/android/app/admin/DnsEvent.java
@@ -96,7 +96,7 @@
@Override
public String toString() {
- return String.format("DnsEvent(%s, %s, %d, %d, %s)", mHostname,
+ return String.format("DnsEvent(%d, %s, %s, %d, %d, %s)", mId, mHostname,
(mIpAddresses == null) ? "NONE" : String.join(" ", mIpAddresses),
mIpAddressesCount, mTimestamp, mPackageName);
}
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index 1d8ddee..7154053 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -226,6 +226,7 @@
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);
List<UserHandle> getSecondaryUsers(in ComponentName who);
diff --git a/core/java/android/app/backup/BackupTransport.java b/core/java/android/app/backup/BackupTransport.java
index da81d19..3558e34 100644
--- a/core/java/android/app/backup/BackupTransport.java
+++ b/core/java/android/app/backup/BackupTransport.java
@@ -55,6 +55,22 @@
// Transport should ignore its own moratoriums for call with this flag set.
public static final int FLAG_USER_INITIATED = 1;
+ /**
+ * For key value backup, indicates that the backup data is a diff from a previous backup. The
+ * transport must apply this diff to an existing backup to build the new backup set.
+ *
+ * @hide
+ */
+ public static final int FLAG_INCREMENTAL = 1 << 1;
+
+ /**
+ * For key value backup, indicates that the backup data is a complete set, not a diff from a
+ * previous backup. The transport should clear any previous backup when storing this backup.
+ *
+ * @hide
+ */
+ public static final int FLAG_NON_INCREMENTAL = 1 << 2;
+
IBackupTransport mBinderImpl = new TransportImpl();
public IBinder getBinder() {
@@ -231,12 +247,18 @@
* {@link #TRANSPORT_OK}, {@link #finishBackup} will then be called to ensure the data
* is sent and recorded successfully.
*
+ * If the backup data is a diff against the previous backup then the flag {@link
+ * BackupTransport#FLAG_INCREMENTAL} will be set. Otherwise, if the data is a complete backup
+ * set then {@link BackupTransport#FLAG_NON_INCREMENTAL} will be set. Before P neither flag will
+ * be set regardless of whether the backup is incremental or not.
+ *
* @param packageInfo The identity of the application whose data is being backed up.
* This specifically includes the signature list for the package.
* @param inFd Descriptor of file with data that resulted from invoking the application's
* BackupService.doBackup() method. This may be a pipe rather than a file on
* persistent media, so it may not be seekable.
- * @param flags {@link BackupTransport#FLAG_USER_INITIATED} or 0.
+ * @param flags a combination of {@link BackupTransport#FLAG_USER_INITIATED}, {@link
+ * BackupTransport#FLAG_NON_INCREMENTAL}, {@link BackupTransport#FLAG_INCREMENTAL}, or 0.
* @return one of {@link BackupTransport#TRANSPORT_OK} (OK so far),
* {@link BackupTransport#TRANSPORT_PACKAGE_REJECTED} (to suppress backup of this
* specific package, but allow others to proceed),
diff --git a/core/java/android/app/servertransaction/ClientTransaction.java b/core/java/android/app/servertransaction/ClientTransaction.java
index 7703c6b..08ad2f0 100644
--- a/core/java/android/app/servertransaction/ClientTransaction.java
+++ b/core/java/android/app/servertransaction/ClientTransaction.java
@@ -56,6 +56,11 @@
/** Target client activity. Might be null if the entire transaction is targeting an app. */
private IBinder mActivityToken;
+ /** Get the target client of the transaction. */
+ public IApplicationThread getClient() {
+ return mClient;
+ }
+
/**
* Add a message to the end of the sequence of callbacks.
* @param activityCallback A single message that can contain a lifecycle request/callback.
diff --git a/core/java/android/app/servertransaction/ObjectPool.java b/core/java/android/app/servertransaction/ObjectPool.java
index 9812125..2fec30a 100644
--- a/core/java/android/app/servertransaction/ObjectPool.java
+++ b/core/java/android/app/servertransaction/ObjectPool.java
@@ -16,8 +16,8 @@
package android.app.servertransaction;
+import java.util.ArrayList;
import java.util.HashMap;
-import java.util.LinkedList;
import java.util.Map;
/**
@@ -27,7 +27,7 @@
class ObjectPool {
private static final Object sPoolSync = new Object();
- private static final Map<Class, LinkedList<? extends ObjectPoolItem>> sPoolMap =
+ private static final Map<Class, ArrayList<? extends ObjectPoolItem>> sPoolMap =
new HashMap<>();
private static final int MAX_POOL_SIZE = 50;
@@ -40,9 +40,9 @@
public static <T extends ObjectPoolItem> T obtain(Class<T> itemClass) {
synchronized (sPoolSync) {
@SuppressWarnings("unchecked")
- LinkedList<T> itemPool = (LinkedList<T>) sPoolMap.get(itemClass);
+ final ArrayList<T> itemPool = (ArrayList<T>) sPoolMap.get(itemClass);
if (itemPool != null && !itemPool.isEmpty()) {
- return itemPool.poll();
+ return itemPool.remove(itemPool.size() - 1);
}
return null;
}
@@ -56,16 +56,20 @@
public static <T extends ObjectPoolItem> void recycle(T item) {
synchronized (sPoolSync) {
@SuppressWarnings("unchecked")
- LinkedList<T> itemPool = (LinkedList<T>) sPoolMap.get(item.getClass());
+ ArrayList<T> itemPool = (ArrayList<T>) sPoolMap.get(item.getClass());
if (itemPool == null) {
- itemPool = new LinkedList<>();
+ itemPool = new ArrayList<>();
sPoolMap.put(item.getClass(), itemPool);
}
- if (itemPool.contains(item)) {
- throw new IllegalStateException("Trying to recycle already recycled item");
+ // Check if the item is already in the pool
+ final int size = itemPool.size();
+ for (int i = 0; i < size; i++) {
+ if (itemPool.get(i) == item) {
+ throw new IllegalStateException("Trying to recycle already recycled item");
+ }
}
- if (itemPool.size() < MAX_POOL_SIZE) {
+ if (size < MAX_POOL_SIZE) {
itemPool.add(item);
}
}
diff --git a/core/java/android/app/usage/UsageStatsManagerInternal.java b/core/java/android/app/usage/UsageStatsManagerInternal.java
index 93e14de..5cd0981 100644
--- a/core/java/android/app/usage/UsageStatsManagerInternal.java
+++ b/core/java/android/app/usage/UsageStatsManagerInternal.java
@@ -22,6 +22,7 @@
import android.content.res.Configuration;
import java.util.List;
+import java.util.Set;
/**
* UsageStatsManager local system service interface.
@@ -159,6 +160,22 @@
public abstract void applyRestoredPayload(@UserIdInt int userId, String key, byte[] payload);
/**
+ * Called by DevicePolicyManagerService to inform that a new admin has been added.
+ *
+ * @param packageName the package in which the admin component is part of.
+ * @param userId the userId in which the admin has been added.
+ */
+ public abstract void onActiveAdminAdded(String packageName, int userId);
+
+ /**
+ * Called by DevicePolicyManagerService to inform about the active admins in an user.
+ *
+ * @param adminApps the set of active admins in {@param userId} or null if there are none.
+ * @param userId the userId to which the admin apps belong.
+ */
+ public abstract void setActiveAdminApps(Set<String> adminApps, int userId);
+
+ /**
* Return usage stats.
*
* @param obfuscateInstantApps whether instant app package names need to be obfuscated in the
diff --git a/core/java/android/bluetooth/BluetoothDevice.java b/core/java/android/bluetooth/BluetoothDevice.java
index ad7a93c..9b736b7 100644
--- a/core/java/android/bluetooth/BluetoothDevice.java
+++ b/core/java/android/bluetooth/BluetoothDevice.java
@@ -618,6 +618,7 @@
*
* @hide
*/
+ @SystemApi
public static final int ACCESS_UNKNOWN = 0;
/**
@@ -626,6 +627,7 @@
*
* @hide
*/
+ @SystemApi
public static final int ACCESS_ALLOWED = 1;
/**
@@ -634,6 +636,7 @@
*
* @hide
*/
+ @SystemApi
public static final int ACCESS_REJECTED = 2;
/**
diff --git a/core/java/android/bluetooth/BluetoothHeadset.java b/core/java/android/bluetooth/BluetoothHeadset.java
index c94540a..a68f485 100644
--- a/core/java/android/bluetooth/BluetoothHeadset.java
+++ b/core/java/android/bluetooth/BluetoothHeadset.java
@@ -556,8 +556,8 @@
* Set priority of the profile
*
* <p> The device should already be paired.
- * Priority can be one of {@link #PRIORITY_ON} or
- * {@link #PRIORITY_OFF},
+ * Priority can be one of {@link BluetoothProfile#PRIORITY_ON} or
+ * {@link BluetoothProfile#PRIORITY_OFF},
*
* <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}
* permission.
diff --git a/core/java/android/bluetooth/BluetoothProfile.java b/core/java/android/bluetooth/BluetoothProfile.java
index 41cf809..0e2263f 100644
--- a/core/java/android/bluetooth/BluetoothProfile.java
+++ b/core/java/android/bluetooth/BluetoothProfile.java
@@ -19,6 +19,7 @@
import android.Manifest;
import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
import java.util.List;
@@ -185,6 +186,7 @@
*
* @hide
**/
+ @SystemApi
public static final int PRIORITY_ON = 100;
/**
@@ -193,6 +195,7 @@
*
* @hide
**/
+ @SystemApi
public static final int PRIORITY_OFF = 0;
/**
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 1a0d5aa..f69e764 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -3660,6 +3660,17 @@
/**
* Use with {@link #getSystemService(String)} to retrieve a
+ * {@link android.telephony.euicc.EuiccCardManager} to access the device eUICC (embedded SIM).
+ *
+ * @see #getSystemService(String)
+ * @see android.telephony.euicc.EuiccCardManager
+ * TODO(b/35851809): Make this a SystemApi.
+ * @hide
+ */
+ public static final String EUICC_CARD_SERVICE = "euicc_card_service";
+
+ /**
+ * Use with {@link #getSystemService(String)} to retrieve a
* {@link android.content.ClipboardManager} for accessing and modifying
* the contents of the global clipboard.
*
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index e319750..cce6b84 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -652,4 +652,8 @@
String getInstantAppAndroidId(String packageName, int userId);
IArtManager getArtManager();
+
+ void setHarmfulAppWarning(String packageName, CharSequence warning, int userId);
+
+ CharSequence getHarmfulAppWarning(String packageName, int userId);
}
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 6cd4285..deb8dfb 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -787,7 +787,8 @@
/**
* Flag parameter for {@link #installPackage} to indicate that this package is an
- * upgrade to a package that refers to the SDK via release letter.
+ * upgrade to a package that refers to the SDK via release letter or is targeting an SDK via
+ * release letter that the current build does not support.
*
* @hide
*/
@@ -5862,4 +5863,37 @@
public @NonNull ArtManager getArtManager() {
throw new UnsupportedOperationException("getArtManager not implemented in subclass");
}
+
+ /**
+ * Sets or clears the harmful app warning details for the given app.
+ *
+ * When set, any attempt to launch an activity in this package will be intercepted and a
+ * warning dialog will be shown to the user instead, with the given warning. The user
+ * will have the option to proceed with the activity launch, or to uninstall the application.
+ *
+ * @param packageName The full name of the package to warn on.
+ * @param warning A warning string to display to the user describing the threat posed by the
+ * application, or null to clear the warning.
+ *
+ * @hide
+ */
+ @RequiresPermission(Manifest.permission.SET_HARMFUL_APP_WARNINGS)
+ @SystemApi
+ public void setHarmfulAppWarning(@NonNull String packageName, @Nullable CharSequence warning) {
+ throw new UnsupportedOperationException("setHarmfulAppWarning not implemented in subclass");
+ }
+
+ /**
+ * Returns the harmful app warning string for the given app, or null if there is none set.
+ *
+ * @param packageName The full name of the desired package.
+ *
+ * @hide
+ */
+ @RequiresPermission(Manifest.permission.SET_HARMFUL_APP_WARNINGS)
+ @Nullable
+ @SystemApi
+ public CharSequence getHarmfulAppWarning(@NonNull String packageName) {
+ throw new UnsupportedOperationException("getHarmfulAppWarning not implemented in subclass");
+ }
}
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index 77eb57f2..a18f22e 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -35,7 +35,6 @@
import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES;
import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_NOT_APK;
-import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_NO_CERTIFICATES;
import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION;
import static android.os.Build.VERSION_CODES.O;
import static android.os.Trace.TRACE_TAG_PACKAGE_MANAGER;
@@ -85,7 +84,6 @@
import android.util.Slog;
import android.util.SparseArray;
import android.util.TypedValue;
-import android.util.apk.ApkSignatureSchemeV2Verifier;
import android.util.apk.ApkSignatureVerifier;
import android.view.Gravity;
@@ -112,8 +110,7 @@
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
-import java.security.cert.Certificate;
-import java.security.cert.CertificateEncodingException;
+import java.security.cert.CertificateException;
import java.security.spec.EncodedKeySpec;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.X509EncodedKeySpec;
@@ -158,10 +155,6 @@
private static final boolean MULTI_PACKAGE_APK_ENABLED = Build.IS_DEBUGGABLE &&
SystemProperties.getBoolean(PROPERTY_CHILD_PACKAGES_ENABLED, false);
- public static final int APK_SIGNING_UNKNOWN = 0;
- public static final int APK_SIGNING_V1 = 1;
- public static final int APK_SIGNING_V2 = 2;
-
private static final float DEFAULT_PRE_O_MAX_ASPECT_RATIO = 1.86f;
// TODO: switch outError users to PackageParserException
@@ -477,8 +470,7 @@
public final int revisionCode;
public final int installLocation;
public final VerifierInfo[] verifiers;
- public final Signature[] signatures;
- public final Certificate[][] certificates;
+ public final SigningDetails signingDetails;
public final boolean coreApp;
public final boolean debuggable;
public final boolean multiArch;
@@ -486,10 +478,11 @@
public final boolean extractNativeLibs;
public final boolean isolatedSplits;
- public ApkLite(String codePath, String packageName, String splitName, boolean isFeatureSplit,
+ public ApkLite(String codePath, String packageName, String splitName,
+ boolean isFeatureSplit,
String configForSplit, String usesSplitName, int versionCode, int versionCodeMajor,
int revisionCode, int installLocation, List<VerifierInfo> verifiers,
- Signature[] signatures, Certificate[][] certificates, boolean coreApp,
+ SigningDetails signingDetails, boolean coreApp,
boolean debuggable, boolean multiArch, boolean use32bitAbi,
boolean extractNativeLibs, boolean isolatedSplits) {
this.codePath = codePath;
@@ -502,9 +495,8 @@
this.versionCodeMajor = versionCodeMajor;
this.revisionCode = revisionCode;
this.installLocation = installLocation;
+ this.signingDetails = signingDetails;
this.verifiers = verifiers.toArray(new VerifierInfo[verifiers.size()]);
- this.signatures = signatures;
- this.certificates = certificates;
this.coreApp = coreApp;
this.debuggable = debuggable;
this.multiArch = multiArch;
@@ -807,10 +799,10 @@
}
}
if ((flags&PackageManager.GET_SIGNATURES) != 0) {
- int N = (p.mSignatures != null) ? p.mSignatures.length : 0;
- if (N > 0) {
- pi.signatures = new Signature[N];
- System.arraycopy(p.mSignatures, 0, pi.signatures, 0, N);
+ if (p.mSigningDetails.hasSignatures()) {
+ int numberOfSigs = p.mSigningDetails.signatures.length;
+ pi.signatures = new Signature[numberOfSigs];
+ System.arraycopy(p.mSigningDetails.signatures, 0, pi.signatures, 0, numberOfSigs);
}
}
return pi;
@@ -818,15 +810,14 @@
public static final int PARSE_MUST_BE_APK = 1 << 0;
public static final int PARSE_IGNORE_PROCESSES = 1 << 1;
+ /** @deprecated forward lock no longer functional. remove. */
+ @Deprecated
public static final int PARSE_FORWARD_LOCK = 1 << 2;
public static final int PARSE_EXTERNAL_STORAGE = 1 << 3;
public static final int PARSE_IS_SYSTEM_DIR = 1 << 4;
public static final int PARSE_COLLECT_CERTIFICATES = 1 << 5;
public static final int PARSE_ENFORCE_CODE = 1 << 6;
public static final int PARSE_FORCE_SDK = 1 << 7;
- /** @deprecated remove when fixing b/68860689 */
- @Deprecated
- public static final int PARSE_IS_EPHEMERAL = 1 << 8;
public static final int PARSE_CHATTY = 1 << 31;
@IntDef(flag = true, prefix = { "PARSE_" }, value = {
@@ -837,7 +828,6 @@
PARSE_FORCE_SDK,
PARSE_FORWARD_LOCK,
PARSE_IGNORE_PROCESSES,
- PARSE_IS_EPHEMERAL,
PARSE_IS_SYSTEM_DIR,
PARSE_MUST_BE_APK,
})
@@ -1349,7 +1339,7 @@
pkg.setVolumeUuid(volumeUuid);
pkg.setApplicationVolumeUuid(volumeUuid);
pkg.setBaseCodePath(apkPath);
- pkg.setSignatures(null);
+ pkg.setSigningDetails(SigningDetails.UNKNOWN);
return pkg;
@@ -1469,57 +1459,19 @@
return pkg;
}
- public static int getApkSigningVersion(Package pkg) {
- try {
- if (ApkSignatureSchemeV2Verifier.hasSignature(pkg.baseCodePath)) {
- return APK_SIGNING_V2;
- }
- return APK_SIGNING_V1;
- } catch (IOException e) {
+ /** Parses the public keys from the set of signatures. */
+ public static ArraySet<PublicKey> toSigningKeys(Signature[] signatures)
+ throws CertificateException {
+ ArraySet<PublicKey> keys = new ArraySet<>(signatures.length);
+ for (int i = 0; i < signatures.length; i++) {
+ keys.add(signatures[i].getPublicKey());
}
- return APK_SIGNING_UNKNOWN;
- }
-
- /**
- * Populates the correct packages fields with the given certificates.
- * <p>
- * This is useful when we've already processed the certificates [such as during package
- * installation through an installer session]. We don't re-process the archive and
- * simply populate the correct fields.
- */
- public static void populateCertificates(Package pkg, Certificate[][] certificates)
- throws PackageParserException {
- pkg.mCertificates = null;
- pkg.mSignatures = null;
- pkg.mSigningKeys = null;
-
- pkg.mCertificates = certificates;
- try {
- pkg.mSignatures = ApkSignatureVerifier.convertToSignatures(certificates);
- } catch (CertificateEncodingException e) {
- // certificates weren't encoded properly; something went wrong
- throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
- "Failed to collect certificates from " + pkg.baseCodePath, e);
- }
- pkg.mSigningKeys = new ArraySet<>(certificates.length);
- for (int i = 0; i < certificates.length; i++) {
- Certificate[] signerCerts = certificates[i];
- Certificate signerCert = signerCerts[0];
- pkg.mSigningKeys.add(signerCert.getPublicKey());
- }
- // add signatures to child packages
- final int childCount = (pkg.childPackages != null) ? pkg.childPackages.size() : 0;
- for (int i = 0; i < childCount; i++) {
- Package childPkg = pkg.childPackages.get(i);
- childPkg.mCertificates = pkg.mCertificates;
- childPkg.mSignatures = pkg.mSignatures;
- childPkg.mSigningKeys = pkg.mSigningKeys;
- }
+ return keys;
}
/**
* Collect certificates from all the APKs described in the given package,
- * populating {@link Package#mSignatures}. Also asserts that all APK
+ * populating {@link Package#mSigningDetails}. Also asserts that all APK
* contents are signed correctly and consistently.
*/
public static void collectCertificates(Package pkg, @ParseFlags int parseFlags)
@@ -1528,17 +1480,13 @@
final int childCount = (pkg.childPackages != null) ? pkg.childPackages.size() : 0;
for (int i = 0; i < childCount; i++) {
Package childPkg = pkg.childPackages.get(i);
- childPkg.mCertificates = pkg.mCertificates;
- childPkg.mSignatures = pkg.mSignatures;
- childPkg.mSigningKeys = pkg.mSigningKeys;
+ childPkg.mSigningDetails = pkg.mSigningDetails;
}
}
private static void collectCertificatesInternal(Package pkg, @ParseFlags int parseFlags)
throws PackageParserException {
- pkg.mCertificates = null;
- pkg.mSignatures = null;
- pkg.mSigningKeys = null;
+ pkg.mSigningDetails = SigningDetails.UNKNOWN;
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "collectCertificates");
try {
@@ -1558,12 +1506,12 @@
throws PackageParserException {
final String apkPath = apkFile.getAbsolutePath();
- int minSignatureScheme = ApkSignatureVerifier.VERSION_JAR_SIGNATURE_SCHEME;
+ int minSignatureScheme = SigningDetails.SignatureSchemeVersion.JAR;
if (pkg.applicationInfo.isStaticSharedLibrary()) {
// must use v2 signing scheme
- minSignatureScheme = ApkSignatureVerifier.VERSION_APK_SIGNATURE_SCHEME_V2;
+ minSignatureScheme = SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V2;
}
- ApkSignatureVerifier.Result verified;
+ SigningDetails verified;
if ((parseFlags & PARSE_IS_SYSTEM_DIR) != 0) {
// systemDir APKs are already trusted, save time by not verifying
verified = ApkSignatureVerifier.plsCertsNoVerifyOnlyCerts(
@@ -1571,29 +1519,14 @@
} else {
verified = ApkSignatureVerifier.verify(apkPath, minSignatureScheme);
}
- if (verified.signatureSchemeVersion
- < ApkSignatureVerifier.VERSION_APK_SIGNATURE_SCHEME_V2) {
- // TODO (b/68860689): move this logic to packagemanagerserivce
- if ((parseFlags & PARSE_IS_EPHEMERAL) != 0) {
- throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
- "No APK Signature Scheme v2 signature in ephemeral package " + apkPath);
- }
- }
// Verify that entries are signed consistently with the first pkg
// we encountered. Note that for splits, certificates may have
// already been populated during an earlier parse of a base APK.
- if (pkg.mCertificates == null) {
- pkg.mCertificates = verified.certs;
- pkg.mSignatures = verified.sigs;
- pkg.mSigningKeys = new ArraySet<>(verified.certs.length);
- for (int i = 0; i < verified.certs.length; i++) {
- Certificate[] signerCerts = verified.certs[i];
- Certificate signerCert = signerCerts[0];
- pkg.mSigningKeys.add(signerCert.getPublicKey());
- }
+ if (pkg.mSigningDetails == SigningDetails.UNKNOWN) {
+ pkg.mSigningDetails = verified;
} else {
- if (!Signature.areExactMatch(pkg.mSignatures, verified.sigs)) {
+ if (!Signature.areExactMatch(pkg.mSigningDetails.signatures, verified.signatures)) {
throw new PackageParserException(
INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES,
apkPath + " has mismatched certificates");
@@ -1655,8 +1588,7 @@
parser = assets.openXmlResourceParser(cookie, ANDROID_MANIFEST_FILENAME);
- final Signature[] signatures;
- final Certificate[][] certificates;
+ final SigningDetails signingDetails;
if ((flags & PARSE_COLLECT_CERTIFICATES) != 0) {
// TODO: factor signature related items out of Package object
final Package tempPkg = new Package((String) null);
@@ -1666,15 +1598,13 @@
} finally {
Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
}
- signatures = tempPkg.mSignatures;
- certificates = tempPkg.mCertificates;
+ signingDetails = tempPkg.mSigningDetails;
} else {
- signatures = null;
- certificates = null;
+ signingDetails = SigningDetails.UNKNOWN;
}
final AttributeSet attrs = parser;
- return parseApkLite(apkPath, parser, attrs, signatures, certificates);
+ return parseApkLite(apkPath, parser, attrs, signingDetails);
} catch (XmlPullParserException | IOException | RuntimeException e) {
Slog.w(TAG, "Failed to parse " + apkPath, e);
@@ -1761,7 +1691,7 @@
}
private static ApkLite parseApkLite(String codePath, XmlPullParser parser, AttributeSet attrs,
- Signature[] signatures, Certificate[][] certificates)
+ SigningDetails signingDetails)
throws IOException, XmlPullParserException, PackageParserException {
final Pair<String, String> packageSplit = parsePackageSplitNames(parser, attrs);
@@ -1854,7 +1784,7 @@
return new ApkLite(codePath, packageSplit.first, packageSplit.second, isFeatureSplit,
configForSplit, usesSplitName, versionCode, versionCodeMajor, revisionCode,
- installLocation, verifiers, signatures, certificates, coreApp, debuggable,
+ installLocation, verifiers, signingDetails, coreApp, debuggable,
multiArch, use32bitAbi, extractNativeLibs, isolatedSplits);
}
@@ -2039,11 +1969,6 @@
String str = sa.getNonConfigurationString(
com.android.internal.R.styleable.AndroidManifest_sharedUserId, 0);
if (str != null && str.length() > 0) {
- if ((flags & PARSE_IS_EPHEMERAL) != 0) {
- outError[0] = "sharedUserId not allowed in ephemeral application";
- mParseError = PackageManager.INSTALL_PARSE_FAILED_BAD_SHARED_USER_ID;
- return null;
- }
String nameError = validateName(str, true, false);
if (nameError != null && !"android".equals(pkg.packageName)) {
outError[0] = "<manifest> specifies bad sharedUserId name \""
@@ -2304,8 +2229,9 @@
return null;
}
+ boolean defaultToCurrentDevBranch = (flags & PARSE_FORCE_SDK) != 0;
final int targetSdkVersion = PackageParser.computeTargetSdkVersion(targetVers,
- targetCode, SDK_VERSION, SDK_CODENAMES, outError);
+ targetCode, SDK_CODENAMES, outError, defaultToCurrentDevBranch);
if (targetSdkVersion < 0) {
mParseError = PackageManager.INSTALL_FAILED_OLDER_SDK;
return null;
@@ -2621,19 +2547,19 @@
* application manifest, or 0 otherwise
* @param targetCode targetSdkVersion code, if specified in the application
* manifest, or {@code null} otherwise
- * @param platformSdkVersion platform SDK version number, typically
- * Build.VERSION.SDK_INT
* @param platformSdkCodenames array of allowed pre-release SDK codenames
* for this platform
* @param outError output array to populate with error, if applicable
+ * @param forceCurrentDev if development target code is not available, use the current
+ * development version by default.
* @return the targetSdkVersion to use at runtime, or -1 if the package is
* not compatible with this platform
* @hide Exposed for unit testing only.
*/
@TestApi
public static int computeTargetSdkVersion(@IntRange(from = 0) int targetVers,
- @Nullable String targetCode, @IntRange(from = 1) int platformSdkVersion,
- @NonNull String[] platformSdkCodenames, @NonNull String[] outError) {
+ @Nullable String targetCode, @NonNull String[] platformSdkCodenames,
+ @NonNull String[] outError, boolean forceCurrentDev) {
// If it's a release SDK, return the version number unmodified.
if (targetCode == null) {
return targetVers;
@@ -2641,7 +2567,7 @@
// If it's a pre-release SDK and the codename matches this platform, it
// definitely targets this SDK.
- if (ArrayUtils.contains(platformSdkCodenames, targetCode)) {
+ if (ArrayUtils.contains(platformSdkCodenames, targetCode) || forceCurrentDev) {
return Build.VERSION_CODES.CUR_DEVELOPMENT;
}
@@ -5734,6 +5660,117 @@
return true;
}
+ /** A container for signing-related data of an application package. */
+ public static final class SigningDetails implements Parcelable {
+
+ @IntDef({SigningDetails.SignatureSchemeVersion.UNKNOWN,
+ SigningDetails.SignatureSchemeVersion.JAR,
+ SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V2,
+ SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V3})
+ public @interface SignatureSchemeVersion {
+ int UNKNOWN = 0;
+ int JAR = 1;
+ int SIGNING_BLOCK_V2 = 2;
+ int SIGNING_BLOCK_V3 = 3;
+ }
+
+ @Nullable
+ public final Signature[] signatures;
+ @SignatureSchemeVersion
+ public final int signatureSchemeVersion;
+ @Nullable
+ public final ArraySet<PublicKey> publicKeys;
+
+ /** A representation of unknown signing details. Use instead of null. */
+ public static final SigningDetails UNKNOWN =
+ new SigningDetails(null, SignatureSchemeVersion.UNKNOWN, null);
+
+ @VisibleForTesting
+ public SigningDetails(Signature[] signatures,
+ @SignatureSchemeVersion int signatureSchemeVersion,
+ ArraySet<PublicKey> keys) {
+ this.signatures = signatures;
+ this.signatureSchemeVersion = signatureSchemeVersion;
+ this.publicKeys = keys;
+ }
+
+ public SigningDetails(Signature[] signatures,
+ @SignatureSchemeVersion int signatureSchemeVersion)
+ throws CertificateException {
+ this(signatures, signatureSchemeVersion, toSigningKeys(signatures));
+ }
+
+ /** Returns true if the signing details have one or more signatures. */
+ public boolean hasSignatures() {
+ return signatures != null && signatures.length > 0;
+ }
+
+ /** Returns true if the signatures in this and other match exactly. */
+ public boolean signaturesMatchExactly(SigningDetails other) {
+ return Signature.areExactMatch(this.signatures, other.signatures);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ boolean isUnknown = UNKNOWN == this;
+ dest.writeBoolean(isUnknown);
+ if (isUnknown) {
+ return;
+ }
+ dest.writeTypedArray(this.signatures, flags);
+ dest.writeInt(this.signatureSchemeVersion);
+ dest.writeArraySet(this.publicKeys);
+ }
+
+ protected SigningDetails(Parcel in) {
+ final ClassLoader boot = Object.class.getClassLoader();
+ this.signatures = in.createTypedArray(Signature.CREATOR);
+ this.signatureSchemeVersion = in.readInt();
+ this.publicKeys = (ArraySet<PublicKey>) in.readArraySet(boot);
+ }
+
+ public static final Creator<SigningDetails> CREATOR = new Creator<SigningDetails>() {
+ @Override
+ public SigningDetails createFromParcel(Parcel source) {
+ if (source.readBoolean()) {
+ return UNKNOWN;
+ }
+ return new SigningDetails(source);
+ }
+
+ @Override
+ public SigningDetails[] newArray(int size) {
+ return new SigningDetails[size];
+ }
+ };
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof SigningDetails)) return false;
+
+ SigningDetails that = (SigningDetails) o;
+
+ if (signatureSchemeVersion != that.signatureSchemeVersion) return false;
+ if (!Signature.areExactMatch(signatures, that.signatures)) return false;
+ return publicKeys != null ? publicKeys.equals(that.publicKeys)
+ : that.publicKeys == null;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = +Arrays.hashCode(signatures);
+ result = 31 * result + signatureSchemeVersion;
+ result = 31 * result + (publicKeys != null ? publicKeys.hashCode() : 0);
+ return result;
+ }
+ }
+
/**
* Representation of a full package parsed from APK files on disk. A package
* consists of a single base APK, and zero or more split APKs.
@@ -5840,8 +5877,7 @@
public int mSharedUserLabel;
// Signatures that were read from the package.
- public Signature[] mSignatures;
- public Certificate[][] mCertificates;
+ @NonNull public SigningDetails mSigningDetails = SigningDetails.UNKNOWN;
// For use by package manager service for quick lookup of
// preferred up order.
@@ -5893,7 +5929,6 @@
/**
* Data used to feed the KeySetManagerService
*/
- public ArraySet<PublicKey> mSigningKeys;
public ArraySet<String> mUpgradeKeySets;
public ArrayMap<String, ArraySet<PublicKey>> mKeySetMapping;
@@ -5950,6 +5985,8 @@
}
}
+ /** @deprecated Forward locked apps no longer supported. Resource path not needed. */
+ @Deprecated
public void setApplicationInfoResourcePath(String resourcePath) {
this.applicationInfo.setResourcePath(resourcePath);
if (childPackages != null) {
@@ -5960,6 +5997,8 @@
}
}
+ /** @deprecated Forward locked apps no longer supported. Resource path not needed. */
+ @Deprecated
public void setApplicationInfoBaseResourcePath(String resourcePath) {
this.applicationInfo.setBaseResourcePath(resourcePath);
if (childPackages != null) {
@@ -6008,6 +6047,8 @@
// Children have no splits
}
+ /** @deprecated Forward locked apps no longer supported. Resource path not needed. */
+ @Deprecated
public void setApplicationInfoSplitResourcePaths(String[] resroucePaths) {
this.applicationInfo.setSplitResourcePaths(resroucePaths);
// Children have no splits
@@ -6037,12 +6078,13 @@
}
}
- public void setSignatures(Signature[] signatures) {
- this.mSignatures = signatures;
+ /** Sets signing details on the package and any of its children. */
+ public void setSigningDetails(@NonNull SigningDetails signingDetails) {
+ mSigningDetails = signingDetails;
if (childPackages != null) {
final int packageCount = childPackages.size();
for (int i = 0; i < packageCount; i++) {
- childPackages.get(i).mSignatures = signatures;
+ childPackages.get(i).mSigningDetails = signingDetails;
}
}
}
@@ -6348,8 +6390,7 @@
}
mSharedUserLabel = dest.readInt();
- mSignatures = (Signature[]) dest.readParcelableArray(boot, Signature.class);
- mCertificates = (Certificate[][]) dest.readSerializable();
+ mSigningDetails = dest.readParcelable(boot);
mPreferredOrder = dest.readInt();
@@ -6389,7 +6430,6 @@
mTrustedOverlay = (dest.readInt() == 1);
mCompileSdkVersion = dest.readInt();
mCompileSdkVersionCodename = dest.readString();
- mSigningKeys = (ArraySet<PublicKey>) dest.readArraySet(boot);
mUpgradeKeySets = (ArraySet<String>) dest.readArraySet(boot);
mKeySetMapping = readKeySetMapping(dest);
@@ -6489,8 +6529,7 @@
dest.writeString(mSharedUserId);
dest.writeInt(mSharedUserLabel);
- dest.writeParcelableArray(mSignatures, flags);
- dest.writeSerializable(mCertificates);
+ dest.writeParcelable(mSigningDetails, flags);
dest.writeInt(mPreferredOrder);
@@ -6515,7 +6554,6 @@
dest.writeInt(mTrustedOverlay ? 1 : 0);
dest.writeInt(mCompileSdkVersion);
dest.writeString(mCompileSdkVersionCodename);
- dest.writeArraySet(mSigningKeys);
dest.writeArraySet(mUpgradeKeySets);
writeKeySetMapping(dest, mKeySetMapping);
dest.writeString(cpuAbiOverride);
diff --git a/core/java/android/content/pm/PackageUserState.java b/core/java/android/content/pm/PackageUserState.java
index 069b2d4..293beb2 100644
--- a/core/java/android/content/pm/PackageUserState.java
+++ b/core/java/android/content/pm/PackageUserState.java
@@ -52,6 +52,7 @@
public int appLinkGeneration;
public int categoryHint = ApplicationInfo.CATEGORY_UNDEFINED;
public int installReason;
+ public String harmfulAppWarning;
public ArraySet<String> disabledComponents;
public ArraySet<String> enabledComponents;
@@ -87,6 +88,7 @@
enabledComponents = ArrayUtils.cloneOrNull(o.enabledComponents);
overlayPaths =
o.overlayPaths == null ? null : Arrays.copyOf(o.overlayPaths, o.overlayPaths.length);
+ harmfulAppWarning = o.harmfulAppWarning;
}
/**
@@ -247,6 +249,11 @@
}
}
}
+ if (harmfulAppWarning == null && oldState.harmfulAppWarning != null
+ || (harmfulAppWarning != null
+ && !harmfulAppWarning.equals(oldState.harmfulAppWarning))) {
+ return false;
+ }
return true;
}
}
diff --git a/core/java/android/content/res/ResourcesImpl.java b/core/java/android/content/res/ResourcesImpl.java
index 3239212..97cb78b 100644
--- a/core/java/android/content/res/ResourcesImpl.java
+++ b/core/java/android/content/res/ResourcesImpl.java
@@ -815,7 +815,7 @@
} finally {
stack.pop();
}
- } catch (Exception e) {
+ } catch (Exception | StackOverflowError e) {
Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
final NotFoundException rnf = new NotFoundException(
"File " + file + " from drawable resource ID #0x" + Integer.toHexString(id));
diff --git a/core/java/android/hardware/display/BrightnessChangeEvent.java b/core/java/android/hardware/display/BrightnessChangeEvent.java
index 0a08353..2301824 100644
--- a/core/java/android/hardware/display/BrightnessChangeEvent.java
+++ b/core/java/android/hardware/display/BrightnessChangeEvent.java
@@ -16,6 +16,8 @@
package android.hardware.display;
+import android.annotation.SystemApi;
+import android.annotation.TestApi;
import android.os.Parcel;
import android.os.Parcelable;
@@ -23,51 +25,65 @@
* Data about a brightness settings change.
*
* {@see DisplayManager.getBrightnessEvents()}
- * TODO make this SystemAPI
* @hide
*/
+@SystemApi
+@TestApi
public final class BrightnessChangeEvent implements Parcelable {
/** Brightness in nits */
- public float brightness;
+ public final float brightness;
/** Timestamp of the change {@see System.currentTimeMillis()} */
- public long timeStamp;
+ public final long timeStamp;
/** Package name of focused activity when brightness was changed.
* This will be null if the caller of {@see DisplayManager.getBrightnessEvents()}
* does not have access to usage stats {@see UsageStatsManager} */
- public String packageName;
+ public final String packageName;
/** User id of of the user running when brightness was changed.
* @hide */
- public int userId;
+ public final int userId;
/** Lux values of recent sensor data */
- public float[] luxValues;
+ public final float[] luxValues;
/** Timestamps of the lux sensor readings {@see System.currentTimeMillis()} */
- public long[] luxTimestamps;
+ public final long[] luxTimestamps;
/** Most recent battery level when brightness was changed or Float.NaN */
- public float batteryLevel;
+ public final float batteryLevel;
/** Color filter active to provide night mode */
- public boolean nightMode;
+ public final boolean nightMode;
/** If night mode color filter is active this will be the temperature in kelvin */
- public int colorTemperature;
+ public final int colorTemperature;
- /** Brightness level before slider adjustment */
- public float lastBrightness;
+ /** Brightness le vel before slider adjustment */
+ public final float lastBrightness;
- public BrightnessChangeEvent() {
+ /** @hide */
+ private BrightnessChangeEvent(float brightness, long timeStamp, String packageName,
+ int userId, float[] luxValues, long[] luxTimestamps, float batteryLevel,
+ boolean nightMode, int colorTemperature, float lastBrightness) {
+ this.brightness = brightness;
+ this.timeStamp = timeStamp;
+ this.packageName = packageName;
+ this.userId = userId;
+ this.luxValues = luxValues;
+ this.luxTimestamps = luxTimestamps;
+ this.batteryLevel = batteryLevel;
+ this.nightMode = nightMode;
+ this.colorTemperature = colorTemperature;
+ this.lastBrightness = lastBrightness;
}
/** @hide */
- public BrightnessChangeEvent(BrightnessChangeEvent other) {
+ public BrightnessChangeEvent(BrightnessChangeEvent other, boolean redactPackage) {
this.brightness = other.brightness;
this.timeStamp = other.timeStamp;
- this.packageName = other.packageName;
+ this.packageName = redactPackage ? null : other.packageName;
this.userId = other.userId;
this.luxValues = other.luxValues;
this.luxTimestamps = other.luxTimestamps;
@@ -118,4 +134,85 @@
dest.writeInt(colorTemperature);
dest.writeFloat(lastBrightness);
}
+
+ /** @hide */
+ public static class Builder {
+ private float mBrightness;
+ private long mTimeStamp;
+ private String mPackageName;
+ private int mUserId;
+ private float[] mLuxValues;
+ private long[] mLuxTimestamps;
+ private float mBatteryLevel;
+ private boolean mNightMode;
+ private int mColorTemperature;
+ private float mLastBrightness;
+
+ /** {@see BrightnessChangeEvent#brightness} */
+ public Builder setBrightness(float brightness) {
+ mBrightness = brightness;
+ return this;
+ }
+
+ /** {@see BrightnessChangeEvent#timeStamp} */
+ public Builder setTimeStamp(long timeStamp) {
+ mTimeStamp = timeStamp;
+ return this;
+ }
+
+ /** {@see BrightnessChangeEvent#packageName} */
+ public Builder setPackageName(String packageName) {
+ mPackageName = packageName;
+ return this;
+ }
+
+ /** {@see BrightnessChangeEvent#userId} */
+ public Builder setUserId(int userId) {
+ mUserId = userId;
+ return this;
+ }
+
+ /** {@see BrightnessChangeEvent#luxValues} */
+ public Builder setLuxValues(float[] luxValues) {
+ mLuxValues = luxValues;
+ return this;
+ }
+
+ /** {@see BrightnessChangeEvent#luxTimestamps} */
+ public Builder setLuxTimestamps(long[] luxTimestamps) {
+ mLuxTimestamps = luxTimestamps;
+ return this;
+ }
+
+ /** {@see BrightnessChangeEvent#batteryLevel} */
+ public Builder setBatteryLevel(float batteryLevel) {
+ mBatteryLevel = batteryLevel;
+ return this;
+ }
+
+ /** {@see BrightnessChangeEvent#nightMode} */
+ public Builder setNightMode(boolean nightMode) {
+ mNightMode = nightMode;
+ return this;
+ }
+
+ /** {@see BrightnessChangeEvent#colorTemperature} */
+ public Builder setColorTemperature(int colorTemperature) {
+ mColorTemperature = colorTemperature;
+ return this;
+ }
+
+ /** {@see BrightnessChangeEvent#lastBrightness} */
+ public Builder setLastBrightness(float lastBrightness) {
+ mLastBrightness = lastBrightness;
+ return this;
+ }
+
+ /** Builds a BrightnessChangeEvent */
+ public BrightnessChangeEvent build() {
+ return new BrightnessChangeEvent(mBrightness, mTimeStamp,
+ mPackageName, mUserId, mLuxValues, mLuxTimestamps, mBatteryLevel,
+ mNightMode, mColorTemperature, mLastBrightness);
+ }
+ }
}
diff --git a/core/java/android/hardware/display/BrightnessConfiguration.java b/core/java/android/hardware/display/BrightnessConfiguration.java
index 6c3be81..2156491 100644
--- a/core/java/android/hardware/display/BrightnessConfiguration.java
+++ b/core/java/android/hardware/display/BrightnessConfiguration.java
@@ -16,6 +16,8 @@
package android.hardware.display;
+import android.annotation.SystemApi;
+import android.annotation.TestApi;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.Pair;
@@ -25,6 +27,8 @@
import java.util.Arrays;
/** @hide */
+@SystemApi
+@TestApi
public final class BrightnessConfiguration implements Parcelable {
private final float[] mLux;
private final float[] mNits;
diff --git a/core/java/android/hardware/display/DisplayManager.java b/core/java/android/hardware/display/DisplayManager.java
index 97e9b9c..76ab35d 100644
--- a/core/java/android/hardware/display/DisplayManager.java
+++ b/core/java/android/hardware/display/DisplayManager.java
@@ -22,6 +22,7 @@
import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
import android.annotation.SystemService;
+import android.annotation.TestApi;
import android.app.KeyguardManager;
import android.content.Context;
import android.graphics.Point;
@@ -622,6 +623,8 @@
* Fetch {@link BrightnessChangeEvent}s.
* @hide until we make it a system api.
*/
+ @SystemApi
+ @TestApi
@RequiresPermission(Manifest.permission.BRIGHTNESS_SLIDER_USAGE)
public List<BrightnessChangeEvent> getBrightnessEvents() {
return mGlobal.getBrightnessEvents(mContext.getOpPackageName());
@@ -632,6 +635,9 @@
*
* @hide
*/
+ @SystemApi
+ @TestApi
+ @RequiresPermission(Manifest.permission.CONFIGURE_DISPLAY_BRIGHTNESS)
public void setBrightnessConfiguration(BrightnessConfiguration c) {
setBrightnessConfigurationForUser(c, UserHandle.myUserId());
}
diff --git a/core/java/android/hardware/radio/ITuner.aidl b/core/java/android/hardware/radio/ITuner.aidl
index ca38076..bf5e391 100644
--- a/core/java/android/hardware/radio/ITuner.aidl
+++ b/core/java/android/hardware/radio/ITuner.aidl
@@ -17,6 +17,7 @@
package android.hardware.radio;
import android.graphics.Bitmap;
+import android.hardware.radio.ProgramList;
import android.hardware.radio.ProgramSelector;
import android.hardware.radio.RadioManager;
@@ -73,14 +74,8 @@
*/
boolean startBackgroundScan();
- /**
- * @param vendorFilter Vendor-specific filter, must be Map<String, String>
- * @return the list, or null if scan is in progress
- * @throws IllegalArgumentException if invalid arguments are passed
- * @throws IllegalStateException if the scan has not been started, client may
- * call startBackgroundScan to fix this.
- */
- List<RadioManager.ProgramInfo> getProgramList(in Map vendorFilter);
+ void startProgramListUpdates(in ProgramList.Filter filter);
+ void stopProgramListUpdates();
boolean isConfigFlagSupported(int flag);
boolean isConfigFlagSet(int flag);
diff --git a/core/java/android/hardware/radio/ITunerCallback.aidl b/core/java/android/hardware/radio/ITunerCallback.aidl
index 775e25c..54af30f 100644
--- a/core/java/android/hardware/radio/ITunerCallback.aidl
+++ b/core/java/android/hardware/radio/ITunerCallback.aidl
@@ -16,6 +16,7 @@
package android.hardware.radio;
+import android.hardware.radio.ProgramList;
import android.hardware.radio.RadioManager;
import android.hardware.radio.RadioMetadata;
@@ -30,6 +31,7 @@
void onBackgroundScanAvailabilityChange(boolean isAvailable);
void onBackgroundScanComplete();
void onProgramListChanged();
+ void onProgramListUpdated(in ProgramList.Chunk chunk);
/**
* @param parameters Vendor-specific key-value pairs, must be Map<String, String>
diff --git a/telephony/java/android/telephony/data/InterfaceAddress.aidl b/core/java/android/hardware/radio/ProgramList.aidl
similarity index 77%
copy from telephony/java/android/telephony/data/InterfaceAddress.aidl
copy to core/java/android/hardware/radio/ProgramList.aidl
index d750363..34b7f97 100644
--- a/telephony/java/android/telephony/data/InterfaceAddress.aidl
+++ b/core/java/android/hardware/radio/ProgramList.aidl
@@ -1,5 +1,5 @@
-/*
- * Copyright 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,7 +14,10 @@
* limitations under the License.
*/
-/** @hide */
-package android.telephony.data;
+package android.hardware.radio;
-parcelable InterfaceAddress;
+/** @hide */
+parcelable ProgramList.Filter;
+
+/** @hide */
+parcelable ProgramList.Chunk;
diff --git a/core/java/android/hardware/radio/ProgramList.java b/core/java/android/hardware/radio/ProgramList.java
new file mode 100644
index 0000000..b2aa9ba
--- /dev/null
+++ b/core/java/android/hardware/radio/ProgramList.java
@@ -0,0 +1,427 @@
+/**
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.hardware.radio;
+
+import android.annotation.CallbackExecutor;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.concurrent.Executor;
+import java.util.stream.Collectors;
+
+/**
+ * @hide
+ */
+@SystemApi
+public final class ProgramList implements AutoCloseable {
+
+ private final Object mLock = new Object();
+ private final Map<ProgramSelector.Identifier, RadioManager.ProgramInfo> mPrograms =
+ new HashMap<>();
+
+ private final List<ListCallback> mListCallbacks = new ArrayList<>();
+ private final List<OnCompleteListener> mOnCompleteListeners = new ArrayList<>();
+ private OnCloseListener mOnCloseListener;
+ private boolean mIsClosed = false;
+ private boolean mIsComplete = false;
+
+ ProgramList() {}
+
+ /**
+ * Callback for list change operations.
+ */
+ public abstract static class ListCallback {
+ /**
+ * Called when item was modified or added to the list.
+ */
+ public void onItemChanged(@NonNull ProgramSelector.Identifier id) { }
+
+ /**
+ * Called when item was removed from the list.
+ */
+ public void onItemRemoved(@NonNull ProgramSelector.Identifier id) { }
+ }
+
+ /**
+ * Listener of list complete event.
+ */
+ public interface OnCompleteListener {
+ /**
+ * Called when the list turned complete (i.e. when the scan process
+ * came to an end).
+ */
+ void onComplete();
+ }
+
+ interface OnCloseListener {
+ void onClose();
+ }
+
+ /**
+ * Registers list change callback with executor.
+ */
+ public void registerListCallback(@NonNull @CallbackExecutor Executor executor,
+ @NonNull ListCallback callback) {
+ registerListCallback(new ListCallback() {
+ public void onItemChanged(@NonNull ProgramSelector.Identifier id) {
+ executor.execute(() -> callback.onItemChanged(id));
+ }
+
+ public void onItemRemoved(@NonNull ProgramSelector.Identifier id) {
+ executor.execute(() -> callback.onItemRemoved(id));
+ }
+ });
+ }
+
+ /**
+ * Registers list change callback.
+ */
+ public void registerListCallback(@NonNull ListCallback callback) {
+ synchronized (mLock) {
+ if (mIsClosed) return;
+ mListCallbacks.add(Objects.requireNonNull(callback));
+ }
+ }
+
+ /**
+ * Unregisters list change callback.
+ */
+ public void unregisterListCallback(@NonNull ListCallback callback) {
+ synchronized (mLock) {
+ if (mIsClosed) return;
+ mListCallbacks.remove(Objects.requireNonNull(callback));
+ }
+ }
+
+ /**
+ * Adds list complete event listener with executor.
+ */
+ public void addOnCompleteListener(@NonNull @CallbackExecutor Executor executor,
+ @NonNull OnCompleteListener listener) {
+ addOnCompleteListener(() -> executor.execute(listener::onComplete));
+ }
+
+ /**
+ * Adds list complete event listener.
+ */
+ public void addOnCompleteListener(@NonNull OnCompleteListener listener) {
+ synchronized (mLock) {
+ if (mIsClosed) return;
+ mOnCompleteListeners.add(Objects.requireNonNull(listener));
+ if (mIsComplete) listener.onComplete();
+ }
+ }
+
+ /**
+ * Removes list complete event listener.
+ */
+ public void removeOnCompleteListener(@NonNull OnCompleteListener listener) {
+ synchronized (mLock) {
+ if (mIsClosed) return;
+ mOnCompleteListeners.remove(Objects.requireNonNull(listener));
+ }
+ }
+
+ void setOnCloseListener(@Nullable OnCloseListener listener) {
+ synchronized (mLock) {
+ if (mOnCloseListener != null) {
+ throw new IllegalStateException("Close callback is already set");
+ }
+ mOnCloseListener = listener;
+ }
+ }
+
+ /**
+ * Disables list updates and releases all resources.
+ */
+ public void close() {
+ synchronized (mLock) {
+ if (mIsClosed) return;
+ mIsClosed = true;
+ mPrograms.clear();
+ mListCallbacks.clear();
+ mOnCompleteListeners.clear();
+ if (mOnCloseListener != null) {
+ mOnCloseListener.onClose();
+ mOnCloseListener = null;
+ }
+ }
+ }
+
+ void apply(@NonNull Chunk chunk) {
+ synchronized (mLock) {
+ if (mIsClosed) return;
+
+ mIsComplete = false;
+
+ if (chunk.isPurge()) {
+ new HashSet<>(mPrograms.keySet()).stream().forEach(id -> removeLocked(id));
+ }
+
+ chunk.getRemoved().stream().forEach(id -> removeLocked(id));
+ chunk.getModified().stream().forEach(info -> putLocked(info));
+
+ if (chunk.isComplete()) {
+ mIsComplete = true;
+ mOnCompleteListeners.forEach(cb -> cb.onComplete());
+ }
+ }
+ }
+
+ private void putLocked(@NonNull RadioManager.ProgramInfo value) {
+ ProgramSelector.Identifier key = value.getSelector().getPrimaryId();
+ mPrograms.put(Objects.requireNonNull(key), value);
+ ProgramSelector.Identifier sel = value.getSelector().getPrimaryId();
+ mListCallbacks.forEach(cb -> cb.onItemChanged(sel));
+ }
+
+ private void removeLocked(@NonNull ProgramSelector.Identifier key) {
+ RadioManager.ProgramInfo removed = mPrograms.remove(Objects.requireNonNull(key));
+ if (removed == null) return;
+ ProgramSelector.Identifier sel = removed.getSelector().getPrimaryId();
+ mListCallbacks.forEach(cb -> cb.onItemRemoved(sel));
+ }
+
+ /**
+ * Converts the program list in its current shape to the static List<>.
+ *
+ * @return the new List<> object; it won't receive any further updates
+ */
+ public @NonNull List<RadioManager.ProgramInfo> toList() {
+ synchronized (mLock) {
+ return mPrograms.values().stream().collect(Collectors.toList());
+ }
+ }
+
+ /**
+ * Returns the program with a specified primary identifier.
+ *
+ * @param id primary identifier of a program to fetch
+ * @return the program info, or null if there is no such program on the list
+ */
+ public @Nullable RadioManager.ProgramInfo get(@NonNull ProgramSelector.Identifier id) {
+ synchronized (mLock) {
+ return mPrograms.get(Objects.requireNonNull(id));
+ }
+ }
+
+ /**
+ * Filter for the program list.
+ */
+ public static final class Filter implements Parcelable {
+ private final @NonNull Set<Integer> mIdentifierTypes;
+ private final @NonNull Set<ProgramSelector.Identifier> mIdentifiers;
+ private final boolean mIncludeCategories;
+ private final boolean mExcludeModifications;
+ private final @Nullable Map<String, String> mVendorFilter;
+
+ /**
+ * Constructor of program list filter.
+ *
+ * Arrays passed to this constructor become owned by this object, do not modify them later.
+ *
+ * @param identifierTypes see getIdentifierTypes()
+ * @param identifiers see getIdentifiers()
+ * @param includeCategories see areCategoriesIncluded()
+ * @param excludeModifications see areModificationsExcluded()
+ */
+ public Filter(@NonNull Set<Integer> identifierTypes,
+ @NonNull Set<ProgramSelector.Identifier> identifiers,
+ boolean includeCategories, boolean excludeModifications) {
+ mIdentifierTypes = Objects.requireNonNull(identifierTypes);
+ mIdentifiers = Objects.requireNonNull(identifiers);
+ mIncludeCategories = includeCategories;
+ mExcludeModifications = excludeModifications;
+ mVendorFilter = null;
+ }
+
+ /**
+ * @hide for framework use only
+ */
+ public Filter(@Nullable Map<String, String> vendorFilter) {
+ mIdentifierTypes = Collections.emptySet();
+ mIdentifiers = Collections.emptySet();
+ mIncludeCategories = false;
+ mExcludeModifications = false;
+ mVendorFilter = vendorFilter;
+ }
+
+ private Filter(@NonNull Parcel in) {
+ mIdentifierTypes = Utils.createIntSet(in);
+ mIdentifiers = Utils.createSet(in, ProgramSelector.Identifier.CREATOR);
+ mIncludeCategories = in.readByte() != 0;
+ mExcludeModifications = in.readByte() != 0;
+ mVendorFilter = Utils.readStringMap(in);
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ Utils.writeIntSet(dest, mIdentifierTypes);
+ Utils.writeSet(dest, mIdentifiers);
+ dest.writeByte((byte) (mIncludeCategories ? 1 : 0));
+ dest.writeByte((byte) (mExcludeModifications ? 1 : 0));
+ Utils.writeStringMap(dest, mVendorFilter);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ public static final Parcelable.Creator<Filter> CREATOR = new Parcelable.Creator<Filter>() {
+ public Filter createFromParcel(Parcel in) {
+ return new Filter(in);
+ }
+
+ public Filter[] newArray(int size) {
+ return new Filter[size];
+ }
+ };
+
+ /**
+ * @hide for framework use only
+ */
+ public Map<String, String> getVendorFilter() {
+ return mVendorFilter;
+ }
+
+ /**
+ * Returns the list of identifier types that satisfy the filter.
+ *
+ * If the program list entry contains at least one identifier of the type
+ * listed, it satisfies this condition.
+ *
+ * Empty list means no filtering on identifier type.
+ *
+ * @return the list of accepted identifier types, must not be modified
+ */
+ public @NonNull Set<Integer> getIdentifierTypes() {
+ return mIdentifierTypes;
+ }
+
+ /**
+ * Returns the list of identifiers that satisfy the filter.
+ *
+ * If the program list entry contains at least one listed identifier,
+ * it satisfies this condition.
+ *
+ * Empty list means no filtering on identifier.
+ *
+ * @return the list of accepted identifiers, must not be modified
+ */
+ public @NonNull Set<ProgramSelector.Identifier> getIdentifiers() {
+ return mIdentifiers;
+ }
+
+ /**
+ * Checks, if non-tunable entries that define tree structure on the
+ * program list (i.e. DAB ensembles) should be included.
+ */
+ public boolean areCategoriesIncluded() {
+ return mIncludeCategories;
+ }
+
+ /**
+ * Checks, if updates on entry modifications should be disabled.
+ *
+ * If true, 'modified' vector of ProgramListChunk must contain list
+ * additions only. Once the program is added to the list, it's not
+ * updated anymore.
+ */
+ public boolean areModificationsExcluded() {
+ return mExcludeModifications;
+ }
+ }
+
+ /**
+ * @hide This is a transport class used for internal communication between
+ * Broadcast Radio Service and RadioManager.
+ * Do not use it directly.
+ */
+ public static final class Chunk implements Parcelable {
+ private final boolean mPurge;
+ private final boolean mComplete;
+ private final @NonNull Set<RadioManager.ProgramInfo> mModified;
+ private final @NonNull Set<ProgramSelector.Identifier> mRemoved;
+
+ public Chunk(boolean purge, boolean complete,
+ @Nullable Set<RadioManager.ProgramInfo> modified,
+ @Nullable Set<ProgramSelector.Identifier> removed) {
+ mPurge = purge;
+ mComplete = complete;
+ mModified = (modified != null) ? modified : Collections.emptySet();
+ mRemoved = (removed != null) ? removed : Collections.emptySet();
+ }
+
+ private Chunk(@NonNull Parcel in) {
+ mPurge = in.readByte() != 0;
+ mComplete = in.readByte() != 0;
+ mModified = Utils.createSet(in, RadioManager.ProgramInfo.CREATOR);
+ mRemoved = Utils.createSet(in, ProgramSelector.Identifier.CREATOR);
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeByte((byte) (mPurge ? 1 : 0));
+ dest.writeByte((byte) (mComplete ? 1 : 0));
+ Utils.writeSet(dest, mModified);
+ Utils.writeSet(dest, mRemoved);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ public static final Parcelable.Creator<Chunk> CREATOR = new Parcelable.Creator<Chunk>() {
+ public Chunk createFromParcel(Parcel in) {
+ return new Chunk(in);
+ }
+
+ public Chunk[] newArray(int size) {
+ return new Chunk[size];
+ }
+ };
+
+ public boolean isPurge() {
+ return mPurge;
+ }
+
+ public boolean isComplete() {
+ return mComplete;
+ }
+
+ public @NonNull Set<RadioManager.ProgramInfo> getModified() {
+ return mModified;
+ }
+
+ public @NonNull Set<ProgramSelector.Identifier> getRemoved() {
+ return mRemoved;
+ }
+ }
+}
diff --git a/core/java/android/hardware/radio/ProgramSelector.java b/core/java/android/hardware/radio/ProgramSelector.java
index 2211cee..3556751 100644
--- a/core/java/android/hardware/radio/ProgramSelector.java
+++ b/core/java/android/hardware/radio/ProgramSelector.java
@@ -59,6 +59,7 @@
*/
@SystemApi
public final class ProgramSelector implements Parcelable {
+ public static final int PROGRAM_TYPE_INVALID = 0;
/** Analogue AM radio (with or without RDS). */
public static final int PROGRAM_TYPE_AM = 1;
/** analogue FM radio (with or without RDS). */
@@ -77,6 +78,7 @@
public static final int PROGRAM_TYPE_VENDOR_START = 1000;
public static final int PROGRAM_TYPE_VENDOR_END = 1999;
@IntDef(prefix = { "PROGRAM_TYPE_" }, value = {
+ PROGRAM_TYPE_INVALID,
PROGRAM_TYPE_AM,
PROGRAM_TYPE_FM,
PROGRAM_TYPE_AM_HD,
@@ -89,6 +91,7 @@
@Retention(RetentionPolicy.SOURCE)
public @interface ProgramType {}
+ public static final int IDENTIFIER_TYPE_INVALID = 0;
/** kHz */
public static final int IDENTIFIER_TYPE_AMFM_FREQUENCY = 1;
/** 16bit */
@@ -148,6 +151,7 @@
public static final int IDENTIFIER_TYPE_VENDOR_PRIMARY_START = PROGRAM_TYPE_VENDOR_START;
public static final int IDENTIFIER_TYPE_VENDOR_PRIMARY_END = PROGRAM_TYPE_VENDOR_END;
@IntDef(prefix = { "IDENTIFIER_TYPE_" }, value = {
+ IDENTIFIER_TYPE_INVALID,
IDENTIFIER_TYPE_AMFM_FREQUENCY,
IDENTIFIER_TYPE_RDS_PI,
IDENTIFIER_TYPE_HD_STATION_ID_EXT,
@@ -268,7 +272,7 @@
* Vendor identifiers are passed as-is to the HAL implementation,
* preserving elements order.
*
- * @return a array of vendor identifiers, must not be modified.
+ * @return an array of vendor identifiers, must not be modified.
*/
public @NonNull long[] getVendorIds() {
return mVendorIds;
diff --git a/core/java/android/hardware/radio/RadioManager.java b/core/java/android/hardware/radio/RadioManager.java
index b740f14..56668ac 100644
--- a/core/java/android/hardware/radio/RadioManager.java
+++ b/core/java/android/hardware/radio/RadioManager.java
@@ -185,25 +185,6 @@
@Retention(RetentionPolicy.SOURCE)
public @interface ConfigFlag {}
- private static void writeStringMap(@NonNull Parcel dest, @NonNull Map<String, String> map) {
- dest.writeInt(map.size());
- for (Map.Entry<String, String> entry : map.entrySet()) {
- dest.writeString(entry.getKey());
- dest.writeString(entry.getValue());
- }
- }
-
- private static @NonNull Map<String, String> readStringMap(@NonNull Parcel in) {
- int size = in.readInt();
- Map<String, String> map = new HashMap<>();
- while (size-- > 0) {
- String key = in.readString();
- String value = in.readString();
- map.put(key, value);
- }
- return map;
- }
-
/*****************************************************************************
* Lists properties, options and radio bands supported by a given broadcast radio module.
* Each module has a unique ID used to address it when calling RadioManager APIs.
@@ -415,7 +396,7 @@
mIsBgScanSupported = in.readInt() == 1;
mSupportedProgramTypes = arrayToSet(in.createIntArray());
mSupportedIdentifierTypes = arrayToSet(in.createIntArray());
- mVendorInfo = readStringMap(in);
+ mVendorInfo = Utils.readStringMap(in);
}
public static final Parcelable.Creator<ModuleProperties> CREATOR
@@ -445,7 +426,7 @@
dest.writeInt(mIsBgScanSupported ? 1 : 0);
dest.writeIntArray(setToArray(mSupportedProgramTypes));
dest.writeIntArray(setToArray(mSupportedIdentifierTypes));
- writeStringMap(dest, mVendorInfo);
+ Utils.writeStringMap(dest, mVendorInfo);
}
@Override
@@ -1410,7 +1391,7 @@
private static final int FLAG_TRAFFIC_ANNOUNCEMENT = 1 << 3;
@NonNull private final ProgramSelector mSelector;
- private final boolean mTuned;
+ private final boolean mTuned; // TODO(b/69958777): replace with mFlags
private final boolean mStereo;
private final boolean mDigital;
private final int mFlags;
@@ -1418,7 +1399,8 @@
private final RadioMetadata mMetadata;
@NonNull private final Map<String, String> mVendorInfo;
- ProgramInfo(@NonNull ProgramSelector selector, boolean tuned, boolean stereo,
+ /** @hide */
+ public ProgramInfo(@NonNull ProgramSelector selector, boolean tuned, boolean stereo,
boolean digital, int signalStrength, RadioMetadata metadata, int flags,
Map<String, String> vendorInfo) {
mSelector = selector;
@@ -1564,7 +1546,7 @@
mMetadata = null;
}
mFlags = in.readInt();
- mVendorInfo = readStringMap(in);
+ mVendorInfo = Utils.readStringMap(in);
}
public static final Parcelable.Creator<ProgramInfo> CREATOR
@@ -1592,7 +1574,7 @@
mMetadata.writeToParcel(dest, flags);
}
dest.writeInt(mFlags);
- writeStringMap(dest, mVendorInfo);
+ Utils.writeStringMap(dest, mVendorInfo);
}
@Override
@@ -1727,7 +1709,8 @@
Log.e(TAG, "Failed to open tuner");
return null;
}
- return new TunerAdapter(tuner, config != null ? config.getType() : BAND_INVALID);
+ return new TunerAdapter(tuner, halCallback,
+ config != null ? config.getType() : BAND_INVALID);
}
@NonNull private final Context mContext;
diff --git a/core/java/android/hardware/radio/RadioTuner.java b/core/java/android/hardware/radio/RadioTuner.java
index 0d367e7..ed20c4a 100644
--- a/core/java/android/hardware/radio/RadioTuner.java
+++ b/core/java/android/hardware/radio/RadioTuner.java
@@ -280,11 +280,29 @@
* @throws IllegalStateException if the scan is in progress or has not been started,
* startBackgroundScan() call may fix it.
* @throws IllegalArgumentException if the vendorFilter argument is not valid.
+ * @deprecated Use {@link getDynamicProgramList} instead.
*/
+ @Deprecated
public abstract @NonNull List<RadioManager.ProgramInfo>
getProgramList(@Nullable Map<String, String> vendorFilter);
/**
+ * Get the dynamic list of discovered radio stations.
+ *
+ * The list object is updated asynchronously; to get the updates register
+ * with {@link ProgramList#addListCallback}.
+ *
+ * When the returned object is no longer used, it must be closed.
+ *
+ * @param filter filter for the list, or null to get the full list.
+ * @return the dynamic program list object, close it after use
+ * or {@code null} if program list is not supported by the tuner
+ */
+ public @Nullable ProgramList getDynamicProgramList(@Nullable ProgramList.Filter filter) {
+ return null;
+ }
+
+ /**
* Checks, if the analog playback is forced, see setAnalogForced.
*
* @throws IllegalStateException if the switch is not supported at current
diff --git a/core/java/android/hardware/radio/TunerAdapter.java b/core/java/android/hardware/radio/TunerAdapter.java
index 8ad609d..91944bf 100644
--- a/core/java/android/hardware/radio/TunerAdapter.java
+++ b/core/java/android/hardware/radio/TunerAdapter.java
@@ -33,15 +33,18 @@
private static final String TAG = "BroadcastRadio.TunerAdapter";
@NonNull private final ITuner mTuner;
+ @NonNull private final TunerCallbackAdapter mCallback;
private boolean mIsClosed = false;
private @RadioManager.Band int mBand;
- TunerAdapter(ITuner tuner, @RadioManager.Band int band) {
- if (tuner == null) {
- throw new NullPointerException();
- }
- mTuner = tuner;
+ private ProgramList mLegacyListProxy;
+ private Map<String, String> mLegacyListFilter;
+
+ TunerAdapter(@NonNull ITuner tuner, @NonNull TunerCallbackAdapter callback,
+ @RadioManager.Band int band) {
+ mTuner = Objects.requireNonNull(tuner);
+ mCallback = Objects.requireNonNull(callback);
mBand = band;
}
@@ -53,6 +56,10 @@
return;
}
mIsClosed = true;
+ if (mLegacyListProxy != null) {
+ mLegacyListProxy.close();
+ mLegacyListProxy = null;
+ }
}
try {
mTuner.close();
@@ -227,10 +234,55 @@
@Override
public @NonNull List<RadioManager.ProgramInfo>
getProgramList(@Nullable Map<String, String> vendorFilter) {
- try {
- return mTuner.getProgramList(vendorFilter);
- } catch (RemoteException e) {
- throw new RuntimeException("service died", e);
+ synchronized (mTuner) {
+ if (mLegacyListProxy == null || !Objects.equals(mLegacyListFilter, vendorFilter)) {
+ Log.i(TAG, "Program list filter has changed, requesting new list");
+ mLegacyListProxy = new ProgramList();
+ mLegacyListFilter = vendorFilter;
+
+ mCallback.clearLastCompleteList();
+ mCallback.setProgramListObserver(mLegacyListProxy, () -> { });
+ try {
+ mTuner.startProgramListUpdates(new ProgramList.Filter(vendorFilter));
+ } catch (RemoteException ex) {
+ throw new RuntimeException("service died", ex);
+ }
+ }
+
+ List<RadioManager.ProgramInfo> list = mCallback.getLastCompleteList();
+ if (list == null) throw new IllegalStateException("Program list is not ready yet");
+ return list;
+ }
+ }
+
+ @Override
+ public @Nullable ProgramList getDynamicProgramList(@Nullable ProgramList.Filter filter) {
+ synchronized (mTuner) {
+ if (mLegacyListProxy != null) {
+ mLegacyListProxy.close();
+ mLegacyListProxy = null;
+ }
+ mLegacyListFilter = null;
+
+ ProgramList list = new ProgramList();
+ mCallback.setProgramListObserver(list, () -> {
+ try {
+ mTuner.stopProgramListUpdates();
+ } catch (RemoteException ex) {
+ Log.e(TAG, "Couldn't stop program list updates", ex);
+ }
+ });
+
+ try {
+ mTuner.startProgramListUpdates(filter);
+ } catch (UnsupportedOperationException ex) {
+ return null;
+ } catch (RemoteException ex) {
+ mCallback.setProgramListObserver(null, () -> { });
+ throw new RuntimeException("service died", ex);
+ }
+
+ return list;
}
}
diff --git a/core/java/android/hardware/radio/TunerCallbackAdapter.java b/core/java/android/hardware/radio/TunerCallbackAdapter.java
index a01f658..b299ffe 100644
--- a/core/java/android/hardware/radio/TunerCallbackAdapter.java
+++ b/core/java/android/hardware/radio/TunerCallbackAdapter.java
@@ -22,7 +22,9 @@
import android.os.Looper;
import android.util.Log;
+import java.util.List;
import java.util.Map;
+import java.util.Objects;
/**
* Implements the ITunerCallback interface by forwarding calls to RadioTuner.Callback.
@@ -30,9 +32,14 @@
class TunerCallbackAdapter extends ITunerCallback.Stub {
private static final String TAG = "BroadcastRadio.TunerCallbackAdapter";
+ private final Object mLock = new Object();
@NonNull private final RadioTuner.Callback mCallback;
@NonNull private final Handler mHandler;
+ @Nullable ProgramList mProgramList;
+ @Nullable List<RadioManager.ProgramInfo> mLastCompleteList; // for legacy getProgramList call
+ private boolean mDelayedCompleteCallback = false;
+
TunerCallbackAdapter(@NonNull RadioTuner.Callback callback, @Nullable Handler handler) {
mCallback = callback;
if (handler == null) {
@@ -42,6 +49,49 @@
}
}
+ void setProgramListObserver(@Nullable ProgramList programList,
+ @NonNull ProgramList.OnCloseListener closeListener) {
+ Objects.requireNonNull(closeListener);
+ synchronized (mLock) {
+ if (mProgramList != null) {
+ Log.w(TAG, "Previous program list observer wasn't properly closed, closing it...");
+ mProgramList.close();
+ }
+ mProgramList = programList;
+ if (programList == null) return;
+ programList.setOnCloseListener(() -> {
+ synchronized (mLock) {
+ if (mProgramList != programList) return;
+ mProgramList = null;
+ mLastCompleteList = null;
+ closeListener.onClose();
+ }
+ });
+ programList.addOnCompleteListener(() -> {
+ synchronized (mLock) {
+ if (mProgramList != programList) return;
+ mLastCompleteList = programList.toList();
+ if (mDelayedCompleteCallback) {
+ Log.d(TAG, "Sending delayed onBackgroundScanComplete callback");
+ sendBackgroundScanCompleteLocked();
+ }
+ }
+ });
+ }
+ }
+
+ @Nullable List<RadioManager.ProgramInfo> getLastCompleteList() {
+ synchronized (mLock) {
+ return mLastCompleteList;
+ }
+ }
+
+ void clearLastCompleteList() {
+ synchronized (mLock) {
+ mLastCompleteList = null;
+ }
+ }
+
@Override
public void onError(int status) {
mHandler.post(() -> mCallback.onError(status));
@@ -87,9 +137,22 @@
mHandler.post(() -> mCallback.onBackgroundScanAvailabilityChange(isAvailable));
}
+ private void sendBackgroundScanCompleteLocked() {
+ mDelayedCompleteCallback = false;
+ mHandler.post(() -> mCallback.onBackgroundScanComplete());
+ }
+
@Override
public void onBackgroundScanComplete() {
- mHandler.post(() -> mCallback.onBackgroundScanComplete());
+ synchronized (mLock) {
+ if (mLastCompleteList == null) {
+ Log.i(TAG, "Got onBackgroundScanComplete callback, but the "
+ + "program list didn't get through yet. Delaying it...");
+ mDelayedCompleteCallback = true;
+ return;
+ }
+ sendBackgroundScanCompleteLocked();
+ }
}
@Override
@@ -98,6 +161,14 @@
}
@Override
+ public void onProgramListUpdated(ProgramList.Chunk chunk) {
+ synchronized (mLock) {
+ if (mProgramList == null) return;
+ mProgramList.apply(Objects.requireNonNull(chunk));
+ }
+ }
+
+ @Override
public void onParametersUpdated(Map parameters) {
mHandler.post(() -> mCallback.onParametersUpdated(parameters));
}
diff --git a/core/java/android/hardware/radio/Utils.java b/core/java/android/hardware/radio/Utils.java
new file mode 100644
index 0000000..09bf8fe
--- /dev/null
+++ b/core/java/android/hardware/radio/Utils.java
@@ -0,0 +1,92 @@
+/**
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.hardware.radio;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+
+final class Utils {
+ static void writeStringMap(@NonNull Parcel dest, @Nullable Map<String, String> map) {
+ if (map == null) {
+ dest.writeInt(0);
+ return;
+ }
+ dest.writeInt(map.size());
+ for (Map.Entry<String, String> entry : map.entrySet()) {
+ dest.writeString(entry.getKey());
+ dest.writeString(entry.getValue());
+ }
+ }
+
+ static @NonNull Map<String, String> readStringMap(@NonNull Parcel in) {
+ int size = in.readInt();
+ Map<String, String> map = new HashMap<>();
+ while (size-- > 0) {
+ String key = in.readString();
+ String value = in.readString();
+ map.put(key, value);
+ }
+ return map;
+ }
+
+ static <T extends Parcelable> void writeSet(@NonNull Parcel dest, @Nullable Set<T> set) {
+ if (set == null) {
+ dest.writeInt(0);
+ return;
+ }
+ dest.writeInt(set.size());
+ set.stream().forEach(elem -> dest.writeTypedObject(elem, 0));
+ }
+
+ static <T> Set<T> createSet(@NonNull Parcel in, Parcelable.Creator<T> c) {
+ int size = in.readInt();
+ Set<T> set = new HashSet<>();
+ while (size-- > 0) {
+ set.add(in.readTypedObject(c));
+ }
+ return set;
+ }
+
+ static void writeIntSet(@NonNull Parcel dest, @Nullable Set<Integer> set) {
+ if (set == null) {
+ dest.writeInt(0);
+ return;
+ }
+ dest.writeInt(set.size());
+ set.stream().forEach(elem -> dest.writeInt(Objects.requireNonNull(elem)));
+ }
+
+ static Set<Integer> createIntSet(@NonNull Parcel in) {
+ return createSet(in, new Parcelable.Creator<Integer>() {
+ public Integer createFromParcel(Parcel in) {
+ return in.readInt();
+ }
+
+ public Integer[] newArray(int size) {
+ return new Integer[size];
+ }
+ });
+ }
+}
diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java
index 8937490..7528bc3 100644
--- a/core/java/android/inputmethodservice/InputMethodService.java
+++ b/core/java/android/inputmethodservice/InputMethodService.java
@@ -340,42 +340,35 @@
final Insets mTmpInsets = new Insets();
final int[] mTmpLocation = new int[2];
- final ViewTreeObserver.OnComputeInternalInsetsListener mInsetsComputer =
- new ViewTreeObserver.OnComputeInternalInsetsListener() {
- public void onComputeInternalInsets(ViewTreeObserver.InternalInsetsInfo info) {
- if (isExtractViewShown()) {
- // In true fullscreen mode, we just say the window isn't covering
- // any content so we don't impact whatever is behind.
- View decor = getWindow().getWindow().getDecorView();
- info.contentInsets.top = info.visibleInsets.top
- = decor.getHeight();
- info.touchableRegion.setEmpty();
- info.setTouchableInsets(ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_FRAME);
- } else {
- onComputeInsets(mTmpInsets);
- info.contentInsets.top = mTmpInsets.contentTopInsets;
- info.visibleInsets.top = mTmpInsets.visibleTopInsets;
- info.touchableRegion.set(mTmpInsets.touchableRegion);
- info.setTouchableInsets(mTmpInsets.touchableInsets);
+ final ViewTreeObserver.OnComputeInternalInsetsListener mInsetsComputer = info -> {
+ if (isExtractViewShown()) {
+ // In true fullscreen mode, we just say the window isn't covering
+ // any content so we don't impact whatever is behind.
+ View decor = getWindow().getWindow().getDecorView();
+ info.contentInsets.top = info.visibleInsets.top = decor.getHeight();
+ info.touchableRegion.setEmpty();
+ info.setTouchableInsets(ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_FRAME);
+ } else {
+ onComputeInsets(mTmpInsets);
+ info.contentInsets.top = mTmpInsets.contentTopInsets;
+ info.visibleInsets.top = mTmpInsets.visibleTopInsets;
+ info.touchableRegion.set(mTmpInsets.touchableRegion);
+ info.setTouchableInsets(mTmpInsets.touchableInsets);
+ }
+ };
+
+ final View.OnClickListener mActionClickListener = v -> {
+ final EditorInfo ei = getCurrentInputEditorInfo();
+ final InputConnection ic = getCurrentInputConnection();
+ if (ei != null && ic != null) {
+ if (ei.actionId != 0) {
+ ic.performEditorAction(ei.actionId);
+ } else if ((ei.imeOptions & EditorInfo.IME_MASK_ACTION) != EditorInfo.IME_ACTION_NONE) {
+ ic.performEditorAction(ei.imeOptions & EditorInfo.IME_MASK_ACTION);
}
}
};
- final View.OnClickListener mActionClickListener = new View.OnClickListener() {
- public void onClick(View v) {
- final EditorInfo ei = getCurrentInputEditorInfo();
- final InputConnection ic = getCurrentInputConnection();
- if (ei != null && ic != null) {
- if (ei.actionId != 0) {
- ic.performEditorAction(ei.actionId);
- } else if ((ei.imeOptions&EditorInfo.IME_MASK_ACTION)
- != EditorInfo.IME_ACTION_NONE) {
- ic.performEditorAction(ei.imeOptions&EditorInfo.IME_MASK_ACTION);
- }
- }
- }
- };
-
/**
* Concrete implementation of
* {@link AbstractInputMethodService.AbstractInputMethodImpl} that provides
@@ -896,20 +889,20 @@
mWindow.getWindow().setWindowAnimations(
com.android.internal.R.style.Animation_InputMethodFancy);
}
- mFullscreenArea = (ViewGroup)mRootView.findViewById(com.android.internal.R.id.fullscreenArea);
+ mFullscreenArea = mRootView.findViewById(com.android.internal.R.id.fullscreenArea);
mExtractViewHidden = false;
- mExtractFrame = (FrameLayout)mRootView.findViewById(android.R.id.extractArea);
+ mExtractFrame = mRootView.findViewById(android.R.id.extractArea);
mExtractView = null;
mExtractEditText = null;
mExtractAccessories = null;
mExtractAction = null;
mFullscreenApplied = false;
-
- mCandidatesFrame = (FrameLayout)mRootView.findViewById(android.R.id.candidatesArea);
- mInputFrame = (FrameLayout)mRootView.findViewById(android.R.id.inputArea);
+
+ mCandidatesFrame = mRootView.findViewById(android.R.id.candidatesArea);
+ mInputFrame = mRootView.findViewById(android.R.id.inputArea);
mInputView = null;
mIsInputViewShown = false;
-
+
mExtractFrame.setVisibility(View.GONE);
mCandidatesVisibility = getCandidatesHiddenVisibility();
mCandidatesFrame.setVisibility(mCandidatesVisibility);
@@ -1089,33 +1082,6 @@
}
/**
- * Close/hide the input method's soft input area, so the user no longer
- * sees it or can interact with it. This can only be called
- * from the currently active input method, as validated by the given token.
- *
- * @param flags Provides additional operating flags. Currently may be
- * 0 or have the {@link InputMethodManager#HIDE_IMPLICIT_ONLY},
- * {@link InputMethodManager#HIDE_NOT_ALWAYS} bit set.
- */
- public void hideSoftInputFromInputMethod(int flags) {
- mImm.hideSoftInputFromInputMethodInternal(mToken, flags);
- }
-
- /**
- * Show the input method's soft input area, so the user
- * sees the input method window and can interact with it.
- * This can only be called from the currently active input method,
- * as validated by the given token.
- *
- * @param flags Provides additional operating flags. Currently may be
- * 0 or have the {@link InputMethodManager#SHOW_IMPLICIT} or
- * {@link InputMethodManager#SHOW_FORCED} bit set.
- */
- public void showSoftInputFromInputMethod(int flags) {
- mImm.showSoftInputFromInputMethodInternal(mToken, flags);
- }
-
- /**
* Force switch to the last used input method and subtype. If the last input method didn't have
* any subtypes, the framework will simply switch to the last input method with no subtype
* specified.
@@ -1461,17 +1427,17 @@
public int getCandidatesHiddenVisibility() {
return isExtractViewShown() ? View.GONE : View.INVISIBLE;
}
-
+
public void showStatusIcon(@DrawableRes int iconResId) {
mStatusIcon = iconResId;
- mImm.showStatusIcon(mToken, getPackageName(), iconResId);
+ mImm.showStatusIconInternal(mToken, getPackageName(), iconResId);
}
-
+
public void hideStatusIcon() {
mStatusIcon = 0;
- mImm.hideStatusIcon(mToken);
+ mImm.hideStatusIconInternal(mToken);
}
-
+
/**
* Force switch to a new input method, as identified by <var>id</var>. This
* input method will be destroyed, and the requested one started on the
@@ -1480,9 +1446,9 @@
* @param id Unique identifier of the new input method ot start.
*/
public void switchInputMethod(String id) {
- mImm.setInputMethod(mToken, id);
+ mImm.setInputMethodInternal(mToken, id);
}
-
+
public void setExtractView(View view) {
mExtractFrame.removeAllViews();
mExtractFrame.addView(view, new FrameLayout.LayoutParams(
@@ -1490,13 +1456,13 @@
ViewGroup.LayoutParams.MATCH_PARENT));
mExtractView = view;
if (view != null) {
- mExtractEditText = (ExtractEditText)view.findViewById(
+ mExtractEditText = view.findViewById(
com.android.internal.R.id.inputExtractEditText);
mExtractEditText.setIME(this);
mExtractAction = view.findViewById(
com.android.internal.R.id.inputExtractAction);
if (mExtractAction != null) {
- mExtractAccessories = (ViewGroup)view.findViewById(
+ mExtractAccessories = view.findViewById(
com.android.internal.R.id.inputExtractAccessories);
}
startExtractingText(false);
@@ -1745,7 +1711,7 @@
// Rethrow the exception to preserve the existing behavior. Some IMEs may have directly
// called this method and relied on this exception for some clean-up tasks.
// TODO: Give developers a clear guideline of whether it's OK to call this method or
- // InputMethodManager#showSoftInputFromInputMethod() should always be used instead.
+ // InputMethodService#requestShowSelf(int) should always be used instead.
throw e;
} finally {
// TODO: Is it OK to set true when we get BadTokenException?
@@ -2067,27 +2033,30 @@
/**
* Close this input method's soft input area, removing it from the display.
- * The input method will continue running, but the user can no longer use
- * it to generate input by touching the screen.
- * @param flags Provides additional operating flags. Currently may be
- * 0 or have the {@link InputMethodManager#HIDE_IMPLICIT_ONLY
- * InputMethodManager.HIDE_IMPLICIT_ONLY} bit set.
+ *
+ * The input method will continue running, but the user can no longer use it to generate input
+ * by touching the screen.
+ *
+ * @see InputMethodManager#HIDE_IMPLICIT_ONLY
+ * @see InputMethodManager#HIDE_NOT_ALWAYS
+ * @param flags Provides additional operating flags.
*/
public void requestHideSelf(int flags) {
- mImm.hideSoftInputFromInputMethod(mToken, flags);
+ mImm.hideSoftInputFromInputMethodInternal(mToken, flags);
}
-
+
/**
- * Show the input method. This is a call back to the
- * IMF to handle showing the input method.
- * @param flags Provides additional operating flags. Currently may be
- * 0 or have the {@link InputMethodManager#SHOW_FORCED
- * InputMethodManager.} bit set.
+ * Show the input method's soft input area, so the user sees the input method window and can
+ * interact with it.
+ *
+ * @see InputMethodManager#SHOW_IMPLICIT
+ * @see InputMethodManager#SHOW_FORCED
+ * @param flags Provides additional operating flags.
*/
- private void requestShowSelf(int flags) {
- mImm.showSoftInputFromInputMethod(mToken, flags);
+ public void requestShowSelf(int flags) {
+ mImm.showSoftInputFromInputMethodInternal(mToken, flags);
}
-
+
private boolean handleBack(boolean doIt) {
if (mShowInputRequested) {
// If the soft input area is shown, back closes it and we
@@ -2754,7 +2723,7 @@
* application.
* This cannot be {@code null}.
* @param inputConnection {@link InputConnection} with which
- * {@link InputConnection#commitContent(InputContentInfo, Bundle)} will be called.
+ * {@link InputConnection#commitContent(InputContentInfo, int, Bundle)} will be called.
* @hide
*/
@Override
diff --git a/core/java/android/net/MacAddress.java b/core/java/android/net/MacAddress.java
index d6992aa..287bdc8 100644
--- a/core/java/android/net/MacAddress.java
+++ b/core/java/android/net/MacAddress.java
@@ -17,6 +17,7 @@
package android.net;
import android.annotation.IntDef;
+import android.annotation.NonNull;
import android.os.Parcel;
import android.os.Parcelable;
@@ -60,7 +61,7 @@
})
public @interface MacAddressType { }
- /** Indicates a MAC address of unknown type. */
+ /** @hide Indicates a MAC address of unknown type. */
public static final int TYPE_UNKNOWN = 0;
/** Indicates a MAC address is a unicast address. */
public static final int TYPE_UNICAST = 1;
@@ -92,7 +93,7 @@
*
* @return the int constant representing the MAC address type of this MacAddress.
*/
- public @MacAddressType int addressType() {
+ public @MacAddressType int getAddressType() {
if (equals(BROADCAST_ADDRESS)) {
return TYPE_BROADCAST;
}
@@ -120,12 +121,12 @@
/**
* @return a byte array representation of this MacAddress.
*/
- public byte[] toByteArray() {
+ public @NonNull byte[] toByteArray() {
return byteAddrFromLongAddr(mAddr);
}
@Override
- public String toString() {
+ public @NonNull String toString() {
return stringAddrFromLongAddr(mAddr);
}
@@ -133,7 +134,7 @@
* @return a String representation of the OUI part of this MacAddress made of 3 hexadecimal
* numbers in [0,ff] joined by ':' characters.
*/
- public String toOuiString() {
+ public @NonNull String toOuiString() {
return String.format(
"%02x:%02x:%02x", (mAddr >> 40) & 0xff, (mAddr >> 32) & 0xff, (mAddr >> 24) & 0xff);
}
@@ -197,7 +198,7 @@
if (!isMacAddress(addr)) {
return TYPE_UNKNOWN;
}
- return MacAddress.fromBytes(addr).addressType();
+ return MacAddress.fromBytes(addr).getAddressType();
}
/**
@@ -211,7 +212,7 @@
*
* @hide
*/
- public static byte[] byteAddrFromStringAddr(String addr) {
+ public static @NonNull byte[] byteAddrFromStringAddr(String addr) {
Preconditions.checkNotNull(addr);
String[] parts = addr.split(":");
if (parts.length != ETHER_ADDR_LEN) {
@@ -239,7 +240,7 @@
*
* @hide
*/
- public static String stringAddrFromByteAddr(byte[] addr) {
+ public static @NonNull String stringAddrFromByteAddr(byte[] addr) {
if (!isMacAddress(addr)) {
return null;
}
@@ -291,7 +292,7 @@
// Internal conversion function equivalent to stringAddrFromByteAddr(byteAddrFromLongAddr(addr))
// that avoids the allocation of an intermediary byte[].
- private static String stringAddrFromLongAddr(long addr) {
+ private static @NonNull String stringAddrFromLongAddr(long addr) {
return String.format("%02x:%02x:%02x:%02x:%02x:%02x",
(addr >> 40) & 0xff,
(addr >> 32) & 0xff,
@@ -310,7 +311,7 @@
* @return the MacAddress corresponding to the given String representation.
* @throws IllegalArgumentException if the given String is not a valid representation.
*/
- public static MacAddress fromString(String addr) {
+ public static @NonNull MacAddress fromString(@NonNull String addr) {
return new MacAddress(longAddrFromStringAddr(addr));
}
@@ -322,7 +323,7 @@
* @return the MacAddress corresponding to the given byte array representation.
* @throws IllegalArgumentException if the given byte array is not a valid representation.
*/
- public static MacAddress fromBytes(byte[] addr) {
+ public static @NonNull MacAddress fromBytes(@NonNull byte[] addr) {
return new MacAddress(longAddrFromByteAddr(addr));
}
@@ -336,7 +337,7 @@
*
* @hide
*/
- public static MacAddress createRandomUnicastAddress() {
+ public static @NonNull MacAddress createRandomUnicastAddress() {
return createRandomUnicastAddress(BASE_GOOGLE_MAC, new Random());
}
@@ -352,7 +353,7 @@
*
* @hide
*/
- public static MacAddress createRandomUnicastAddress(MacAddress base, Random r) {
+ public static @NonNull MacAddress createRandomUnicastAddress(MacAddress base, Random r) {
long addr = (base.mAddr & OUI_MASK) | (NIC_MASK & r.nextLong());
addr = addr | LOCALLY_ASSIGNED_MASK;
addr = addr & ~MULTICAST_MASK;
diff --git a/core/java/android/net/Network.java b/core/java/android/net/Network.java
index 1a3ce91..5df168d 100644
--- a/core/java/android/net/Network.java
+++ b/core/java/android/net/Network.java
@@ -357,13 +357,13 @@
// Multiple Provisioning Domains API recommendations, as made by the
// IETF mif working group.
//
- // The HANDLE_MAGIC value MUST be kept in sync with the corresponding
+ // The handleMagic value MUST be kept in sync with the corresponding
// value in the native/android/net.c NDK implementation.
if (netId == 0) {
return 0L; // make this zero condition obvious for debugging
}
- final long HANDLE_MAGIC = 0xfacade;
- return (((long) netId) << 32) | HANDLE_MAGIC;
+ final long handleMagic = 0xcafed00dL;
+ return (((long) netId) << 32) | handleMagic;
}
// implement the Parcelable interface
diff --git a/core/java/android/net/metrics/WakeupStats.java b/core/java/android/net/metrics/WakeupStats.java
index 7277ba3..bb36536 100644
--- a/core/java/android/net/metrics/WakeupStats.java
+++ b/core/java/android/net/metrics/WakeupStats.java
@@ -80,7 +80,7 @@
break;
}
- switch (ev.dstHwAddr.addressType()) {
+ switch (ev.dstHwAddr.getAddressType()) {
case MacAddress.TYPE_UNICAST:
l2UnicastCount++;
break;
diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java
index 77e4808..dc271d8 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -235,8 +235,11 @@
* New in version 29:
* - Process states re-ordered. TOP_SLEEPING now below BACKGROUND. HEAVY_WEIGHT introduced.
* - CPU times per UID process state
+ * New in version 30:
+ * - Uid.PROCESS_STATE_FOREGROUND_SERVICE only tracks
+ * ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE.
*/
- static final int CHECKIN_VERSION = 29;
+ static final int CHECKIN_VERSION = 30;
/**
* Old version, we hit 9 and ran out of room, need to remove.
@@ -694,17 +697,17 @@
// total time a uid has had any processes running at all.
/**
- * Time this uid has any processes in the top state (or above such as persistent).
+ * Time this uid has any processes in the top state.
*/
public static final int PROCESS_STATE_TOP = 0;
/**
- * Time this uid has any process with a started out bound foreground service, but
+ * Time this uid has any process with a started foreground service, but
* none in the "top" state.
*/
public static final int PROCESS_STATE_FOREGROUND_SERVICE = 1;
/**
* Time this uid has any process in an active foreground state, but none in the
- * "top sleeping" or better state.
+ * "foreground service" or better state. Persistent and other foreground states go here.
*/
public static final int PROCESS_STATE_FOREGROUND = 2;
/**
diff --git a/core/java/android/os/IUserManager.aidl b/core/java/android/os/IUserManager.aidl
index 01d6b02..65e9473 100644
--- a/core/java/android/os/IUserManager.aidl
+++ b/core/java/android/os/IUserManager.aidl
@@ -97,7 +97,7 @@
boolean isUserRunning(int userId);
boolean isUserNameSet(int userHandle);
boolean hasRestrictedProfiles();
- boolean trySetQuietModeEnabled(String callingPackage, boolean enableQuietMode, int userHandle, in IntentSender target);
+ boolean requestQuietModeEnabled(String callingPackage, boolean enableQuietMode, int userHandle, in IntentSender target);
long getUserStartRealtime();
long getUserUnlockRealtime();
}
diff --git a/core/java/android/os/PowerManager.java b/core/java/android/os/PowerManager.java
index cd6d41b..3798a5e 100644
--- a/core/java/android/os/PowerManager.java
+++ b/core/java/android/os/PowerManager.java
@@ -565,6 +565,42 @@
int OPTIONAL_SENSORS = 13;
}
+ /**
+ * Either the location providers shouldn't be affected by battery saver,
+ * or battery saver is off.
+ */
+ public static final int LOCATION_MODE_NO_CHANGE = 0;
+
+ /**
+ * In this mode, the GPS based location provider should be disabled when battery saver is on and
+ * the device is non-interactive.
+ */
+ public static final int LOCATION_MODE_GPS_DISABLED_WHEN_SCREEN_OFF = 1;
+
+ /**
+ * All location providers should be disabled when battery saver is on and
+ * the device is non-interactive.
+ */
+ public static final int LOCATION_MODE_ALL_DISABLED_WHEN_SCREEN_OFF = 2;
+
+ /**
+ * In this mode, all the location providers will be kept available, but location fixes
+ * should only be provided to foreground apps.
+ */
+ public static final int LOCATION_MODE_FOREGROUND_ONLY = 3;
+
+ /**
+ * @hide
+ */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = {"LOCATION_MODE_"}, value = {
+ LOCATION_MODE_NO_CHANGE,
+ LOCATION_MODE_GPS_DISABLED_WHEN_SCREEN_OFF,
+ LOCATION_MODE_ALL_DISABLED_WHEN_SCREEN_OFF,
+ LOCATION_MODE_FOREGROUND_ONLY,
+ })
+ public @interface LocationPowerSaveMode {}
+
final Context mContext;
final IPowerManager mService;
final Handler mHandler;
@@ -1143,6 +1179,24 @@
}
/**
+ * Returns how location features should behave when battery saver is on. When battery saver
+ * is off, this will always return {@link #LOCATION_MODE_NO_CHANGE}.
+ *
+ * <p>This API is normally only useful for components that provide location features.
+ *
+ * @see #isPowerSaveMode()
+ * @see #ACTION_POWER_SAVE_MODE_CHANGED
+ */
+ @LocationPowerSaveMode
+ public int getLocationPowerSaveMode() {
+ final PowerSaveState powerSaveState = getPowerSaveState(ServiceType.GPS);
+ if (!powerSaveState.globalBatterySaverEnabled) {
+ return LOCATION_MODE_NO_CHANGE;
+ }
+ return powerSaveState.gpsMode;
+ }
+
+ /**
* Returns true if the device is currently in idle mode. This happens when a device
* has been sitting unused and unmoving for a sufficiently long period of time, so that
* it decides to go into a lower power-use state. This may involve things like turning
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index 44238df..2197484 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -209,6 +209,35 @@
public static final String DISALLOW_AIRPLANE_MODE = "no_airplane_mode";
/**
+ * Specifies if a user is disallowed from configuring brightness. When device owner sets it,
+ * it'll only be applied on the target(system) user.
+ *
+ * <p>The default value is <code>false</code>.
+ *
+ * <p>This user restriction has no effect on managed profiles.
+ * <p>Key for user restrictions.
+ * <p>Type: Boolean
+ * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
+ * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
+ * @see #getUserRestrictions()
+ */
+ public static final String DISALLOW_CONFIG_BRIGHTNESS = "no_config_brightness";
+
+ /**
+ * Specifies if ambient display is disallowed for the user.
+ *
+ * <p>The default value is <code>false</code>.
+ *
+ * <p>This user restriction has no effect on managed profiles.
+ * <p>Key for user restrictions.
+ * <p>Type: Boolean
+ * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
+ * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
+ * @see #getUserRestrictions()
+ */
+ public static final String DISALLOW_AMBIENT_DISPLAY = "no_ambient_display";
+
+ /**
* Specifies if a user is disallowed from enabling the
* "Unknown Sources" setting, that allows installation of apps from unknown sources.
* The default value is <code>false</code>.
@@ -877,6 +906,27 @@
public static final String DISALLOW_USER_SWITCH = "no_user_switch";
/**
+ * Specifies whether the user can share file / picture / data from the primary user into the
+ * managed profile, either by sending them from the primary side, or by picking up data within
+ * an app in the managed profile.
+ * <p>
+ * When a managed profile is created, the system allows the user to send data from the primary
+ * side to the profile by setting up certain default cross profile intent filters. If
+ * this is undesired, this restriction can be set to disallow it. Note that this restriction
+ * will not block any sharing allowed by explicit
+ * {@link DevicePolicyManager#addCrossProfileIntentFilter} calls by the profile owner.
+ * <p>
+ * This restriction is only meaningful when set by profile owner. When it is set by device
+ * owner, it does not have any effect.
+ * <p>
+ * The default value is <code>false</code>.
+ *
+ * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
+ * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
+ * @see #getUserRestrictions()
+ */
+ public static final String DISALLOW_SHARE_INTO_MANAGED_PROFILE = "no_sharing_into_profile";
+ /**
* Application restriction key that is used to indicate the pending arrival
* of real restrictions for the app.
*
@@ -2194,6 +2244,12 @@
}
}
+ /** @removed */
+ @Deprecated
+ public boolean trySetQuietModeEnabled(boolean enableQuietMode, @NonNull UserHandle userHandle) {
+ return requestQuietModeEnabled(enableQuietMode, userHandle);
+ }
+
/**
* Enables or disables quiet mode for a managed profile. If quiet mode is enabled, apps in a
* managed profile don't run, generate notifications, or consume data or battery.
@@ -2219,22 +2275,22 @@
*
* @see #isQuietModeEnabled(UserHandle)
*/
- public boolean trySetQuietModeEnabled(boolean enableQuietMode, @NonNull UserHandle userHandle) {
- return trySetQuietModeEnabled(enableQuietMode, userHandle, null);
+ public boolean requestQuietModeEnabled(boolean enableQuietMode, @NonNull UserHandle userHandle) {
+ return requestQuietModeEnabled(enableQuietMode, userHandle, null);
}
/**
- * Similar to {@link #trySetQuietModeEnabled(boolean, UserHandle)}, except you can specify
+ * Similar to {@link #requestQuietModeEnabled(boolean, UserHandle)}, except you can specify
* a target to start when user is unlocked. If {@code target} is specified, caller must have
* the {@link android.Manifest.permission#MANAGE_USERS} permission.
*
- * @see {@link #trySetQuietModeEnabled(boolean, UserHandle)}
+ * @see {@link #requestQuietModeEnabled(boolean, UserHandle)}
* @hide
*/
- public boolean trySetQuietModeEnabled(
+ public boolean requestQuietModeEnabled(
boolean enableQuietMode, @NonNull UserHandle userHandle, IntentSender target) {
try {
- return mService.trySetQuietModeEnabled(
+ return mService.requestQuietModeEnabled(
mContext.getPackageName(), enableQuietMode, userHandle.getIdentifier(), target);
} catch (RemoteException re) {
throw re.rethrowFromSystemServer();
diff --git a/core/java/android/os/VintfObject.java b/core/java/android/os/VintfObject.java
index 340f3fb..12a495b 100644
--- a/core/java/android/os/VintfObject.java
+++ b/core/java/android/os/VintfObject.java
@@ -76,8 +76,8 @@
/**
* @return a list of VNDK snapshots supported by the framework, as
* specified in framework manifest. For example,
- * [("25.0.5", ["libjpeg.so", "libbase.so"]),
- * ("25.1.3", ["libjpeg.so", "libbase.so"])]
+ * [("27", ["libjpeg.so", "libbase.so"]),
+ * ("28", ["libjpeg.so", "libbase.so"])]
*/
public static native Map<String, String[]> getVndkSnapshots();
}
diff --git a/core/java/android/os/storage/StorageVolume.java b/core/java/android/os/storage/StorageVolume.java
index 070b8c1..839a8bf 100644
--- a/core/java/android/os/storage/StorageVolume.java
+++ b/core/java/android/os/storage/StorageVolume.java
@@ -394,4 +394,32 @@
parcel.writeString(mFsUuid);
parcel.writeString(mState);
}
+
+ /** {@hide} */
+ public static final class ScopedAccessProviderContract {
+
+ private ScopedAccessProviderContract() {
+ throw new UnsupportedOperationException("contains constants only");
+ }
+
+ public static final String AUTHORITY = "com.android.documentsui.scopedAccess";
+
+ public static final String TABLE_PACKAGES = "packages";
+ public static final String TABLE_PERMISSIONS = "permissions";
+
+ public static final String COL_PACKAGE = "package_name";
+ public static final String COL_VOLUME_UUID = "volume_uuid";
+ public static final String COL_DIRECTORY = "directory";
+ public static final String COL_GRANTED = "granted";
+
+ public static final String[] TABLE_PACKAGES_COLUMNS = new String[] { COL_PACKAGE };
+ public static final String[] TABLE_PERMISSIONS_COLUMNS =
+ new String[] { COL_PACKAGE, COL_VOLUME_UUID, COL_DIRECTORY, COL_GRANTED };
+
+ public static final int TABLE_PACKAGES_COL_PACKAGE = 0;
+ public static final int TABLE_PERMISSIONS_COL_PACKAGE = 0;
+ public static final int TABLE_PERMISSIONS_COL_VOLUME_UUID = 1;
+ public static final int TABLE_PERMISSIONS_COL_DIRECTORY = 2;
+ public static final int TABLE_PERMISSIONS_COL_GRANTED = 3;
+ }
}
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 66d1bba..850aedd 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -6802,6 +6802,37 @@
public static final String ASSIST_DISCLOSURE_ENABLED = "assist_disclosure_enabled";
/**
+ * Control if rotation suggestions are sent to System UI when in rotation locked mode.
+ * Done to enable screen rotation while the the screen rotation is locked. Enabling will
+ * poll the accelerometer in rotation locked mode.
+ *
+ * If 0, then rotation suggestions are not sent to System UI. If 1, suggestions are sent.
+ *
+ * @hide
+ */
+
+ public static final String SHOW_ROTATION_SUGGESTIONS = "show_rotation_suggestions";
+
+ /**
+ * The disabled state of SHOW_ROTATION_SUGGESTIONS.
+ * @hide
+ */
+ public static final int SHOW_ROTATION_SUGGESTIONS_DISABLED = 0x0;
+
+ /**
+ * The enabled state of SHOW_ROTATION_SUGGESTIONS.
+ * @hide
+ */
+ public static final int SHOW_ROTATION_SUGGESTIONS_ENABLED = 0x1;
+
+ /**
+ * The default state of SHOW_ROTATION_SUGGESTIONS.
+ * @hide
+ */
+ public static final int SHOW_ROTATION_SUGGESTIONS_DEFAULT =
+ SHOW_ROTATION_SUGGESTIONS_DISABLED;
+
+ /**
* Read only list of the service components that the current user has explicitly allowed to
* see and assist with all of the user's notifications.
*
@@ -9622,6 +9653,17 @@
public static final String ALWAYS_ON_DISPLAY_CONSTANTS = "always_on_display_constants";
/**
+ * System VDSO global setting. This links to the "sys.vdso" system property.
+ * The following values are supported:
+ * false -> both 32 and 64 bit vdso disabled
+ * 32 -> 32 bit vdso enabled
+ * 64 -> 64 bit vdso enabled
+ * Any other value defaults to both 32 bit and 64 bit true.
+ * @hide
+ */
+ public static final String SYS_VDSO = "sys_vdso";
+
+ /**
* App standby (app idle) specific settings.
* This is encoded as a key=value list, separated by commas. Ex:
* <p>
@@ -10027,6 +10069,7 @@
* If 1 low power mode is enabled.
* @hide
*/
+ @TestApi
public static final String LOW_POWER_MODE = "low_power";
/**
@@ -11248,6 +11291,15 @@
*/
public static final String ENABLE_GNSS_RAW_MEAS_FULL_TRACKING =
"enable_gnss_raw_meas_full_tracking";
+
+ /**
+ * Whether we've enabled zram on this device. Takes effect on
+ * reboot. The value "1" enables zram; "0" disables it, and
+ * everything else is unspecified.
+ * @hide
+ */
+ public static final String ZRAM_ENABLED =
+ "zram_enabled";
}
/**
diff --git a/core/java/android/provider/VoicemailContract.java b/core/java/android/provider/VoicemailContract.java
index 70de9ee..c568b6f 100644
--- a/core/java/android/provider/VoicemailContract.java
+++ b/core/java/android/provider/VoicemailContract.java
@@ -106,9 +106,12 @@
/**
* Broadcast intent to inform a new visual voicemail SMS has been received. This intent will
- * only be delivered to the telephony service. {@link #EXTRA_VOICEMAIL_SMS} will be included.
- */
- /** @hide */
+ * only be delivered to the telephony service.
+ *
+ * @see #EXTRA_VOICEMAIL_SMS
+ * @see #EXTRA_TARGET_PACKAGE
+ *
+ * @hide */
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
public static final String ACTION_VOICEMAIL_SMS_RECEIVED =
"com.android.internal.provider.action.VOICEMAIL_SMS_RECEIVED";
@@ -121,6 +124,19 @@
public static final String EXTRA_VOICEMAIL_SMS = "android.provider.extra.VOICEMAIL_SMS";
/**
+ * Extra in {@link #ACTION_VOICEMAIL_SMS_RECEIVED} indicating the target package to bind {@link
+ * android.telephony.VisualVoicemailService}.
+ *
+ * <p>This extra should be set to android.telephony.VisualVoicemailSmsFilterSettings#packageName
+ * while performing filtering. Since the default dialer might change between the filter sending
+ * it and telephony binding to the service, this ensures the service will not receive SMS
+ * filtered by the previous app.
+ *
+ * @hide
+ */
+ public static final String EXTRA_TARGET_PACKAGE = "android.provider.extra.TARGET_PACAKGE";
+
+ /**
* Extra included in {@link Intent#ACTION_PROVIDER_CHANGED} broadcast intents to indicate if the
* receiving package made this change.
*/
diff --git a/core/java/android/security/recoverablekeystore/KeyStoreRecoveryData.aidl b/core/java/android/security/keystore/EntryRecoveryData.aidl
similarity index 88%
copy from core/java/android/security/recoverablekeystore/KeyStoreRecoveryData.aidl
copy to core/java/android/security/keystore/EntryRecoveryData.aidl
index bd76051..c6c20e3 100644
--- a/core/java/android/security/recoverablekeystore/KeyStoreRecoveryData.aidl
+++ b/core/java/android/security/keystore/EntryRecoveryData.aidl
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package android.security.recoverablekeystore;
+package android.security.keystore;
/* @hide */
-parcelable KeyStoreRecoveryData;
+parcelable EntryRecoveryData;
diff --git a/core/java/android/security/keystore/EntryRecoveryData.java b/core/java/android/security/keystore/EntryRecoveryData.java
new file mode 100644
index 0000000..aaca3fe
--- /dev/null
+++ b/core/java/android/security/keystore/EntryRecoveryData.java
@@ -0,0 +1,144 @@
+/*
+ * 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.security.keystore;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.util.Preconditions;
+
+/**
+ * Helper class with data necessary recover a single application key, given a recovery key.
+ *
+ * <ul>
+ * <li>Alias - Keystore alias of the key.
+ * <li>Encrypted key material.
+ * </ul>
+ *
+ * Note that Application info is not included. Recovery Agent can only make its own keys
+ * recoverable.
+ *
+ * @hide
+ */
+public final class EntryRecoveryData implements Parcelable {
+ private String mAlias;
+ // The only supported format is AES-256 symmetric key.
+ private byte[] mEncryptedKeyMaterial;
+
+ /**
+ * Builder for creating {@link EntryRecoveryData}.
+ */
+ public static class Builder {
+ private EntryRecoveryData mInstance = new EntryRecoveryData();
+
+ /**
+ * Sets Application-specific alias of the key.
+ *
+ * @param alias The alias.
+ * @return This builder.
+ */
+ public Builder setAlias(@NonNull String alias) {
+ mInstance.mAlias = alias;
+ return this;
+ }
+
+ /**
+ * Sets key material encrypted by recovery key.
+ *
+ * @param encryptedKeyMaterial The key material
+ * @return This builder
+ */
+
+ public Builder setEncryptedKeyMaterial(@NonNull byte[] encryptedKeyMaterial) {
+ mInstance.mEncryptedKeyMaterial = encryptedKeyMaterial;
+ return this;
+ }
+
+ /**
+ * Creates a new {@link EntryRecoveryData} instance.
+ *
+ * @return new instance
+ * @throws NullPointerException if some required fields were not set.
+ */
+ public @NonNull EntryRecoveryData build() {
+ Preconditions.checkNotNull(mInstance.mAlias);
+ Preconditions.checkNotNull(mInstance.mEncryptedKeyMaterial);
+ return mInstance;
+ }
+ }
+
+ private EntryRecoveryData() {
+
+ }
+
+ /**
+ * Deprecated - consider using Builder.
+ * @hide
+ */
+ public EntryRecoveryData(@NonNull String alias, @NonNull byte[] encryptedKeyMaterial) {
+ mAlias = Preconditions.checkNotNull(alias);
+ mEncryptedKeyMaterial = Preconditions.checkNotNull(encryptedKeyMaterial);
+ }
+
+ /**
+ * Application-specific alias of the key.
+ *
+ * @see java.security.KeyStore.aliases
+ */
+ public @NonNull String getAlias() {
+ return mAlias;
+ }
+
+ /** Key material encrypted by recovery key. */
+ public @NonNull byte[] getEncryptedKeyMaterial() {
+ return mEncryptedKeyMaterial;
+ }
+
+ public static final Parcelable.Creator<EntryRecoveryData> CREATOR =
+ new Parcelable.Creator<EntryRecoveryData>() {
+ public EntryRecoveryData createFromParcel(Parcel in) {
+ return new EntryRecoveryData(in);
+ }
+
+ public EntryRecoveryData[] newArray(int length) {
+ return new EntryRecoveryData[length];
+ }
+ };
+
+ /**
+ * @hide
+ */
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeString(mAlias);
+ out.writeByteArray(mEncryptedKeyMaterial);
+ }
+
+ /**
+ * @hide
+ */
+ protected EntryRecoveryData(Parcel in) {
+ mAlias = in.readString();
+ mEncryptedKeyMaterial = in.createByteArray();
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+}
diff --git a/core/java/android/security/recoverablekeystore/KeyStoreRecoveryData.aidl b/core/java/android/security/keystore/KeyDerivationParams.aidl
similarity index 88%
copy from core/java/android/security/recoverablekeystore/KeyStoreRecoveryData.aidl
copy to core/java/android/security/keystore/KeyDerivationParams.aidl
index bd76051..f39aa04 100644
--- a/core/java/android/security/recoverablekeystore/KeyStoreRecoveryData.aidl
+++ b/core/java/android/security/keystore/KeyDerivationParams.aidl
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package android.security.recoverablekeystore;
+package android.security.keystore;
/* @hide */
-parcelable KeyStoreRecoveryData;
+parcelable KeyDerivationParams;
diff --git a/core/java/android/security/recoverablekeystore/KeyDerivationParameters.java b/core/java/android/security/keystore/KeyDerivationParams.java
similarity index 68%
rename from core/java/android/security/recoverablekeystore/KeyDerivationParameters.java
rename to core/java/android/security/keystore/KeyDerivationParams.java
index d162455..b702acc 100644
--- a/core/java/android/security/recoverablekeystore/KeyDerivationParameters.java
+++ b/core/java/android/security/keystore/KeyDerivationParams.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package android.security.recoverablekeystore;
+package android.security.keystore;
import android.annotation.IntDef;
import android.annotation.NonNull;
@@ -28,21 +28,17 @@
/**
* Collection of parameters which define a key derivation function.
- * Supports
+ * Currently only supports salted SHA-256
*
- * <ul>
- * <li>SHA256
- * <li>Argon2id
- * </ul>
* @hide
*/
-public final class KeyDerivationParameters implements Parcelable {
+public final class KeyDerivationParams implements Parcelable {
private final int mAlgorithm;
private byte[] mSalt;
/** @hide */
@Retention(RetentionPolicy.SOURCE)
- @IntDef({ALGORITHM_SHA256, ALGORITHM_ARGON2ID})
+ @IntDef(prefix = {"ALGORITHM_"}, value = {ALGORITHM_SHA256, ALGORITHM_ARGON2ID})
public @interface KeyDerivationAlgorithm {
}
@@ -53,6 +49,7 @@
/**
* Argon2ID
+ * @hide
*/
// TODO: add Argon2ID support.
public static final int ALGORITHM_ARGON2ID = 2;
@@ -60,11 +57,11 @@
/**
* Creates instance of the class to to derive key using salted SHA256 hash.
*/
- public static KeyDerivationParameters createSha256Parameters(@NonNull byte[] salt) {
- return new KeyDerivationParameters(ALGORITHM_SHA256, salt);
+ public static KeyDerivationParams createSha256Params(@NonNull byte[] salt) {
+ return new KeyDerivationParams(ALGORITHM_SHA256, salt);
}
- private KeyDerivationParameters(@KeyDerivationAlgorithm int algorithm, @NonNull byte[] salt) {
+ private KeyDerivationParams(@KeyDerivationAlgorithm int algorithm, @NonNull byte[] salt) {
mAlgorithm = algorithm;
mSalt = Preconditions.checkNotNull(salt);
}
@@ -83,24 +80,30 @@
return mSalt;
}
- public static final Parcelable.Creator<KeyDerivationParameters> CREATOR =
- new Parcelable.Creator<KeyDerivationParameters>() {
- public KeyDerivationParameters createFromParcel(Parcel in) {
- return new KeyDerivationParameters(in);
+ public static final Parcelable.Creator<KeyDerivationParams> CREATOR =
+ new Parcelable.Creator<KeyDerivationParams>() {
+ public KeyDerivationParams createFromParcel(Parcel in) {
+ return new KeyDerivationParams(in);
}
- public KeyDerivationParameters[] newArray(int length) {
- return new KeyDerivationParameters[length];
+ public KeyDerivationParams[] newArray(int length) {
+ return new KeyDerivationParams[length];
}
};
+ /**
+ * @hide
+ */
@Override
public void writeToParcel(Parcel out, int flags) {
out.writeInt(mAlgorithm);
out.writeByteArray(mSalt);
}
- protected KeyDerivationParameters(Parcel in) {
+ /**
+ * @hide
+ */
+ protected KeyDerivationParams(Parcel in) {
mAlgorithm = in.readInt();
mSalt = in.createByteArray();
}
diff --git a/core/java/android/security/recoverablekeystore/KeyStoreRecoveryData.aidl b/core/java/android/security/keystore/RecoveryData.aidl
similarity index 88%
rename from core/java/android/security/recoverablekeystore/KeyStoreRecoveryData.aidl
rename to core/java/android/security/keystore/RecoveryData.aidl
index bd76051..4200de1 100644
--- a/core/java/android/security/recoverablekeystore/KeyStoreRecoveryData.aidl
+++ b/core/java/android/security/keystore/RecoveryData.aidl
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package android.security.recoverablekeystore;
+package android.security.keystore;
/* @hide */
-parcelable KeyStoreRecoveryData;
+parcelable RecoveryData;
diff --git a/core/java/android/security/keystore/RecoveryData.java b/core/java/android/security/keystore/RecoveryData.java
new file mode 100644
index 0000000..897aa18
--- /dev/null
+++ b/core/java/android/security/keystore/RecoveryData.java
@@ -0,0 +1,200 @@
+/*
+ * 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.security.keystore;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.util.Preconditions;
+
+import java.util.List;
+
+/**
+ * Helper class which returns data necessary to recover keys.
+ * Contains
+ *
+ * <ul>
+ * <li>Snapshot version.
+ * <li>Recovery metadata with UI and key derivation parameters.
+ * <li>List of application keys encrypted by recovery key.
+ * <li>Encrypted recovery key.
+ * </ul>
+ *
+ * @hide
+ */
+public final class RecoveryData implements Parcelable {
+ private int mSnapshotVersion;
+ private List<RecoveryMetadata> mRecoveryMetadata;
+ private List<EntryRecoveryData> mEntryRecoveryData;
+ private byte[] mEncryptedRecoveryKeyBlob;
+
+ /**
+ * @hide
+ * Deprecated, consider using builder.
+ */
+ public RecoveryData(
+ int snapshotVersion,
+ @NonNull List<RecoveryMetadata> recoveryMetadata,
+ @NonNull List<EntryRecoveryData> entryRecoveryData,
+ @NonNull byte[] encryptedRecoveryKeyBlob) {
+ mSnapshotVersion = snapshotVersion;
+ mRecoveryMetadata =
+ Preconditions.checkCollectionElementsNotNull(recoveryMetadata, "recoveryMetadata");
+ mEntryRecoveryData = Preconditions.checkCollectionElementsNotNull(entryRecoveryData,
+ "entryRecoveryData");
+ mEncryptedRecoveryKeyBlob = Preconditions.checkNotNull(encryptedRecoveryKeyBlob);
+ }
+
+ private RecoveryData() {
+
+ }
+
+ /**
+ * Snapshot version for given account. It is incremented when user secret or list of application
+ * keys changes.
+ */
+ public int getSnapshotVersion() {
+ return mSnapshotVersion;
+ }
+
+ /**
+ * UI and key derivation parameters. Note that combination of secrets may be used.
+ */
+ public @NonNull List<RecoveryMetadata> getRecoveryMetadata() {
+ return mRecoveryMetadata;
+ }
+
+ /**
+ * List of application keys, with key material encrypted by
+ * the recovery key ({@link #getEncryptedRecoveryKeyBlob}).
+ */
+ public @NonNull List<EntryRecoveryData> getEntryRecoveryData() {
+ return mEntryRecoveryData;
+ }
+
+ /**
+ * Recovery key blob, encrypted by user secret and recovery service public key.
+ */
+ public @NonNull byte[] getEncryptedRecoveryKeyBlob() {
+ return mEncryptedRecoveryKeyBlob;
+ }
+
+ public static final Parcelable.Creator<RecoveryData> CREATOR =
+ new Parcelable.Creator<RecoveryData>() {
+ public RecoveryData createFromParcel(Parcel in) {
+ return new RecoveryData(in);
+ }
+
+ public RecoveryData[] newArray(int length) {
+ return new RecoveryData[length];
+ }
+ };
+
+ /**
+ * Builder for creating {@link RecoveryData}.
+ */
+ public static class Builder {
+ private RecoveryData mInstance = new RecoveryData();
+
+ /**
+ * Snapshot version for given account.
+ *
+ * @param snapshotVersion The snapshot version
+ * @return This builder.
+ */
+ public Builder setSnapshotVersion(int snapshotVersion) {
+ mInstance.mSnapshotVersion = snapshotVersion;
+ return this;
+ }
+
+ /**
+ * Sets UI and key derivation parameters
+ *
+ * @param recoveryMetadata The UI and key derivation parameters
+ * @return This builder.
+ */
+ public Builder setRecoveryMetadata(@NonNull List<RecoveryMetadata> recoveryMetadata) {
+ mInstance.mRecoveryMetadata = recoveryMetadata;
+ return this;
+ }
+
+ /**
+ * List of application keys.
+ *
+ * @param entryRecoveryData List of application keys
+ * @return This builder.
+ */
+ public Builder setEntryRecoveryData(List<EntryRecoveryData> entryRecoveryData) {
+ mInstance.mEntryRecoveryData = entryRecoveryData;
+ return this;
+ }
+
+ /**
+ * Sets recovery key blob
+ *
+ * @param encryptedRecoveryKeyBlob The recovery key blob.
+ * @return This builder.
+ */
+ public Builder setEncryptedRecoveryKeyBlob(@NonNull byte[] encryptedRecoveryKeyBlob) {
+ mInstance.mEncryptedRecoveryKeyBlob = encryptedRecoveryKeyBlob;
+ return this;
+ }
+
+
+ /**
+ * Creates a new {@link RecoveryData} instance.
+ *
+ * @return new instance
+ * @throws NullPointerException if some required fields were not set.
+ */
+ public @NonNull RecoveryData build() {
+ Preconditions.checkCollectionElementsNotNull(mInstance.mRecoveryMetadata,
+ "recoveryMetadata");
+ Preconditions.checkCollectionElementsNotNull(mInstance.mEntryRecoveryData,
+ "entryRecoveryData");
+ Preconditions.checkNotNull(mInstance.mEncryptedRecoveryKeyBlob);
+ return mInstance;
+ }
+ }
+
+ /**
+ * @hide
+ */
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeInt(mSnapshotVersion);
+ out.writeTypedList(mRecoveryMetadata);
+ out.writeByteArray(mEncryptedRecoveryKeyBlob);
+ out.writeTypedList(mEntryRecoveryData);
+ }
+
+ /**
+ * @hide
+ */
+ protected RecoveryData(Parcel in) {
+ mSnapshotVersion = in.readInt();
+ mRecoveryMetadata = in.createTypedArrayList(RecoveryMetadata.CREATOR);
+ mEncryptedRecoveryKeyBlob = in.createByteArray();
+ mEntryRecoveryData = in.createTypedArrayList(EntryRecoveryData.CREATOR);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+}
diff --git a/core/java/android/security/keystore/RecoveryManager.java b/core/java/android/security/keystore/RecoveryManager.java
new file mode 100644
index 0000000..99bd284
--- /dev/null
+++ b/core/java/android/security/keystore/RecoveryManager.java
@@ -0,0 +1,406 @@
+/*
+ * 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.security.keystore;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.PendingIntent;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.ServiceSpecificException;
+
+import com.android.internal.widget.ILockSettings;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * A wrapper around KeyStore which lets key be exported to trusted hardware on server side and
+ * recovered later.
+ *
+ * @hide
+ */
+public class RecoveryManager {
+
+ /** Key has been successfully synced. */
+ public static final int RECOVERY_STATUS_SYNCED = 0;
+ /** Waiting for recovery agent to sync the key. */
+ public static final int RECOVERY_STATUS_SYNC_IN_PROGRESS = 1;
+ /** Recovery account is not available. */
+ public static final int RECOVERY_STATUS_MISSING_ACCOUNT = 2;
+ /** Key cannot be synced. */
+ public static final int RECOVERY_STATUS_PERMANENT_FAILURE = 3;
+
+ private final ILockSettings mBinder;
+
+ private RecoveryManager(ILockSettings binder) {
+ mBinder = binder;
+ }
+
+ /**
+ * Gets a new instance of the class.
+ */
+ public static RecoveryManager getInstance() {
+ ILockSettings lockSettings =
+ ILockSettings.Stub.asInterface(ServiceManager.getService("lock_settings"));
+ return new RecoveryManager(lockSettings);
+ }
+
+ /**
+ * Initializes key recovery service for the calling application. RecoveryManager
+ * randomly chooses one of the keys from the list and keeps it to use for future key export
+ * operations. Collection of all keys in the list must be signed by the provided {@code
+ * rootCertificateAlias}, which must also be present in the list of root certificates
+ * preinstalled on the device. The random selection allows RecoveryManager to select
+ * which of a set of remote recovery service devices will be used.
+ *
+ * <p>In addition, RecoveryManager enforces a delay of three months between
+ * consecutive initialization attempts, to limit the ability of an attacker to often switch
+ * remote recovery devices and significantly increase number of recovery attempts.
+ *
+ * @param rootCertificateAlias alias of a root certificate preinstalled on the device
+ * @param signedPublicKeyList binary blob a list of X509 certificates and signature
+ * @throws RecoveryManagerException if signature is invalid, or key rotation was rate
+ * limited.
+ * @hide
+ */
+ public void initRecoveryService(
+ @NonNull String rootCertificateAlias, @NonNull byte[] signedPublicKeyList)
+ throws RecoveryManagerException {
+ try {
+ mBinder.initRecoveryService(rootCertificateAlias, signedPublicKeyList);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ } catch (ServiceSpecificException e) {
+ throw RecoveryManagerException.fromServiceSpecificException(e);
+ }
+ }
+
+ /**
+ * Returns data necessary to store all recoverable keys for given account. Key material is
+ * encrypted with user secret and recovery public key.
+ *
+ * @param account specific to Recovery agent.
+ * @return Data necessary to recover keystore.
+ * @hide
+ */
+ public @NonNull RecoveryData getRecoveryData(@NonNull byte[] account)
+ throws RecoveryManagerException {
+ try {
+ RecoveryData recoveryData = mBinder.getRecoveryData(account);
+ return recoveryData;
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ } catch (ServiceSpecificException e) {
+ throw RecoveryManagerException.fromServiceSpecificException(e);
+ }
+ }
+
+ /**
+ * Sets a listener which notifies recovery agent that new recovery snapshot is available. {@link
+ * #getRecoveryData} can be used to get the snapshot. Note that every recovery agent can have at
+ * most one registered listener at any time.
+ *
+ * @param intent triggered when new snapshot is available. Unregisters listener if the value is
+ * {@code null}.
+ * @hide
+ */
+ public void setSnapshotCreatedPendingIntent(@Nullable PendingIntent intent)
+ throws RecoveryManagerException {
+ try {
+ mBinder.setSnapshotCreatedPendingIntent(intent);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ } catch (ServiceSpecificException e) {
+ throw RecoveryManagerException.fromServiceSpecificException(e);
+ }
+ }
+
+ /**
+ * Returns a map from recovery agent accounts to corresponding KeyStore recovery snapshot
+ * version. Version zero is used, if no snapshots were created for the account.
+ *
+ * @return Map from recovery agent accounts to snapshot versions.
+ * @see RecoveryData#getSnapshotVersion
+ * @hide
+ */
+ public @NonNull Map<byte[], Integer> getRecoverySnapshotVersions()
+ throws RecoveryManagerException {
+ try {
+ // IPC doesn't support generic Maps.
+ @SuppressWarnings("unchecked")
+ Map<byte[], Integer> result =
+ (Map<byte[], Integer>) mBinder.getRecoverySnapshotVersions();
+ return result;
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ } catch (ServiceSpecificException e) {
+ throw RecoveryManagerException.fromServiceSpecificException(e);
+ }
+ }
+
+ /**
+ * Server parameters used to generate new recovery key blobs. This value will be included in
+ * {@code RecoveryData.getEncryptedRecoveryKeyBlob()}. The same value must be included
+ * in vaultParams {@link #startRecoverySession}
+ *
+ * @param serverParams included in recovery key blob.
+ * @see #getRecoveryData
+ * @throws RecoveryManagerException If parameters rotation is rate limited.
+ * @hide
+ */
+ public void setServerParams(byte[] serverParams) throws RecoveryManagerException {
+ try {
+ mBinder.setServerParams(serverParams);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ } catch (ServiceSpecificException e) {
+ throw RecoveryManagerException.fromServiceSpecificException(e);
+ }
+ }
+
+ /**
+ * Updates recovery status for given keys. It is used to notify keystore that key was
+ * successfully stored on the server or there were an error. Application can check this value
+ * using {@code getRecoveyStatus}.
+ *
+ * @param packageName Application whose recoverable keys' statuses are to be updated.
+ * @param aliases List of application-specific key aliases. If the array is empty, updates the
+ * status for all existing recoverable keys.
+ * @param status Status specific to recovery agent.
+ */
+ public void setRecoveryStatus(
+ @NonNull String packageName, @Nullable String[] aliases, int status)
+ throws NameNotFoundException, RecoveryManagerException {
+ try {
+ mBinder.setRecoveryStatus(packageName, aliases, status);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ } catch (ServiceSpecificException e) {
+ throw RecoveryManagerException.fromServiceSpecificException(e);
+ }
+ }
+
+ /**
+ * Returns a {@code Map} from Application's KeyStore key aliases to their recovery status.
+ * Negative status values are reserved for recovery agent specific codes. List of common codes:
+ *
+ * <ul>
+ * <li>{@link #RECOVERY_STATUS_SYNCED}
+ * <li>{@link #RECOVERY_STATUS_SYNC_IN_PROGRESS}
+ * <li>{@link #RECOVERY_STATUS_MISSING_ACCOUNT}
+ * <li>{@link #RECOVERY_STATUS_PERMANENT_FAILURE}
+ * </ul>
+ *
+ * @return {@code Map} from KeyStore alias to recovery status.
+ * @see #setRecoveryStatus
+ * @hide
+ */
+ public Map<String, Integer> getRecoveryStatus()
+ throws RecoveryManagerException {
+ try {
+ // IPC doesn't support generic Maps.
+ @SuppressWarnings("unchecked")
+ Map<String, Integer> result =
+ (Map<String, Integer>) mBinder.getRecoveryStatus(/*packageName=*/ null);
+ return result;
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ } catch (ServiceSpecificException e) {
+ throw RecoveryManagerException.fromServiceSpecificException(e);
+ }
+ }
+
+ /**
+ * Specifies a set of secret types used for end-to-end keystore encryption. Knowing all of them
+ * is necessary to recover data.
+ *
+ * @param secretTypes {@link RecoveryMetadata#TYPE_LOCKSCREEN} or {@link
+ * RecoveryMetadata#TYPE_CUSTOM_PASSWORD}
+ */
+ public void setRecoverySecretTypes(
+ @NonNull @RecoveryMetadata.UserSecretType int[] secretTypes)
+ throws RecoveryManagerException {
+ try {
+ mBinder.setRecoverySecretTypes(secretTypes);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ } catch (ServiceSpecificException e) {
+ throw RecoveryManagerException.fromServiceSpecificException(e);
+ }
+ }
+
+ /**
+ * Defines a set of secret types used for end-to-end keystore encryption. Knowing all of them is
+ * necessary to generate RecoveryData.
+ *
+ * @return list of recovery secret types
+ * @see RecoveryData
+ */
+ public @NonNull @RecoveryMetadata.UserSecretType int[] getRecoverySecretTypes()
+ throws RecoveryManagerException {
+ try {
+ return mBinder.getRecoverySecretTypes();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ } catch (ServiceSpecificException e) {
+ throw RecoveryManagerException.fromServiceSpecificException(e);
+ }
+ }
+
+ /**
+ * Returns a list of recovery secret types, necessary to create a pending recovery snapshot.
+ * When user enters a secret of a pending type {@link #recoverySecretAvailable} should be
+ * called.
+ *
+ * @return list of recovery secret types
+ * @hide
+ */
+ public @NonNull @RecoveryMetadata.UserSecretType int[] getPendingRecoverySecretTypes()
+ throws RecoveryManagerException {
+ try {
+ return mBinder.getPendingRecoverySecretTypes();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ } catch (ServiceSpecificException e) {
+ throw RecoveryManagerException.fromServiceSpecificException(e);
+ }
+ }
+
+ /**
+ * Method notifies KeyStore that a user-generated secret is available. This method generates a
+ * symmetric session key which a trusted remote device can use to return a recovery key. Caller
+ * should use {@link RecoveryMetadata#clearSecret} to override the secret value in
+ * memory.
+ *
+ * @param recoverySecret user generated secret together with parameters necessary to regenerate
+ * it on a new device.
+ * @hide
+ */
+ public void recoverySecretAvailable(@NonNull RecoveryMetadata recoverySecret)
+ throws RecoveryManagerException {
+ try {
+ mBinder.recoverySecretAvailable(recoverySecret);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ } catch (ServiceSpecificException e) {
+ throw RecoveryManagerException.fromServiceSpecificException(e);
+ }
+ }
+
+ /**
+ * Initializes recovery session and returns a blob with proof of recovery secrets possession.
+ * The method generates symmetric key for a session, which trusted remote device can use to
+ * return recovery key.
+ *
+ * @param sessionId ID for recovery session.
+ * @param verifierPublicKey Encoded {@code java.security.cert.X509Certificate} with Public key
+ * used to create the recovery blob on the source device.
+ * Keystore will verify the certificate using root of trust.
+ * @param vaultParams Must match the parameters in the corresponding field in the recovery blob.
+ * Used to limit number of guesses.
+ * @param vaultChallenge Data passed from server for this recovery session and used to prevent
+ * replay attacks
+ * @param secrets Secrets provided by user, the method only uses type and secret fields.
+ * @return Binary blob with recovery claim. It is encrypted with verifierPublicKey and contains
+ * a proof of user secrets, session symmetric key and parameters necessary to identify the
+ * counter with the number of failed recovery attempts.
+ */
+ public @NonNull byte[] startRecoverySession(
+ @NonNull String sessionId,
+ @NonNull byte[] verifierPublicKey,
+ @NonNull byte[] vaultParams,
+ @NonNull byte[] vaultChallenge,
+ @NonNull List<RecoveryMetadata> secrets)
+ throws RecoveryManagerException {
+ try {
+ byte[] recoveryClaim =
+ mBinder.startRecoverySession(
+ sessionId,
+ verifierPublicKey,
+ vaultParams,
+ vaultChallenge,
+ secrets);
+ return recoveryClaim;
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ } catch (ServiceSpecificException e) {
+ throw RecoveryManagerException.fromServiceSpecificException(e);
+ }
+ }
+
+ /**
+ * Imports keys.
+ *
+ * @param sessionId Id for recovery session, same as in
+ * {@link #startRecoverySession(String, byte[], byte[], byte[], List)}.
+ * @param recoveryKeyBlob Recovery blob encrypted by symmetric key generated for this session.
+ * @param applicationKeys Application keys. Key material can be decrypted using recoveryKeyBlob
+ * and session. KeyStore only uses package names from the application info in {@link
+ * EntryRecoveryData}. Caller is responsibility to perform certificates check.
+ * @return Map from alias to raw key material.
+ */
+ public Map<String, byte[]> recoverKeys(
+ @NonNull String sessionId,
+ @NonNull byte[] recoveryKeyBlob,
+ @NonNull List<EntryRecoveryData> applicationKeys)
+ throws RecoveryManagerException {
+ try {
+ return (Map<String, byte[]>) mBinder.recoverKeys(
+ sessionId, recoveryKeyBlob, applicationKeys);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ } catch (ServiceSpecificException e) {
+ throw RecoveryManagerException.fromServiceSpecificException(e);
+ }
+ }
+
+ /**
+ * Generates a key called {@code alias} and loads it into the recoverable key store. Returns the
+ * raw material of the key.
+ *
+ * @param alias The key alias.
+ * @throws RecoveryManagerException if an error occurred generating and storing the
+ * key.
+ */
+ public byte[] generateAndStoreKey(@NonNull String alias)
+ throws RecoveryManagerException {
+ try {
+ return mBinder.generateAndStoreKey(alias);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ } catch (ServiceSpecificException e) {
+ throw RecoveryManagerException.fromServiceSpecificException(e);
+ }
+ }
+
+ /**
+ * Removes a key called {@code alias} from the recoverable key store.
+ *
+ * @param alias The key alias.
+ */
+ public void removeKey(@NonNull String alias) throws RecoveryManagerException {
+ try {
+ mBinder.removeKey(alias);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ } catch (ServiceSpecificException e) {
+ throw RecoveryManagerException.fromServiceSpecificException(e);
+ }
+ }
+}
diff --git a/core/java/android/security/keystore/RecoveryManagerException.java b/core/java/android/security/keystore/RecoveryManagerException.java
new file mode 100644
index 0000000..344718a
--- /dev/null
+++ b/core/java/android/security/keystore/RecoveryManagerException.java
@@ -0,0 +1,111 @@
+/*
+ * 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.security.keystore;
+
+import android.os.ServiceSpecificException;
+
+/**
+ * Exception thrown by {@link RecoveryManager} methods.
+ *
+ * @hide
+ */
+public class RecoveryManagerException extends Exception {
+ /**
+ * Failed because the loader has not been initialized with a recovery public key yet.
+ */
+ public static final int ERROR_UNINITIALIZED_RECOVERY_PUBLIC_KEY = 20;
+
+ /**
+ * Failed because no snapshot is yet pending to be synced for the user.
+ */
+ public static final int ERROR_NO_SNAPSHOT_PENDING = 21;
+
+ /**
+ * Failed due to an error internal to AndroidKeyStore.
+ */
+ public static final int ERROR_KEYSTORE_INTERNAL_ERROR = 22;
+
+ /**
+ * Failed because the user does not have a lock screen set.
+ */
+ public static final int ERROR_INSECURE_USER = 24;
+
+ /**
+ * Failed because of an internal database error.
+ */
+ public static final int ERROR_DATABASE_ERROR = 25;
+
+ /**
+ * Failed because the provided certificate was not a valid X509 certificate.
+ */
+ public static final int ERROR_BAD_X509_CERTIFICATE = 26;
+
+ /**
+ * Should never be thrown - some algorithm that all AOSP implementations must support is
+ * not available.
+ */
+ public static final int ERROR_UNEXPECTED_MISSING_ALGORITHM = 27;
+
+ /**
+ * Error thrown if decryption failed. This might be because the tag is wrong, the key is wrong,
+ * the data has become corrupted, the data has been tampered with, etc.
+ */
+ public static final int ERROR_DECRYPTION_FAILED = 28;
+
+ /**
+ * Rate limit is enforced to prevent using too many trusted remote devices, since each device
+ * can have its own number of user secret guesses allowed.
+ *
+ * @hide
+ */
+ public static final int ERROR_RATE_LIMIT_EXCEEDED = 29;
+
+ private int mErrorCode;
+
+ /**
+ * Creates new {@link #RecoveryManagerException} instance from the error code.
+ *
+ * @param errorCode An error code, as listed at the top of this file.
+ * @param message The associated error message.
+ * @hide
+ */
+ public static RecoveryManagerException fromErrorCode(
+ int errorCode, String message) {
+ return new RecoveryManagerException(errorCode, message);
+ }
+ /**
+ * Creates new {@link #RecoveryManagerException} from {@link
+ * ServiceSpecificException}.
+ *
+ * @param e exception thrown on service side.
+ * @hide
+ */
+ static RecoveryManagerException fromServiceSpecificException(
+ ServiceSpecificException e) throws RecoveryManagerException {
+ throw RecoveryManagerException.fromErrorCode(e.errorCode, e.getMessage());
+ }
+
+ private RecoveryManagerException(int errorCode, String message) {
+ super(message);
+ mErrorCode = errorCode;
+ }
+
+ /** Returns errorCode. */
+ public int getErrorCode() {
+ return mErrorCode;
+ }
+}
diff --git a/core/java/android/security/recoverablekeystore/KeyStoreRecoveryData.aidl b/core/java/android/security/keystore/RecoveryMetadata.aidl
similarity index 88%
copy from core/java/android/security/recoverablekeystore/KeyStoreRecoveryData.aidl
copy to core/java/android/security/keystore/RecoveryMetadata.aidl
index bd76051..8e342b4 100644
--- a/core/java/android/security/recoverablekeystore/KeyStoreRecoveryData.aidl
+++ b/core/java/android/security/keystore/RecoveryMetadata.aidl
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package android.security.recoverablekeystore;
+package android.security.keystore;
/* @hide */
-parcelable KeyStoreRecoveryData;
+parcelable RecoveryMetadata;
diff --git a/core/java/android/security/keystore/RecoveryMetadata.java b/core/java/android/security/keystore/RecoveryMetadata.java
new file mode 100644
index 0000000..3f09455
--- /dev/null
+++ b/core/java/android/security/keystore/RecoveryMetadata.java
@@ -0,0 +1,274 @@
+/*
+ * 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.security.keystore;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.util.Preconditions;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Arrays;
+
+/**
+ * Helper class with data necessary to recover Keystore on a new device.
+ * It defines UI shown to the user and a way to derive a cryptographic key from user output.
+ *
+ * @hide
+ */
+public final class RecoveryMetadata implements Parcelable {
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({TYPE_LOCKSCREEN, TYPE_CUSTOM_PASSWORD})
+ public @interface UserSecretType {
+ }
+
+ /**
+ * Lockscreen secret is required to recover KeyStore.
+ */
+ public static final int TYPE_LOCKSCREEN = 100;
+
+ /**
+ * Custom passphrase, unrelated to lock screen, is required to recover KeyStore.
+ */
+ public static final int TYPE_CUSTOM_PASSWORD = 101;
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({TYPE_PIN, TYPE_PASSWORD, TYPE_PATTERN})
+ public @interface LockScreenUiFormat {
+ }
+
+ /**
+ * Pin with digits only.
+ */
+ public static final int TYPE_PIN = 1;
+
+ /**
+ * Password. String with latin-1 characters only.
+ */
+ public static final int TYPE_PASSWORD = 2;
+
+ /**
+ * Pattern with 3 by 3 grid.
+ */
+ public static final int TYPE_PATTERN = 3;
+
+ @UserSecretType
+ private Integer mUserSecretType;
+
+ @LockScreenUiFormat
+ private Integer mLockScreenUiFormat;
+
+ /**
+ * Parameters of the key derivation function, including algorithm, difficulty, salt.
+ */
+ private KeyDerivationParams mKeyDerivationParams;
+ private byte[] mSecret; // Derived from user secret. The field must have limited visibility.
+
+ /**
+ * @param secret Constructor creates a reference to the secret. Caller must use
+ * @link {#clearSecret} to overwrite its value in memory.
+ * @hide
+ */
+ public RecoveryMetadata(@UserSecretType int userSecretType,
+ @LockScreenUiFormat int lockScreenUiFormat,
+ @NonNull KeyDerivationParams keyDerivationParams,
+ @NonNull byte[] secret) {
+ mUserSecretType = userSecretType;
+ mLockScreenUiFormat = lockScreenUiFormat;
+ mKeyDerivationParams = Preconditions.checkNotNull(keyDerivationParams);
+ mSecret = Preconditions.checkNotNull(secret);
+ }
+
+ private RecoveryMetadata() {
+
+ }
+
+ /**
+ * @see TYPE_LOCKSCREEN
+ * @see TYPE_CUSTOM_PASSWORD
+ */
+ public @UserSecretType int getUserSecretType() {
+ return mUserSecretType;
+ }
+
+ /**
+ * Specifies UX shown to user during recovery.
+ * Default value is {@code TYPE_LOCKSCREEN}
+ *
+ * @see TYPE_PIN
+ * @see TYPE_PASSWORD
+ * @see TYPE_PATTERN
+ */
+ public @LockScreenUiFormat int getLockScreenUiFormat() {
+ return mLockScreenUiFormat;
+ }
+
+ /**
+ * Specifies function used to derive symmetric key from user input
+ * Format is defined in separate util class.
+ */
+ public @NonNull KeyDerivationParams getKeyDerivationParams() {
+ return mKeyDerivationParams;
+ }
+
+ /**
+ * Secret derived from user input.
+ * Default value is empty array
+ *
+ * @return secret or empty array
+ */
+ public @NonNull byte[] getSecret() {
+ return mSecret;
+ }
+
+ /**
+ * Builder for creating {@link RecoveryMetadata}.
+ */
+ public static class Builder {
+ private RecoveryMetadata mInstance = new RecoveryMetadata();
+
+ /**
+ * Sets user secret type.
+ *
+ * @see TYPE_LOCKSCREEN
+ * @see TYPE_CUSTOM_PASSWORD
+ * @param userSecretType The secret type
+ * @return This builder.
+ */
+ public Builder setUserSecretType(@UserSecretType int userSecretType) {
+ mInstance.mUserSecretType = userSecretType;
+ return this;
+ }
+
+ /**
+ * Sets UI format.
+ *
+ * @see TYPE_PIN
+ * @see TYPE_PASSWORD
+ * @see TYPE_PATTERN
+ * @param lockScreenUiFormat The UI format
+ * @return This builder.
+ */
+ public Builder setLockScreenUiFormat(@LockScreenUiFormat int lockScreenUiFormat) {
+ mInstance.mLockScreenUiFormat = lockScreenUiFormat;
+ return this;
+ }
+
+ /**
+ * Sets parameters of the key derivation function.
+ *
+ * @param keyDerivationParams Key derivation Params
+ * @return This builder.
+ */
+ public Builder setKeyDerivationParams(@NonNull KeyDerivationParams
+ keyDerivationParams) {
+ mInstance.mKeyDerivationParams = keyDerivationParams;
+ return this;
+ }
+
+ /**
+ * Secret derived from user input, or empty array.
+ *
+ * @param secret The secret.
+ * @return This builder.
+ */
+ public Builder setSecret(@NonNull byte[] secret) {
+ mInstance.mSecret = secret;
+ return this;
+ }
+
+
+ /**
+ * Creates a new {@link RecoveryMetadata} instance.
+ * The instance will include default values, if {@link setSecret}
+ * or {@link setUserSecretType} were not called.
+ *
+ * @return new instance
+ * @throws NullPointerException if some required fields were not set.
+ */
+ public @NonNull RecoveryMetadata build() {
+ if (mInstance.mUserSecretType == null) {
+ mInstance.mUserSecretType = TYPE_LOCKSCREEN;
+ }
+ Preconditions.checkNotNull(mInstance.mLockScreenUiFormat);
+ Preconditions.checkNotNull(mInstance.mKeyDerivationParams);
+ if (mInstance.mSecret == null) {
+ mInstance.mSecret = new byte[]{};
+ }
+ return mInstance;
+ }
+ }
+
+ /**
+ * 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.
+ */
+ public void clearSecret() {
+ Arrays.fill(mSecret, (byte) 0);
+ }
+
+ public static final Parcelable.Creator<RecoveryMetadata> CREATOR =
+ new Parcelable.Creator<RecoveryMetadata>() {
+ public RecoveryMetadata createFromParcel(Parcel in) {
+ return new RecoveryMetadata(in);
+ }
+
+ public RecoveryMetadata[] newArray(int length) {
+ return new RecoveryMetadata[length];
+ }
+ };
+
+ /**
+ * @hide
+ */
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeInt(mUserSecretType);
+ out.writeInt(mLockScreenUiFormat);
+ out.writeTypedObject(mKeyDerivationParams, flags);
+ out.writeByteArray(mSecret);
+ }
+
+ /**
+ * @hide
+ */
+ protected RecoveryMetadata(Parcel in) {
+ mUserSecretType = in.readInt();
+ mLockScreenUiFormat = in.readInt();
+ mKeyDerivationParams = in.readTypedObject(KeyDerivationParams.CREATOR);
+ mSecret = in.createByteArray();
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+}
diff --git a/core/java/android/security/recoverablekeystore/KeyDerivationParameters.aidl b/core/java/android/security/recoverablekeystore/KeyDerivationParameters.aidl
deleted file mode 100644
index fe13179..0000000
--- a/core/java/android/security/recoverablekeystore/KeyDerivationParameters.aidl
+++ /dev/null
@@ -1,20 +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.security.recoverablekeystore;
-
-/* @hide */
-parcelable KeyDerivationParameters;
diff --git a/core/java/android/security/recoverablekeystore/KeyEntryRecoveryData.aidl b/core/java/android/security/recoverablekeystore/KeyEntryRecoveryData.aidl
deleted file mode 100644
index 1674e51..0000000
--- a/core/java/android/security/recoverablekeystore/KeyEntryRecoveryData.aidl
+++ /dev/null
@@ -1,20 +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.security.recoverablekeystore;
-
-/* @hide */
-parcelable KeyEntryRecoveryData;
diff --git a/core/java/android/security/recoverablekeystore/KeyEntryRecoveryData.java b/core/java/android/security/recoverablekeystore/KeyEntryRecoveryData.java
deleted file mode 100644
index 5f56c91..0000000
--- a/core/java/android/security/recoverablekeystore/KeyEntryRecoveryData.java
+++ /dev/null
@@ -1,88 +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.security.recoverablekeystore;
-
-import android.annotation.NonNull;
-import android.os.Parcel;
-import android.os.Parcelable;
-
-import com.android.internal.util.Preconditions;
-
-/**
- * Helper class with data necessary recover a single application key, given a recovery key.
- *
- * <ul>
- * <li>Alias - Keystore alias of the key.
- * <li>Encrypted key material.
- * </ul>
- *
- * Note that Application info is not included. Recovery Agent can only make its own keys
- * recoverable.
- *
- * @hide
- */
-public final class KeyEntryRecoveryData implements Parcelable {
- private final String mAlias;
- // The only supported format is AES-256 symmetric key.
- private final byte[] mEncryptedKeyMaterial;
-
- public KeyEntryRecoveryData(@NonNull String alias, @NonNull byte[] encryptedKeyMaterial) {
- mAlias = Preconditions.checkNotNull(alias);
- mEncryptedKeyMaterial = Preconditions.checkNotNull(encryptedKeyMaterial);
- }
-
- /**
- * Application-specific alias of the key.
- *
- * @see java.security.KeyStore.aliases
- */
- public @NonNull String getAlias() {
- return mAlias;
- }
-
- /** Encrypted key material encrypted by recovery key. */
- public @NonNull byte[] getEncryptedKeyMaterial() {
- return mEncryptedKeyMaterial;
- }
-
- public static final Parcelable.Creator<KeyEntryRecoveryData> CREATOR =
- new Parcelable.Creator<KeyEntryRecoveryData>() {
- public KeyEntryRecoveryData createFromParcel(Parcel in) {
- return new KeyEntryRecoveryData(in);
- }
-
- public KeyEntryRecoveryData[] newArray(int length) {
- return new KeyEntryRecoveryData[length];
- }
- };
-
- @Override
- public void writeToParcel(Parcel out, int flags) {
- out.writeString(mAlias);
- out.writeByteArray(mEncryptedKeyMaterial);
- }
-
- protected KeyEntryRecoveryData(Parcel in) {
- mAlias = in.readString();
- mEncryptedKeyMaterial = in.createByteArray();
- }
-
- @Override
- public int describeContents() {
- return 0;
- }
-}
diff --git a/core/java/android/security/recoverablekeystore/KeyStoreRecoveryData.java b/core/java/android/security/recoverablekeystore/KeyStoreRecoveryData.java
deleted file mode 100644
index 087f7a2..0000000
--- a/core/java/android/security/recoverablekeystore/KeyStoreRecoveryData.java
+++ /dev/null
@@ -1,115 +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.security.recoverablekeystore;
-
-import android.annotation.NonNull;
-import android.os.Parcel;
-import android.os.Parcelable;
-
-import com.android.internal.util.Preconditions;
-
-import java.util.List;
-
-/**
- * Helper class which returns data necessary to recover keys.
- * Contains
- *
- * <ul>
- * <li>Snapshot version.
- * <li>Recovery metadata with UI and key derivation parameters.
- * <li>List of application keys encrypted by recovery key.
- * <li>Encrypted recovery key.
- * </ul>
- *
- * @hide
- */
-public final class KeyStoreRecoveryData implements Parcelable {
- private final int mSnapshotVersion;
- private final List<KeyStoreRecoveryMetadata> mRecoveryMetadata;
- private final List<KeyEntryRecoveryData> mApplicationKeyBlobs;
- private final byte[] mEncryptedRecoveryKeyBlob;
-
- public KeyStoreRecoveryData(int snapshotVersion, @NonNull List<KeyStoreRecoveryMetadata>
- recoveryMetadata, @NonNull List<KeyEntryRecoveryData> applicationKeyBlobs,
- @NonNull byte[] encryptedRecoveryKeyBlob) {
- mSnapshotVersion = snapshotVersion;
- mRecoveryMetadata = Preconditions.checkNotNull(recoveryMetadata);
- mApplicationKeyBlobs = Preconditions.checkNotNull(applicationKeyBlobs);
- mEncryptedRecoveryKeyBlob = Preconditions.checkNotNull(encryptedRecoveryKeyBlob);
- }
-
- /**
- * Snapshot version for given account. It is incremented when user secret or list of application
- * keys changes.
- */
- public int getSnapshotVersion() {
- return mSnapshotVersion;
- }
-
- /**
- * UI and key derivation parameters. Note that combination of secrets may be used.
- */
- public @NonNull List<KeyStoreRecoveryMetadata> getRecoveryMetadata() {
- return mRecoveryMetadata;
- }
-
- /**
- * List of application keys, with key material encrypted by
- * the recovery key ({@link #getEncryptedRecoveryKeyBlob}).
- */
- public @NonNull List<KeyEntryRecoveryData> getApplicationKeyBlobs() {
- return mApplicationKeyBlobs;
- }
-
- /**
- * Recovery key blob, encrypted by user secret and recovery service public key.
- */
- public @NonNull byte[] getEncryptedRecoveryKeyBlob() {
- return mEncryptedRecoveryKeyBlob;
- }
-
- public static final Parcelable.Creator<KeyStoreRecoveryData> CREATOR =
- new Parcelable.Creator<KeyStoreRecoveryData>() {
- public KeyStoreRecoveryData createFromParcel(Parcel in) {
- return new KeyStoreRecoveryData(in);
- }
-
- public KeyStoreRecoveryData[] newArray(int length) {
- return new KeyStoreRecoveryData[length];
- }
- };
-
- @Override
- public void writeToParcel(Parcel out, int flags) {
- out.writeInt(mSnapshotVersion);
- out.writeTypedList(mRecoveryMetadata);
- out.writeByteArray(mEncryptedRecoveryKeyBlob);
- out.writeTypedList(mApplicationKeyBlobs);
- }
-
- protected KeyStoreRecoveryData(Parcel in) {
- mSnapshotVersion = in.readInt();
- mRecoveryMetadata = in.createTypedArrayList(KeyStoreRecoveryMetadata.CREATOR);
- mEncryptedRecoveryKeyBlob = in.createByteArray();
- mApplicationKeyBlobs = in.createTypedArrayList(KeyEntryRecoveryData.CREATOR);
- }
-
- @Override
- public int describeContents() {
- return 0;
- }
-}
diff --git a/core/java/android/security/recoverablekeystore/KeyStoreRecoveryMetadata.aidl b/core/java/android/security/recoverablekeystore/KeyStoreRecoveryMetadata.aidl
deleted file mode 100644
index e1d49de..0000000
--- a/core/java/android/security/recoverablekeystore/KeyStoreRecoveryMetadata.aidl
+++ /dev/null
@@ -1,20 +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.security.recoverablekeystore;
-
-/* @hide */
-parcelable KeyStoreRecoveryMetadata;
diff --git a/core/java/android/security/recoverablekeystore/KeyStoreRecoveryMetadata.java b/core/java/android/security/recoverablekeystore/KeyStoreRecoveryMetadata.java
deleted file mode 100644
index 43f9c80..0000000
--- a/core/java/android/security/recoverablekeystore/KeyStoreRecoveryMetadata.java
+++ /dev/null
@@ -1,180 +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.security.recoverablekeystore;
-
-import android.annotation.IntDef;
-import android.annotation.NonNull;
-import android.os.Parcel;
-import android.os.Parcelable;
-
-import com.android.internal.util.Preconditions;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.util.Arrays;
-
-/**
- * Helper class with data necessary to recover Keystore on a new device.
- * It defines UI shown to the user and a way to derive a cryptographic key from user output.
- *
- * @hide
- */
-public final class KeyStoreRecoveryMetadata implements Parcelable {
- /** @hide */
- @Retention(RetentionPolicy.SOURCE)
- @IntDef({TYPE_LOCKSCREEN, TYPE_CUSTOM_PASSWORD})
- public @interface UserSecretType {
- }
-
- /**
- * Lockscreen secret is required to recover KeyStore.
- */
- public static final int TYPE_LOCKSCREEN = 1;
-
- /**
- * Custom passphrase, unrelated to lock screen, is required to recover KeyStore.
- */
- public static final int TYPE_CUSTOM_PASSWORD = 2;
-
- /** @hide */
- @Retention(RetentionPolicy.SOURCE)
- @IntDef({TYPE_PIN, TYPE_PASSWORD, TYPE_PATTERN})
- public @interface LockScreenUiFormat {
- }
-
- /**
- * Pin with digits only.
- */
- public static final int TYPE_PIN = 1;
-
- /**
- * Password. String with latin-1 characters only.
- */
- public static final int TYPE_PASSWORD = 2;
-
- /**
- * Pattern with 3 by 3 grid.
- */
- public static final int TYPE_PATTERN = 3;
-
- @UserSecretType
- private final int mUserSecretType;
-
- @LockScreenUiFormat
- private final int mLockScreenUiFormat;
-
- /**
- * Parameters of key derivation function, including algorithm, difficulty, salt.
- */
- private KeyDerivationParameters mKeyDerivationParameters;
- private byte[] mSecret; // Derived from user secret. The field must have limited visibility.
-
- /**
- * @param secret Constructor creates a reference to the secret. Caller must use
- * @link {#clearSecret} to overwrite its value in memory.
- */
- public KeyStoreRecoveryMetadata(@UserSecretType int userSecretType,
- @LockScreenUiFormat int lockScreenUiFormat,
- @NonNull KeyDerivationParameters keyDerivationParameters, @NonNull byte[] secret) {
- mUserSecretType = userSecretType;
- mLockScreenUiFormat = lockScreenUiFormat;
- mKeyDerivationParameters = Preconditions.checkNotNull(keyDerivationParameters);
- mSecret = Preconditions.checkNotNull(secret);
- }
-
- /**
- * Specifies UX shown to user during recovery.
- *
- * @see KeyStore.TYPE_PIN
- * @see KeyStore.TYPE_PASSWORD
- * @see KeyStore.TYPE_PATTERN
- */
- public @LockScreenUiFormat int getLockScreenUiFormat() {
- return mLockScreenUiFormat;
- }
-
- /**
- * Specifies function used to derive symmetric key from user input
- * Format is defined in separate util class.
- */
- public @NonNull KeyDerivationParameters getKeyDerivationParameters() {
- return mKeyDerivationParameters;
- }
-
- /**
- * Secret string derived from user input.
- */
- public @NonNull byte[] getSecret() {
- return mSecret;
- }
-
- /**
- * @see KeyStore.TYPE_LOCKSCREEN
- * @see KeyStore.TYPE_CUSTOM_PASSWORD
- */
- public @UserSecretType int getUserSecretType() {
- return mUserSecretType;
- }
-
- /**
- * 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.
- */
- public void clearSecret() {
- Arrays.fill(mSecret, (byte) 0);
- }
-
- public static final Parcelable.Creator<KeyStoreRecoveryMetadata> CREATOR =
- new Parcelable.Creator<KeyStoreRecoveryMetadata>() {
- public KeyStoreRecoveryMetadata createFromParcel(Parcel in) {
- return new KeyStoreRecoveryMetadata(in);
- }
-
- public KeyStoreRecoveryMetadata[] newArray(int length) {
- return new KeyStoreRecoveryMetadata[length];
- }
- };
-
- @Override
- public void writeToParcel(Parcel out, int flags) {
- out.writeInt(mUserSecretType);
- out.writeInt(mLockScreenUiFormat);
- out.writeTypedObject(mKeyDerivationParameters, flags);
- out.writeByteArray(mSecret);
- }
-
- protected KeyStoreRecoveryMetadata(Parcel in) {
- mUserSecretType = in.readInt();
- mLockScreenUiFormat = in.readInt();
- mKeyDerivationParameters = in.readTypedObject(KeyDerivationParameters.CREATOR);
- mSecret = in.createByteArray();
- }
-
- @Override
- public int describeContents() {
- return 0;
- }
-}
diff --git a/core/java/android/security/recoverablekeystore/RecoverableKeyStoreLoader.java b/core/java/android/security/recoverablekeystore/RecoverableKeyStoreLoader.java
deleted file mode 100644
index a65330c..0000000
--- a/core/java/android/security/recoverablekeystore/RecoverableKeyStoreLoader.java
+++ /dev/null
@@ -1,510 +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.security.recoverablekeystore;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.app.PendingIntent;
-import android.content.pm.PackageManager.NameNotFoundException;
-import android.os.RemoteException;
-import android.os.ServiceManager;
-import android.os.ServiceSpecificException;
-import android.security.KeyStore;
-import android.util.AndroidException;
-
-import com.android.internal.widget.ILockSettings;
-
-import java.util.List;
-import java.util.Map;
-
-/**
- * A wrapper around KeyStore which lets key be exported to trusted hardware on server side and
- * recovered later.
- *
- * @hide
- */
-public class RecoverableKeyStoreLoader {
-
- public static final String PERMISSION_RECOVER_KEYSTORE = "android.permission.RECOVER_KEYSTORE";
-
- public static final int NO_ERROR = KeyStore.NO_ERROR;
- public static final int SYSTEM_ERROR = KeyStore.SYSTEM_ERROR;
-
- /**
- * Failed because the loader has not been initialized with a recovery public key yet.
- */
- public static final int ERROR_UNINITIALIZED_RECOVERY_PUBLIC_KEY = 20;
-
- /**
- * Failed because no snapshot is yet pending to be synced for the user.
- */
- public static final int ERROR_NO_SNAPSHOT_PENDING = 21;
-
- /**
- * Failed due to an error internal to AndroidKeyStore.
- */
- public static final int ERROR_KEYSTORE_INTERNAL_ERROR = 22;
-
- /**
- * Failed because the user does not have a lock screen set.
- */
- public static final int ERROR_INSECURE_USER = 24;
-
- /**
- * Failed because of an internal database error.
- */
- public static final int ERROR_DATABASE_ERROR = 25;
-
- /**
- * Failed because the provided certificate was not a valid X509 certificate.
- */
- public static final int ERROR_BAD_X509_CERTIFICATE = 26;
-
- /**
- * Should never be thrown - some algorithm that all AOSP implementations must support is
- * not available.
- */
- public static final int ERROR_UNEXPECTED_MISSING_ALGORITHM = 27;
-
- /**
- * The caller is attempting to perform an operation that is not yet fully supported in the API.
- */
- public static final int ERROR_NOT_YET_SUPPORTED = 28;
-
- /**
- * Error thrown if decryption failed. This might be because the tag is wrong, the key is wrong,
- * the data has become corrupted, the data has been tampered with, etc.
- */
- public static final int ERROR_DECRYPTION_FAILED = 29;
-
- /**
- * Rate limit is enforced to prevent using too many trusted remote devices, since each device
- * can have its own number of user secret guesses allowed.
- *
- * @hide
- */
- public static final int ERROR_RATE_LIMIT_EXCEEDED = 30;
-
- /** Key has been successfully synced. */
- public static final int RECOVERY_STATUS_SYNCED = 0;
- /** Waiting for recovery agent to sync the key. */
- public static final int RECOVERY_STATUS_SYNC_IN_PROGRESS = 1;
- /** Recovery account is not available. */
- public static final int RECOVERY_STATUS_MISSING_ACCOUNT = 2;
- /** Key cannot be synced. */
- public static final int RECOVERY_STATUS_PERMANENT_FAILURE = 3;
-
- private final ILockSettings mBinder;
-
- private RecoverableKeyStoreLoader(ILockSettings binder) {
- mBinder = binder;
- }
-
- /** @hide */
- public static RecoverableKeyStoreLoader getInstance() {
- ILockSettings lockSettings =
- ILockSettings.Stub.asInterface(ServiceManager.getService("lock_settings"));
- return new RecoverableKeyStoreLoader(lockSettings);
- }
-
- /**
- * Exceptions returned by {@link RecoverableKeyStoreLoader}.
- *
- * @hide
- */
- public static class RecoverableKeyStoreLoaderException extends AndroidException {
- private int mErrorCode;
-
- /**
- * Creates new {@link #RecoverableKeyStoreLoaderException} instance from the error code.
- *
- * @param errorCode An error code, as listed at the top of this file.
- * @param message The associated error message.
- * @hide
- */
- public static RecoverableKeyStoreLoaderException fromErrorCode(
- int errorCode, String message) {
- return new RecoverableKeyStoreLoaderException(errorCode, message);
- }
-
- /**
- * Creates new {@link #RecoverableKeyStoreLoaderException} from {@link
- * ServiceSpecificException}.
- *
- * @param e exception thrown on service side.
- * @hide
- */
- static RecoverableKeyStoreLoaderException fromServiceSpecificException(
- ServiceSpecificException e) throws RecoverableKeyStoreLoaderException {
- throw RecoverableKeyStoreLoaderException.fromErrorCode(e.errorCode, e.getMessage());
- }
-
- private RecoverableKeyStoreLoaderException(int errorCode, String message) {
- super(message);
- mErrorCode = errorCode;
- }
-
- /** Returns errorCode. */
- public int getErrorCode() {
- return mErrorCode;
- }
- }
-
- /**
- * Initializes key recovery service for the calling application. RecoverableKeyStoreLoader
- * randomly chooses one of the keys from the list and keeps it to use for future key export
- * operations. Collection of all keys in the list must be signed by the provided {@code
- * rootCertificateAlias}, which must also be present in the list of root certificates
- * preinstalled on the device. The random selection allows RecoverableKeyStoreLoader to select
- * which of a set of remote recovery service devices will be used.
- *
- * <p>In addition, RecoverableKeyStoreLoader enforces a delay of three months between
- * consecutive initialization attempts, to limit the ability of an attacker to often switch
- * remote recovery devices and significantly increase number of recovery attempts.
- *
- * @param rootCertificateAlias alias of a root certificate preinstalled on the device
- * @param signedPublicKeyList binary blob a list of X509 certificates and signature
- * @throws RecoverableKeyStoreLoaderException if signature is invalid, or key rotation was rate
- * limited.
- * @hide
- */
- public void initRecoveryService(
- @NonNull String rootCertificateAlias, @NonNull byte[] signedPublicKeyList)
- throws RecoverableKeyStoreLoaderException {
- try {
- mBinder.initRecoveryService(rootCertificateAlias, signedPublicKeyList);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- } catch (ServiceSpecificException e) {
- throw RecoverableKeyStoreLoaderException.fromServiceSpecificException(e);
- }
- }
-
- /**
- * Returns data necessary to store all recoverable keys for given account. Key material is
- * encrypted with user secret and recovery public key.
- *
- * @param account specific to Recovery agent.
- * @return Data necessary to recover keystore.
- * @hide
- */
- public @NonNull KeyStoreRecoveryData getRecoveryData(@NonNull byte[] account)
- throws RecoverableKeyStoreLoaderException {
- try {
- KeyStoreRecoveryData recoveryData = mBinder.getRecoveryData(account);
- return recoveryData;
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- } catch (ServiceSpecificException e) {
- throw RecoverableKeyStoreLoaderException.fromServiceSpecificException(e);
- }
- }
-
- /**
- * Sets a listener which notifies recovery agent that new recovery snapshot is available. {@link
- * #getRecoveryData} can be used to get the snapshot. Note that every recovery agent can have at
- * most one registered listener at any time.
- *
- * @param intent triggered when new snapshot is available. Unregisters listener if the value is
- * {@code null}.
- * @hide
- */
- public void setSnapshotCreatedPendingIntent(@Nullable PendingIntent intent)
- throws RecoverableKeyStoreLoaderException {
- try {
- mBinder.setSnapshotCreatedPendingIntent(intent);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- } catch (ServiceSpecificException e) {
- throw RecoverableKeyStoreLoaderException.fromServiceSpecificException(e);
- }
- }
-
- /**
- * Returns a map from recovery agent accounts to corresponding KeyStore recovery snapshot
- * version. Version zero is used, if no snapshots were created for the account.
- *
- * @return Map from recovery agent accounts to snapshot versions.
- * @see KeyStoreRecoveryData#getSnapshotVersion
- * @hide
- */
- public @NonNull Map<byte[], Integer> getRecoverySnapshotVersions()
- throws RecoverableKeyStoreLoaderException {
- try {
- // IPC doesn't support generic Maps.
- @SuppressWarnings("unchecked")
- Map<byte[], Integer> result =
- (Map<byte[], Integer>) mBinder.getRecoverySnapshotVersions();
- return result;
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- } catch (ServiceSpecificException e) {
- throw RecoverableKeyStoreLoaderException.fromServiceSpecificException(e);
- }
- }
-
- /**
- * Server parameters used to generate new recovery key blobs. This value will be included in
- * {@code KeyStoreRecoveryData.getEncryptedRecoveryKeyBlob()}. The same value must be included
- * in vaultParams {@link #startRecoverySession}
- *
- * @param serverParameters included in recovery key blob.
- * @see #getRecoveryData
- * @throws RecoverableKeyStoreLoaderException If parameters rotation is rate limited.
- * @hide
- */
- public void setServerParameters(long serverParameters)
- throws RecoverableKeyStoreLoaderException {
- try {
- mBinder.setServerParameters(serverParameters);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- } catch (ServiceSpecificException e) {
- throw RecoverableKeyStoreLoaderException.fromServiceSpecificException(e);
- }
- }
-
- /**
- * Updates recovery status for given keys. It is used to notify keystore that key was
- * successfully stored on the server or there were an error. Application can check this value
- * using {@code getRecoveyStatus}.
- *
- * @param packageName Application whose recoverable keys' statuses are to be updated.
- * @param aliases List of application-specific key aliases. If the array is empty, updates the
- * status for all existing recoverable keys.
- * @param status Status specific to recovery agent.
- */
- public void setRecoveryStatus(
- @NonNull String packageName, @Nullable String[] aliases, int status)
- throws NameNotFoundException, RecoverableKeyStoreLoaderException {
- try {
- mBinder.setRecoveryStatus(packageName, aliases, status);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- } catch (ServiceSpecificException e) {
- throw RecoverableKeyStoreLoaderException.fromServiceSpecificException(e);
- }
- }
-
- /**
- * Returns a {@code Map} from Application's KeyStore key aliases to their recovery status.
- * Negative status values are reserved for recovery agent specific codes. List of common codes:
- *
- * <ul>
- * <li>{@link #RECOVERY_STATUS_SYNCED}
- * <li>{@link #RECOVERY_STATUS_SYNC_IN_PROGRESS}
- * <li>{@link #RECOVERY_STATUS_MISSING_ACCOUNT}
- * <li>{@link #RECOVERY_STATUS_PERMANENT_FAILURE}
- * </ul>
- *
- * @param packageName Application whose recoverable keys' statuses are to be retrieved. if
- * {@code null} caller's package will be used.
- * @return {@code Map} from KeyStore alias to recovery status.
- * @see #setRecoveryStatus
- * @hide
- */
- public Map<String, Integer> getRecoveryStatus(@Nullable String packageName)
- throws RecoverableKeyStoreLoaderException {
- try {
- // IPC doesn't support generic Maps.
- @SuppressWarnings("unchecked")
- Map<String, Integer> result =
- (Map<String, Integer>)
- mBinder.getRecoveryStatus(packageName);
- return result;
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- } catch (ServiceSpecificException e) {
- throw RecoverableKeyStoreLoaderException.fromServiceSpecificException(e);
- }
- }
-
- /**
- * Specifies a set of secret types used for end-to-end keystore encryption. Knowing all of them
- * is necessary to recover data.
- *
- * @param secretTypes {@link KeyStoreRecoveryMetadata#TYPE_LOCKSCREEN} or {@link
- * KeyStoreRecoveryMetadata#TYPE_CUSTOM_PASSWORD}
- */
- public void setRecoverySecretTypes(
- @NonNull @KeyStoreRecoveryMetadata.UserSecretType int[] secretTypes)
- throws RecoverableKeyStoreLoaderException {
- try {
- mBinder.setRecoverySecretTypes(secretTypes);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- } catch (ServiceSpecificException e) {
- throw RecoverableKeyStoreLoaderException.fromServiceSpecificException(e);
- }
- }
-
- /**
- * Defines a set of secret types used for end-to-end keystore encryption. Knowing all of them is
- * necessary to generate KeyStoreRecoveryData.
- *
- * @return list of recovery secret types
- * @see KeyStoreRecoveryData
- */
- public @NonNull @KeyStoreRecoveryMetadata.UserSecretType int[] getRecoverySecretTypes()
- throws RecoverableKeyStoreLoaderException {
- try {
- return mBinder.getRecoverySecretTypes();
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- } catch (ServiceSpecificException e) {
- throw RecoverableKeyStoreLoaderException.fromServiceSpecificException(e);
- }
- }
-
- /**
- * Returns a list of recovery secret types, necessary to create a pending recovery snapshot.
- * When user enters a secret of a pending type {@link #recoverySecretAvailable} should be
- * called.
- *
- * @return list of recovery secret types
- */
- public @NonNull @KeyStoreRecoveryMetadata.UserSecretType int[] getPendingRecoverySecretTypes()
- throws RecoverableKeyStoreLoaderException {
- try {
- return mBinder.getPendingRecoverySecretTypes();
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- } catch (ServiceSpecificException e) {
- throw RecoverableKeyStoreLoaderException.fromServiceSpecificException(e);
- }
- }
-
- /**
- * Method notifies KeyStore that a user-generated secret is available. This method generates a
- * symmetric session key which a trusted remote device can use to return a recovery key. Caller
- * should use {@link KeyStoreRecoveryMetadata#clearSecret} to override the secret value in
- * memory.
- *
- * @param recoverySecret user generated secret together with parameters necessary to regenerate
- * it on a new device.
- */
- public void recoverySecretAvailable(@NonNull KeyStoreRecoveryMetadata recoverySecret)
- throws RecoverableKeyStoreLoaderException {
- try {
- mBinder.recoverySecretAvailable(recoverySecret);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- } catch (ServiceSpecificException e) {
- throw RecoverableKeyStoreLoaderException.fromServiceSpecificException(e);
- }
- }
-
- /**
- * Initializes recovery session and returns a blob with proof of recovery secrets possession.
- * The method generates symmetric key for a session, which trusted remote device can use to
- * return recovery key.
- *
- * @param sessionId ID for recovery session.
- * @param verifierPublicKey Certificate with Public key used to create the recovery blob on the
- * source device. Keystore will verify the certificate using root of trust.
- * @param vaultParams Must match the parameters in the corresponding field in the recovery blob.
- * Used to limit number of guesses.
- * @param vaultChallenge Data passed from server for this recovery session and used to prevent
- * replay attacks
- * @param secrets Secrets provided by user, the method only uses type and secret fields.
- * @return Binary blob with recovery claim. It is encrypted with verifierPublicKey and contains
- * a proof of user secrets, session symmetric key and parameters necessary to identify the
- * counter with the number of failed recovery attempts.
- */
- public @NonNull byte[] startRecoverySession(
- @NonNull String sessionId,
- @NonNull byte[] verifierPublicKey,
- @NonNull byte[] vaultParams,
- @NonNull byte[] vaultChallenge,
- @NonNull List<KeyStoreRecoveryMetadata> secrets)
- throws RecoverableKeyStoreLoaderException {
- try {
- byte[] recoveryClaim =
- mBinder.startRecoverySession(
- sessionId,
- verifierPublicKey,
- vaultParams,
- vaultChallenge,
- secrets);
- return recoveryClaim;
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- } catch (ServiceSpecificException e) {
- throw RecoverableKeyStoreLoaderException.fromServiceSpecificException(e);
- }
- }
-
- /**
- * Imports keys.
- *
- * @param sessionId Id for recovery session, same as in
- * {@link #startRecoverySession(String, byte[], byte[], byte[], List)}.
- * @param recoveryKeyBlob Recovery blob encrypted by symmetric key generated for this session.
- * @param applicationKeys Application keys. Key material can be decrypted using recoveryKeyBlob
- * and session. KeyStore only uses package names from the application info in {@link
- * KeyEntryRecoveryData}. Caller is responsibility to perform certificates check.
- * @return Map from alias to raw key material.
- */
- public Map<String, byte[]> recoverKeys(
- @NonNull String sessionId,
- @NonNull byte[] recoveryKeyBlob,
- @NonNull List<KeyEntryRecoveryData> applicationKeys)
- throws RecoverableKeyStoreLoaderException {
- try {
- return (Map<String, byte[]>) mBinder.recoverKeys(
- sessionId, recoveryKeyBlob, applicationKeys);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- } catch (ServiceSpecificException e) {
- throw RecoverableKeyStoreLoaderException.fromServiceSpecificException(e);
- }
- }
-
- /**
- * Generates a key called {@code alias} and loads it into the recoverable key store. Returns the
- * raw material of the key.
- *
- * @param alias The key alias.
- * @throws RecoverableKeyStoreLoaderException if an error occurred generating and storing the
- * key.
- */
- public byte[] generateAndStoreKey(@NonNull String alias)
- throws RecoverableKeyStoreLoaderException {
- try {
- return mBinder.generateAndStoreKey(alias);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- } catch (ServiceSpecificException e) {
- throw RecoverableKeyStoreLoaderException.fromServiceSpecificException(e);
- }
- }
-
- /**
- * Removes a key called {@code alias} from the recoverable key store.
- *
- * @param alias The key alias.
- */
- public void removeKey(@NonNull String alias) throws RecoverableKeyStoreLoaderException {
- try {
- mBinder.removeKey(alias);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- } catch (ServiceSpecificException e) {
- throw RecoverableKeyStoreLoaderException.fromServiceSpecificException(e);
- }
- }
-}
diff --git a/telephony/java/android/telephony/data/InterfaceAddress.aidl b/core/java/android/service/euicc/EuiccProfileInfo.aidl
similarity index 81%
rename from telephony/java/android/telephony/data/InterfaceAddress.aidl
rename to core/java/android/service/euicc/EuiccProfileInfo.aidl
index d750363..321021b 100644
--- a/telephony/java/android/telephony/data/InterfaceAddress.aidl
+++ b/core/java/android/service/euicc/EuiccProfileInfo.aidl
@@ -1,5 +1,5 @@
/*
- * Copyright 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.
@@ -13,8 +13,6 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+package android.service.euicc;
-/** @hide */
-package android.telephony.data;
-
-parcelable InterfaceAddress;
+parcelable EuiccProfileInfo;
diff --git a/core/java/android/text/Layout.java b/core/java/android/text/Layout.java
index bf4b6ac..aa97b2a 100644
--- a/core/java/android/text/Layout.java
+++ b/core/java/android/text/Layout.java
@@ -1917,10 +1917,10 @@
private static float measurePara(TextPaint paint, CharSequence text, int start, int end,
TextDirectionHeuristic textDir) {
- MeasuredText mt = null;
+ MeasuredParagraph mt = null;
TextLine tl = TextLine.obtain();
try {
- mt = MeasuredText.buildForBidi(text, start, end, textDir, mt);
+ mt = MeasuredParagraph.buildForBidi(text, start, end, textDir, mt);
final char[] chars = mt.getChars();
final int len = chars.length;
final Directions directions = mt.getDirections(0, len);
diff --git a/core/java/android/text/MeasuredParagraph.java b/core/java/android/text/MeasuredParagraph.java
new file mode 100644
index 0000000..c93e036
--- /dev/null
+++ b/core/java/android/text/MeasuredParagraph.java
@@ -0,0 +1,677 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.text;
+
+import android.annotation.FloatRange;
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.graphics.Paint;
+import android.text.AutoGrowArray.ByteArray;
+import android.text.AutoGrowArray.FloatArray;
+import android.text.AutoGrowArray.IntArray;
+import android.text.Layout.Directions;
+import android.text.style.MetricAffectingSpan;
+import android.text.style.ReplacementSpan;
+import android.util.Pools.SynchronizedPool;
+
+import dalvik.annotation.optimization.CriticalNative;
+
+import libcore.util.NativeAllocationRegistry;
+
+import java.util.Arrays;
+
+/**
+ * MeasuredParagraph provides text information for rendering purpose.
+ *
+ * The first motivation of this class is identify the text directions and retrieving individual
+ * character widths. However retrieving character widths is slower than identifying text directions.
+ * Thus, this class provides several builder methods for specific purposes.
+ *
+ * - buildForBidi:
+ * Compute only text directions.
+ * - buildForMeasurement:
+ * Compute text direction and all character widths.
+ * - buildForStaticLayout:
+ * This is bit special. StaticLayout also needs to know text direction and character widths for
+ * line breaking, but all things are done in native code. Similarly, text measurement is done
+ * in native code. So instead of storing result to Java array, this keeps the result in native
+ * code since there is no good reason to move the results to Java layer.
+ *
+ * In addition to the character widths, some additional information is computed for each purposes,
+ * e.g. whole text length for measurement or font metrics for static layout.
+ *
+ * MeasuredParagraph is NOT a thread safe object.
+ * @hide
+ */
+public class MeasuredParagraph {
+ private static final char OBJECT_REPLACEMENT_CHARACTER = '\uFFFC';
+
+ private static final NativeAllocationRegistry sRegistry = new NativeAllocationRegistry(
+ MeasuredParagraph.class.getClassLoader(), nGetReleaseFunc(), 1024);
+
+ private MeasuredParagraph() {} // Use build static functions instead.
+
+ private static final SynchronizedPool<MeasuredParagraph> sPool = new SynchronizedPool<>(1);
+
+ private static @NonNull MeasuredParagraph obtain() { // Use build static functions instead.
+ final MeasuredParagraph mt = sPool.acquire();
+ return mt != null ? mt : new MeasuredParagraph();
+ }
+
+ /**
+ * Recycle the MeasuredParagraph.
+ *
+ * Do not call any methods after you call this method.
+ */
+ public void recycle() {
+ release();
+ sPool.release(this);
+ }
+
+ // The casted original text.
+ //
+ // This may be null if the passed text is not a Spanned.
+ private @Nullable Spanned mSpanned;
+
+ // The start offset of the target range in the original text (mSpanned);
+ private @IntRange(from = 0) int mTextStart;
+
+ // The length of the target range in the original text.
+ private @IntRange(from = 0) int mTextLength;
+
+ // The copied character buffer for measuring text.
+ //
+ // The length of this array is mTextLength.
+ private @Nullable char[] mCopiedBuffer;
+
+ // The whole paragraph direction.
+ private @Layout.Direction int mParaDir;
+
+ // True if the text is LTR direction and doesn't contain any bidi characters.
+ private boolean mLtrWithoutBidi;
+
+ // The bidi level for individual characters.
+ //
+ // This is empty if mLtrWithoutBidi is true.
+ private @NonNull ByteArray mLevels = new ByteArray();
+
+ // The whole width of the text.
+ // See getWholeWidth comments.
+ private @FloatRange(from = 0.0f) float mWholeWidth;
+
+ // Individual characters' widths.
+ // See getWidths comments.
+ private @Nullable FloatArray mWidths = new FloatArray();
+
+ // The span end positions.
+ // See getSpanEndCache comments.
+ private @Nullable IntArray mSpanEndCache = new IntArray(4);
+
+ // The font metrics.
+ // See getFontMetrics comments.
+ private @Nullable IntArray mFontMetrics = new IntArray(4 * 4);
+
+ // The native MeasuredParagraph.
+ // See getNativePtr comments.
+ // Do not modify these members directly. Use bindNativeObject/unbindNativeObject instead.
+ private /* Maybe Zero */ long mNativePtr = 0;
+ private @Nullable Runnable mNativeObjectCleaner;
+
+ // Associate the native object to this Java object.
+ private void bindNativeObject(/* Non Zero*/ long nativePtr) {
+ mNativePtr = nativePtr;
+ mNativeObjectCleaner = sRegistry.registerNativeAllocation(this, nativePtr);
+ }
+
+ // Decouple the native object from this Java object and release the native object.
+ private void unbindNativeObject() {
+ if (mNativePtr != 0) {
+ mNativeObjectCleaner.run();
+ mNativePtr = 0;
+ }
+ }
+
+ // Following two objects are for avoiding object allocation.
+ private @NonNull TextPaint mCachedPaint = new TextPaint();
+ private @Nullable Paint.FontMetricsInt mCachedFm;
+
+ /**
+ * Releases internal buffers.
+ */
+ public void release() {
+ reset();
+ mLevels.clearWithReleasingLargeArray();
+ mWidths.clearWithReleasingLargeArray();
+ mFontMetrics.clearWithReleasingLargeArray();
+ mSpanEndCache.clearWithReleasingLargeArray();
+ }
+
+ /**
+ * Resets the internal state for starting new text.
+ */
+ private void reset() {
+ mSpanned = null;
+ mCopiedBuffer = null;
+ mWholeWidth = 0;
+ mLevels.clear();
+ mWidths.clear();
+ mFontMetrics.clear();
+ mSpanEndCache.clear();
+ unbindNativeObject();
+ }
+
+ /**
+ * Returns the characters to be measured.
+ *
+ * This is always available.
+ */
+ public @NonNull char[] getChars() {
+ return mCopiedBuffer;
+ }
+
+ /**
+ * Returns the paragraph direction.
+ *
+ * This is always available.
+ */
+ public @Layout.Direction int getParagraphDir() {
+ return mParaDir;
+ }
+
+ /**
+ * Returns the directions.
+ *
+ * This is always available.
+ */
+ public Directions getDirections(@IntRange(from = 0) int start, // inclusive
+ @IntRange(from = 0) int end) { // exclusive
+ if (mLtrWithoutBidi) {
+ return Layout.DIRS_ALL_LEFT_TO_RIGHT;
+ }
+
+ final int length = end - start;
+ return AndroidBidi.directions(mParaDir, mLevels.getRawArray(), start, mCopiedBuffer, start,
+ length);
+ }
+
+ /**
+ * Returns the whole text width.
+ *
+ * This is available only if the MeasureText is computed with computeForMeasurement.
+ * Returns 0 in other cases.
+ */
+ public @FloatRange(from = 0.0f) float getWholeWidth() {
+ return mWholeWidth;
+ }
+
+ /**
+ * Returns the individual character's width.
+ *
+ * This is available only if the MeasureText is computed with computeForMeasurement.
+ * Returns empty array in other cases.
+ */
+ public @NonNull FloatArray getWidths() {
+ return mWidths;
+ }
+
+ /**
+ * Returns the MetricsAffectingSpan end indices.
+ *
+ * If the input text is not a spanned string, this has one value that is the length of the text.
+ *
+ * This is available only if the MeasureText is computed with computeForStaticLayout.
+ * Returns empty array in other cases.
+ */
+ public @NonNull IntArray getSpanEndCache() {
+ return mSpanEndCache;
+ }
+
+ /**
+ * Returns the int array which holds FontMetrics.
+ *
+ * This array holds the repeat of top, bottom, ascent, descent of font metrics value.
+ *
+ * This is available only if the MeasureText is computed with computeForStaticLayout.
+ * Returns empty array in other cases.
+ */
+ public @NonNull IntArray getFontMetrics() {
+ return mFontMetrics;
+ }
+
+ /**
+ * Returns the native ptr of the MeasuredParagraph.
+ *
+ * This is available only if the MeasureText is computed with computeForStaticLayout.
+ * Returns 0 in other cases.
+ */
+ public /* Maybe Zero */ long getNativePtr() {
+ return mNativePtr;
+ }
+
+ /**
+ * Generates new MeasuredParagraph for Bidi computation.
+ *
+ * If recycle is null, this returns new instance. If recycle is not null, this fills computed
+ * result to recycle and returns recycle.
+ *
+ * @param text the character sequence to be measured
+ * @param start the inclusive start offset of the target region in the text
+ * @param end the exclusive end offset of the target region in the text
+ * @param textDir the text direction
+ * @param recycle pass existing MeasuredParagraph if you want to recycle it.
+ *
+ * @return measured text
+ */
+ public static @NonNull MeasuredParagraph buildForBidi(@NonNull CharSequence text,
+ @IntRange(from = 0) int start,
+ @IntRange(from = 0) int end,
+ @NonNull TextDirectionHeuristic textDir,
+ @Nullable MeasuredParagraph recycle) {
+ final MeasuredParagraph mt = recycle == null ? obtain() : recycle;
+ mt.resetAndAnalyzeBidi(text, start, end, textDir);
+ return mt;
+ }
+
+ /**
+ * Generates new MeasuredParagraph for measuring texts.
+ *
+ * If recycle is null, this returns new instance. If recycle is not null, this fills computed
+ * result to recycle and returns recycle.
+ *
+ * @param paint the paint to be used for rendering the text.
+ * @param text the character sequence to be measured
+ * @param start the inclusive start offset of the target region in the text
+ * @param end the exclusive end offset of the target region in the text
+ * @param textDir the text direction
+ * @param recycle pass existing MeasuredParagraph if you want to recycle it.
+ *
+ * @return measured text
+ */
+ public static @NonNull MeasuredParagraph buildForMeasurement(@NonNull TextPaint paint,
+ @NonNull CharSequence text,
+ @IntRange(from = 0) int start,
+ @IntRange(from = 0) int end,
+ @NonNull TextDirectionHeuristic textDir,
+ @Nullable MeasuredParagraph recycle) {
+ final MeasuredParagraph mt = recycle == null ? obtain() : recycle;
+ mt.resetAndAnalyzeBidi(text, start, end, textDir);
+
+ mt.mWidths.resize(mt.mTextLength);
+ if (mt.mTextLength == 0) {
+ return mt;
+ }
+
+ if (mt.mSpanned == null) {
+ // No style change by MetricsAffectingSpan. Just measure all text.
+ mt.applyMetricsAffectingSpan(
+ paint, null /* spans */, start, end, 0 /* native static layout ptr */);
+ } else {
+ // There may be a MetricsAffectingSpan. Split into span transitions and apply styles.
+ int spanEnd;
+ for (int spanStart = start; spanStart < end; spanStart = spanEnd) {
+ spanEnd = mt.mSpanned.nextSpanTransition(spanStart, end, MetricAffectingSpan.class);
+ MetricAffectingSpan[] spans = mt.mSpanned.getSpans(spanStart, spanEnd,
+ MetricAffectingSpan.class);
+ spans = TextUtils.removeEmptySpans(spans, mt.mSpanned, MetricAffectingSpan.class);
+ mt.applyMetricsAffectingSpan(
+ paint, spans, spanStart, spanEnd, 0 /* native static layout ptr */);
+ }
+ }
+ return mt;
+ }
+
+ /**
+ * Generates new MeasuredParagraph for StaticLayout.
+ *
+ * If recycle is null, this returns new instance. If recycle is not null, this fills computed
+ * result to recycle and returns recycle.
+ *
+ * @param paint the paint to be used for rendering the text.
+ * @param text the character sequence to be measured
+ * @param start the inclusive start offset of the target region in the text
+ * @param end the exclusive end offset of the target region in the text
+ * @param textDir the text direction
+ * @param recycle pass existing MeasuredParagraph if you want to recycle it.
+ *
+ * @return measured text
+ */
+ public static @NonNull MeasuredParagraph buildForStaticLayout(
+ @NonNull TextPaint paint,
+ @NonNull CharSequence text,
+ @IntRange(from = 0) int start,
+ @IntRange(from = 0) int end,
+ @NonNull TextDirectionHeuristic textDir,
+ @Nullable MeasuredParagraph recycle) {
+ final MeasuredParagraph mt = recycle == null ? obtain() : recycle;
+ mt.resetAndAnalyzeBidi(text, start, end, textDir);
+ if (mt.mTextLength == 0) {
+ // Need to build empty native measured text for StaticLayout.
+ // TODO: Stop creating empty measured text for empty lines.
+ long nativeBuilderPtr = nInitBuilder();
+ try {
+ mt.bindNativeObject(
+ nBuildNativeMeasuredParagraph(nativeBuilderPtr, mt.mCopiedBuffer));
+ } finally {
+ nFreeBuilder(nativeBuilderPtr);
+ }
+ return mt;
+ }
+
+ long nativeBuilderPtr = nInitBuilder();
+ try {
+ if (mt.mSpanned == null) {
+ // No style change by MetricsAffectingSpan. Just measure all text.
+ mt.applyMetricsAffectingSpan(paint, null /* spans */, start, end, nativeBuilderPtr);
+ mt.mSpanEndCache.append(end);
+ } else {
+ // There may be a MetricsAffectingSpan. Split into span transitions and apply
+ // styles.
+ int spanEnd;
+ for (int spanStart = start; spanStart < end; spanStart = spanEnd) {
+ spanEnd = mt.mSpanned.nextSpanTransition(spanStart, end,
+ MetricAffectingSpan.class);
+ MetricAffectingSpan[] spans = mt.mSpanned.getSpans(spanStart, spanEnd,
+ MetricAffectingSpan.class);
+ spans = TextUtils.removeEmptySpans(spans, mt.mSpanned,
+ MetricAffectingSpan.class);
+ mt.applyMetricsAffectingSpan(paint, spans, spanStart, spanEnd,
+ nativeBuilderPtr);
+ mt.mSpanEndCache.append(spanEnd);
+ }
+ }
+ mt.bindNativeObject(nBuildNativeMeasuredParagraph(nativeBuilderPtr, mt.mCopiedBuffer));
+ } finally {
+ nFreeBuilder(nativeBuilderPtr);
+ }
+
+ return mt;
+ }
+
+ /**
+ * Reset internal state and analyzes text for bidirectional runs.
+ *
+ * @param text the character sequence to be measured
+ * @param start the inclusive start offset of the target region in the text
+ * @param end the exclusive end offset of the target region in the text
+ * @param textDir the text direction
+ */
+ private void resetAndAnalyzeBidi(@NonNull CharSequence text,
+ @IntRange(from = 0) int start, // inclusive
+ @IntRange(from = 0) int end, // exclusive
+ @NonNull TextDirectionHeuristic textDir) {
+ reset();
+ mSpanned = text instanceof Spanned ? (Spanned) text : null;
+ mTextStart = start;
+ mTextLength = end - start;
+
+ if (mCopiedBuffer == null || mCopiedBuffer.length != mTextLength) {
+ mCopiedBuffer = new char[mTextLength];
+ }
+ TextUtils.getChars(text, start, end, mCopiedBuffer, 0);
+
+ // Replace characters associated with ReplacementSpan to U+FFFC.
+ if (mSpanned != null) {
+ ReplacementSpan[] spans = mSpanned.getSpans(start, end, ReplacementSpan.class);
+
+ for (int i = 0; i < spans.length; i++) {
+ int startInPara = mSpanned.getSpanStart(spans[i]) - start;
+ int endInPara = mSpanned.getSpanEnd(spans[i]) - start;
+ // The span interval may be larger and must be restricted to [start, end)
+ if (startInPara < 0) startInPara = 0;
+ if (endInPara > mTextLength) endInPara = mTextLength;
+ Arrays.fill(mCopiedBuffer, startInPara, endInPara, OBJECT_REPLACEMENT_CHARACTER);
+ }
+ }
+
+ if ((textDir == TextDirectionHeuristics.LTR
+ || textDir == TextDirectionHeuristics.FIRSTSTRONG_LTR
+ || textDir == TextDirectionHeuristics.ANYRTL_LTR)
+ && TextUtils.doesNotNeedBidi(mCopiedBuffer, 0, mTextLength)) {
+ mLevels.clear();
+ mParaDir = Layout.DIR_LEFT_TO_RIGHT;
+ mLtrWithoutBidi = true;
+ } else {
+ final int bidiRequest;
+ if (textDir == TextDirectionHeuristics.LTR) {
+ bidiRequest = Layout.DIR_REQUEST_LTR;
+ } else if (textDir == TextDirectionHeuristics.RTL) {
+ bidiRequest = Layout.DIR_REQUEST_RTL;
+ } else if (textDir == TextDirectionHeuristics.FIRSTSTRONG_LTR) {
+ bidiRequest = Layout.DIR_REQUEST_DEFAULT_LTR;
+ } else if (textDir == TextDirectionHeuristics.FIRSTSTRONG_RTL) {
+ bidiRequest = Layout.DIR_REQUEST_DEFAULT_RTL;
+ } else {
+ final boolean isRtl = textDir.isRtl(mCopiedBuffer, 0, mTextLength);
+ bidiRequest = isRtl ? Layout.DIR_REQUEST_RTL : Layout.DIR_REQUEST_LTR;
+ }
+ mLevels.resize(mTextLength);
+ mParaDir = AndroidBidi.bidi(bidiRequest, mCopiedBuffer, mLevels.getRawArray());
+ mLtrWithoutBidi = false;
+ }
+ }
+
+ private void applyReplacementRun(@NonNull ReplacementSpan replacement,
+ @IntRange(from = 0) int start, // inclusive, in copied buffer
+ @IntRange(from = 0) int end, // exclusive, in copied buffer
+ /* Maybe Zero */ long nativeBuilderPtr) {
+ // Use original text. Shouldn't matter.
+ // TODO: passing uninitizlied FontMetrics to developers. Do we need to keep this for
+ // backward compatibility? or Should we initialize them for getFontMetricsInt?
+ final float width = replacement.getSize(
+ mCachedPaint, mSpanned, start + mTextStart, end + mTextStart, mCachedFm);
+ if (nativeBuilderPtr == 0) {
+ // Assigns all width to the first character. This is the same behavior as minikin.
+ mWidths.set(start, width);
+ if (end > start + 1) {
+ Arrays.fill(mWidths.getRawArray(), start + 1, end, 0.0f);
+ }
+ mWholeWidth += width;
+ } else {
+ nAddReplacementRun(nativeBuilderPtr, mCachedPaint.getNativeInstance(), start, end,
+ width);
+ }
+ }
+
+ private void applyStyleRun(@IntRange(from = 0) int start, // inclusive, in copied buffer
+ @IntRange(from = 0) int end, // exclusive, in copied buffer
+ /* Maybe Zero */ long nativeBuilderPtr) {
+ if (nativeBuilderPtr != 0) {
+ mCachedPaint.getFontMetricsInt(mCachedFm);
+ }
+
+ if (mLtrWithoutBidi) {
+ // If the whole text is LTR direction, just apply whole region.
+ if (nativeBuilderPtr == 0) {
+ mWholeWidth += mCachedPaint.getTextRunAdvances(
+ mCopiedBuffer, start, end - start, start, end - start, false /* isRtl */,
+ mWidths.getRawArray(), start);
+ } else {
+ nAddStyleRun(nativeBuilderPtr, mCachedPaint.getNativeInstance(), start, end,
+ false /* isRtl */);
+ }
+ } else {
+ // If there is multiple bidi levels, split into individual bidi level and apply style.
+ byte level = mLevels.get(start);
+ // Note that the empty text or empty range won't reach this method.
+ // Safe to search from start + 1.
+ for (int levelStart = start, levelEnd = start + 1;; ++levelEnd) {
+ if (levelEnd == end || mLevels.get(levelEnd) != level) { // transition point
+ final boolean isRtl = (level & 0x1) != 0;
+ if (nativeBuilderPtr == 0) {
+ final int levelLength = levelEnd - levelStart;
+ mWholeWidth += mCachedPaint.getTextRunAdvances(
+ mCopiedBuffer, levelStart, levelLength, levelStart, levelLength,
+ isRtl, mWidths.getRawArray(), levelStart);
+ } else {
+ nAddStyleRun(nativeBuilderPtr, mCachedPaint.getNativeInstance(), levelStart,
+ levelEnd, isRtl);
+ }
+ if (levelEnd == end) {
+ break;
+ }
+ levelStart = levelEnd;
+ level = mLevels.get(levelEnd);
+ }
+ }
+ }
+ }
+
+ private void applyMetricsAffectingSpan(
+ @NonNull TextPaint paint,
+ @Nullable MetricAffectingSpan[] spans,
+ @IntRange(from = 0) int start, // inclusive, in original text buffer
+ @IntRange(from = 0) int end, // exclusive, in original text buffer
+ /* Maybe Zero */ long nativeBuilderPtr) {
+ mCachedPaint.set(paint);
+ // XXX paint should not have a baseline shift, but...
+ mCachedPaint.baselineShift = 0;
+
+ final boolean needFontMetrics = nativeBuilderPtr != 0;
+
+ if (needFontMetrics && mCachedFm == null) {
+ mCachedFm = new Paint.FontMetricsInt();
+ }
+
+ ReplacementSpan replacement = null;
+ if (spans != null) {
+ for (int i = 0; i < spans.length; i++) {
+ MetricAffectingSpan span = spans[i];
+ if (span instanceof ReplacementSpan) {
+ // The last ReplacementSpan is effective for backward compatibility reasons.
+ replacement = (ReplacementSpan) span;
+ } else {
+ // TODO: No need to call updateMeasureState for ReplacementSpan as well?
+ span.updateMeasureState(mCachedPaint);
+ }
+ }
+ }
+
+ final int startInCopiedBuffer = start - mTextStart;
+ final int endInCopiedBuffer = end - mTextStart;
+
+ if (replacement != null) {
+ applyReplacementRun(replacement, startInCopiedBuffer, endInCopiedBuffer,
+ nativeBuilderPtr);
+ } else {
+ applyStyleRun(startInCopiedBuffer, endInCopiedBuffer, nativeBuilderPtr);
+ }
+
+ if (needFontMetrics) {
+ if (mCachedPaint.baselineShift < 0) {
+ mCachedFm.ascent += mCachedPaint.baselineShift;
+ mCachedFm.top += mCachedPaint.baselineShift;
+ } else {
+ mCachedFm.descent += mCachedPaint.baselineShift;
+ mCachedFm.bottom += mCachedPaint.baselineShift;
+ }
+
+ mFontMetrics.append(mCachedFm.top);
+ mFontMetrics.append(mCachedFm.bottom);
+ mFontMetrics.append(mCachedFm.ascent);
+ mFontMetrics.append(mCachedFm.descent);
+ }
+ }
+
+ /**
+ * Returns the maximum index that the accumulated width not exceeds the width.
+ *
+ * If forward=false is passed, returns the minimum index from the end instead.
+ *
+ * This only works if the MeasuredParagraph is computed with computeForMeasurement.
+ * Undefined behavior in other case.
+ */
+ @IntRange(from = 0) int breakText(int limit, boolean forwards, float width) {
+ float[] w = mWidths.getRawArray();
+ if (forwards) {
+ int i = 0;
+ while (i < limit) {
+ width -= w[i];
+ if (width < 0.0f) break;
+ i++;
+ }
+ while (i > 0 && mCopiedBuffer[i - 1] == ' ') i--;
+ return i;
+ } else {
+ int i = limit - 1;
+ while (i >= 0) {
+ width -= w[i];
+ if (width < 0.0f) break;
+ i--;
+ }
+ while (i < limit - 1 && (mCopiedBuffer[i + 1] == ' ' || w[i + 1] == 0.0f)) {
+ i++;
+ }
+ return limit - i - 1;
+ }
+ }
+
+ /**
+ * Returns the length of the substring.
+ *
+ * This only works if the MeasuredParagraph is computed with computeForMeasurement.
+ * Undefined behavior in other case.
+ */
+ @FloatRange(from = 0.0f) float measure(int start, int limit) {
+ float width = 0;
+ float[] w = mWidths.getRawArray();
+ for (int i = start; i < limit; ++i) {
+ width += w[i];
+ }
+ return width;
+ }
+
+ private static native /* Non Zero */ long nInitBuilder();
+
+ /**
+ * Apply style to make native measured text.
+ *
+ * @param nativeBuilderPtr The native MeasuredParagraph builder pointer.
+ * @param paintPtr The native paint pointer to be applied.
+ * @param start The start offset in the copied buffer.
+ * @param end The end offset in the copied buffer.
+ * @param isRtl True if the text is RTL.
+ */
+ private static native void nAddStyleRun(/* Non Zero */ long nativeBuilderPtr,
+ /* Non Zero */ long paintPtr,
+ @IntRange(from = 0) int start,
+ @IntRange(from = 0) int end,
+ boolean isRtl);
+
+ /**
+ * Apply ReplacementRun to make native measured text.
+ *
+ * @param nativeBuilderPtr The native MeasuredParagraph builder pointer.
+ * @param paintPtr The native paint pointer to be applied.
+ * @param start The start offset in the copied buffer.
+ * @param end The end offset in the copied buffer.
+ * @param width The width of the replacement.
+ */
+ private static native void nAddReplacementRun(/* Non Zero */ long nativeBuilderPtr,
+ /* Non Zero */ long paintPtr,
+ @IntRange(from = 0) int start,
+ @IntRange(from = 0) int end,
+ @FloatRange(from = 0) float width);
+
+ private static native long nBuildNativeMeasuredParagraph(/* Non Zero */ long nativeBuilderPtr,
+ @NonNull char[] text);
+
+ private static native void nFreeBuilder(/* Non Zero */ long nativeBuilderPtr);
+
+ @CriticalNative
+ private static native /* Non Zero */ long nGetReleaseFunc();
+}
diff --git a/core/java/android/text/MeasuredText.java b/core/java/android/text/MeasuredText.java
index 14d6f9e..2c30360 100644
--- a/core/java/android/text/MeasuredText.java
+++ b/core/java/android/text/MeasuredText.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2010 The Android Open Source Project
+ * Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,661 +16,255 @@
package android.text;
-import android.annotation.FloatRange;
import android.annotation.IntRange;
import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.graphics.Paint;
-import android.text.AutoGrowArray.ByteArray;
-import android.text.AutoGrowArray.FloatArray;
-import android.text.AutoGrowArray.IntArray;
-import android.text.Layout.Directions;
-import android.text.style.MetricAffectingSpan;
-import android.text.style.ReplacementSpan;
-import android.util.Pools.SynchronizedPool;
+import android.util.IntArray;
-import dalvik.annotation.optimization.CriticalNative;
+import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.Preconditions;
-import libcore.util.NativeAllocationRegistry;
-
-import java.util.Arrays;
+import java.util.ArrayList;
/**
- * MeasuredText provides text information for rendering purpose.
- *
- * The first motivation of this class is identify the text directions and retrieving individual
- * character widths. However retrieving character widths is slower than identifying text directions.
- * Thus, this class provides several builder methods for specific purposes.
- *
- * - buildForBidi:
- * Compute only text directions.
- * - buildForMeasurement:
- * Compute text direction and all character widths.
- * - buildForStaticLayout:
- * This is bit special. StaticLayout also needs to know text direction and character widths for
- * line breaking, but all things are done in native code. Similarly, text measurement is done
- * in native code. So instead of storing result to Java array, this keeps the result in native
- * code since there is no good reason to move the results to Java layer.
- *
- * In addition to the character widths, some additional information is computed for each purposes,
- * e.g. whole text length for measurement or font metrics for static layout.
- *
- * MeasuredText is NOT a thread safe object.
- * @hide
+ * A text which has already been measured.
*/
-public class MeasuredText {
- private static final char OBJECT_REPLACEMENT_CHARACTER = '\uFFFC';
+public class MeasuredText implements Spanned {
+ private static final char LINE_FEED = '\n';
- private static final NativeAllocationRegistry sRegistry = new NativeAllocationRegistry(
- MeasuredText.class.getClassLoader(), nGetReleaseFunc(), 1024);
+ // The original text.
+ private final @NonNull CharSequence mText;
- private MeasuredText() {} // Use build static functions instead.
+ // The inclusive start offset of the measuring target.
+ private final @IntRange(from = 0) int mStart;
- private static final SynchronizedPool<MeasuredText> sPool = new SynchronizedPool<>(1);
+ // The exclusive end offset of the measuring target.
+ private final @IntRange(from = 0) int mEnd;
- private static @NonNull MeasuredText obtain() { // Use build static functions instead.
- final MeasuredText mt = sPool.acquire();
- return mt != null ? mt : new MeasuredText();
+ // The TextPaint used for measurement.
+ private final @NonNull TextPaint mPaint;
+
+ // The requested text direction.
+ private final @NonNull TextDirectionHeuristic mTextDir;
+
+ // The measured paragraph texts.
+ private final @NonNull MeasuredParagraph[] mMeasuredParagraphs;
+
+ // The sorted paragraph end offsets.
+ private final @NonNull int[] mParagraphBreakPoints;
+
+ /**
+ * Build MeasuredText from the text.
+ *
+ * @param text The text to be measured.
+ * @param paint The paint to be used for drawing.
+ * @param textDir The text direction.
+ * @return The measured text.
+ */
+ public static @NonNull MeasuredText build(@NonNull CharSequence text,
+ @NonNull TextPaint paint,
+ @NonNull TextDirectionHeuristic textDir) {
+ return MeasuredText.build(text, paint, textDir, 0, text.length());
}
/**
- * Recycle the MeasuredText.
+ * Build MeasuredText from the specific range of the text..
*
- * Do not call any methods after you call this method.
+ * @param text The text to be measured.
+ * @param paint The paint to be used for drawing.
+ * @param textDir The text direction.
+ * @param start The inclusive start offset of the text.
+ * @param end The exclusive start offset of the text.
+ * @return The measured text.
*/
- public void recycle() {
- release();
- sPool.release(this);
+ public static @NonNull MeasuredText build(@NonNull CharSequence text,
+ @NonNull TextPaint paint,
+ @NonNull TextDirectionHeuristic textDir,
+ @IntRange(from = 0) int start,
+ @IntRange(from = 0) int end) {
+ Preconditions.checkNotNull(text);
+ Preconditions.checkNotNull(paint);
+ Preconditions.checkNotNull(textDir);
+ Preconditions.checkArgumentInRange(start, 0, text.length(), "start");
+ Preconditions.checkArgumentInRange(end, 0, text.length(), "end");
+
+ final IntArray paragraphEnds = new IntArray();
+ final ArrayList<MeasuredParagraph> measuredTexts = new ArrayList<>();
+
+ int paraEnd = 0;
+ for (int paraStart = start; paraStart < end; paraStart = paraEnd) {
+ paraEnd = TextUtils.indexOf(text, LINE_FEED, paraStart, end);
+ if (paraEnd < 0) {
+ // No LINE_FEED(U+000A) character found. Use end of the text as the paragraph end.
+ paraEnd = end;
+ } else {
+ paraEnd++; // Includes LINE_FEED(U+000A) to the prev paragraph.
+ }
+
+ paragraphEnds.add(paraEnd);
+ measuredTexts.add(MeasuredParagraph.buildForStaticLayout(
+ paint, text, paraStart, paraEnd, textDir, null /* no recycle */));
+ }
+
+ return new MeasuredText(text, start, end, paint, textDir,
+ measuredTexts.toArray(new MeasuredParagraph[measuredTexts.size()]),
+ paragraphEnds.toArray());
}
- // The casted original text.
+ // Use MeasuredText.build instead.
+ private MeasuredText(@NonNull CharSequence text,
+ @IntRange(from = 0) int start,
+ @IntRange(from = 0) int end,
+ @NonNull TextPaint paint,
+ @NonNull TextDirectionHeuristic textDir,
+ @NonNull MeasuredParagraph[] measuredTexts,
+ @NonNull int[] paragraphBreakPoints) {
+ mText = text;
+ mStart = start;
+ mEnd = end;
+ mPaint = paint;
+ mMeasuredParagraphs = measuredTexts;
+ mParagraphBreakPoints = paragraphBreakPoints;
+ mTextDir = textDir;
+ }
+
+ /**
+ * Return the underlying text.
+ */
+ public @NonNull CharSequence getText() {
+ return mText;
+ }
+
+ /**
+ * Returns the inclusive start offset of measured region.
+ */
+ public @IntRange(from = 0) int getStart() {
+ return mStart;
+ }
+
+ /**
+ * Returns the exclusive end offset of measured region.
+ */
+ public @IntRange(from = 0) int getEnd() {
+ return mEnd;
+ }
+
+ /**
+ * Returns the text direction associated with char sequence.
+ */
+ public @NonNull TextDirectionHeuristic getTextDir() {
+ return mTextDir;
+ }
+
+ /**
+ * Returns the paint used to measure this text.
+ */
+ public @NonNull TextPaint getPaint() {
+ return mPaint;
+ }
+
+ /**
+ * Returns the length of the paragraph of this text.
+ */
+ public @IntRange(from = 0) int getParagraphCount() {
+ return mParagraphBreakPoints.length;
+ }
+
+ /**
+ * Returns the paragraph start offset of the text.
+ */
+ public @IntRange(from = 0) int getParagraphStart(@IntRange(from = 0) int paraIndex) {
+ Preconditions.checkArgumentInRange(paraIndex, 0, getParagraphCount(), "paraIndex");
+ return paraIndex == 0 ? mStart : mParagraphBreakPoints[paraIndex - 1];
+ }
+
+ /**
+ * Returns the paragraph end offset of the text.
+ */
+ public @IntRange(from = 0) int getParagraphEnd(@IntRange(from = 0) int paraIndex) {
+ Preconditions.checkArgumentInRange(paraIndex, 0, getParagraphCount(), "paraIndex");
+ return mParagraphBreakPoints[paraIndex];
+ }
+
+ /** @hide */
+ public @NonNull MeasuredParagraph getMeasuredParagraph(@IntRange(from = 0) int paraIndex) {
+ return mMeasuredParagraphs[paraIndex];
+ }
+
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+ // Spanned overrides
//
- // This may be null if the passed text is not a Spanned.
- private @Nullable Spanned mSpanned;
+ // Just proxy for underlying mText if appropriate.
- // The start offset of the target range in the original text (mSpanned);
- private @IntRange(from = 0) int mTextStart;
+ @Override
+ public <T> T[] getSpans(int start, int end, Class<T> type) {
+ if (mText instanceof Spanned) {
+ return ((Spanned) mText).getSpans(start, end, type);
+ } else {
+ return ArrayUtils.emptyArray(type);
+ }
+ }
- // The length of the target range in the original text.
- private @IntRange(from = 0) int mTextLength;
+ @Override
+ public int getSpanStart(Object tag) {
+ if (mText instanceof Spanned) {
+ return ((Spanned) mText).getSpanStart(tag);
+ } else {
+ return -1;
+ }
+ }
- // The copied character buffer for measuring text.
+ @Override
+ public int getSpanEnd(Object tag) {
+ if (mText instanceof Spanned) {
+ return ((Spanned) mText).getSpanEnd(tag);
+ } else {
+ return -1;
+ }
+ }
+
+ @Override
+ public int getSpanFlags(Object tag) {
+ if (mText instanceof Spanned) {
+ return ((Spanned) mText).getSpanFlags(tag);
+ } else {
+ return 0;
+ }
+ }
+
+ @Override
+ public int nextSpanTransition(int start, int limit, Class type) {
+ if (mText instanceof Spanned) {
+ return ((Spanned) mText).nextSpanTransition(start, limit, type);
+ } else {
+ return mText.length();
+ }
+ }
+
+ ///////////////////////////////////////////////////////////////////////////////////////////////
+ // CharSequence overrides.
//
- // The length of this array is mTextLength.
- private @Nullable char[] mCopiedBuffer;
+ // Just proxy for underlying mText.
- // The whole paragraph direction.
- private @Layout.Direction int mParaDir;
-
- // True if the text is LTR direction and doesn't contain any bidi characters.
- private boolean mLtrWithoutBidi;
-
- // The bidi level for individual characters.
- //
- // This is empty if mLtrWithoutBidi is true.
- private @NonNull ByteArray mLevels = new ByteArray();
-
- // The whole width of the text.
- // See getWholeWidth comments.
- private @FloatRange(from = 0.0f) float mWholeWidth;
-
- // Individual characters' widths.
- // See getWidths comments.
- private @Nullable FloatArray mWidths = new FloatArray();
-
- // The span end positions.
- // See getSpanEndCache comments.
- private @Nullable IntArray mSpanEndCache = new IntArray(4);
-
- // The font metrics.
- // See getFontMetrics comments.
- private @Nullable IntArray mFontMetrics = new IntArray(4 * 4);
-
- // The native MeasuredText.
- // See getNativePtr comments.
- // Do not modify these members directly. Use bindNativeObject/unbindNativeObject instead.
- private /* Maybe Zero */ long mNativePtr = 0;
- private @Nullable Runnable mNativeObjectCleaner;
-
- // Associate the native object to this Java object.
- private void bindNativeObject(/* Non Zero*/ long nativePtr) {
- mNativePtr = nativePtr;
- mNativeObjectCleaner = sRegistry.registerNativeAllocation(this, nativePtr);
+ @Override
+ public int length() {
+ return mText.length();
}
- // Decouple the native object from this Java object and release the native object.
- private void unbindNativeObject() {
- if (mNativePtr != 0) {
- mNativeObjectCleaner.run();
- mNativePtr = 0;
- }
+ @Override
+ public char charAt(int index) {
+ // TODO: Should this be index + mStart ?
+ return mText.charAt(index);
}
- // Following two objects are for avoiding object allocation.
- private @NonNull TextPaint mCachedPaint = new TextPaint();
- private @Nullable Paint.FontMetricsInt mCachedFm;
-
- /**
- * Releases internal buffers.
- */
- public void release() {
- reset();
- mLevels.clearWithReleasingLargeArray();
- mWidths.clearWithReleasingLargeArray();
- mFontMetrics.clearWithReleasingLargeArray();
- mSpanEndCache.clearWithReleasingLargeArray();
+ @Override
+ public CharSequence subSequence(int start, int end) {
+ // TODO: return MeasuredText.
+ // TODO: Should this be index + mStart, end + mStart ?
+ return mText.subSequence(start, end);
}
- /**
- * Resets the internal state for starting new text.
- */
- private void reset() {
- mSpanned = null;
- mCopiedBuffer = null;
- mWholeWidth = 0;
- mLevels.clear();
- mWidths.clear();
- mFontMetrics.clear();
- mSpanEndCache.clear();
- unbindNativeObject();
+ @Override
+ public String toString() {
+ return mText.toString();
}
-
- /**
- * Returns the characters to be measured.
- *
- * This is always available.
- */
- public @NonNull char[] getChars() {
- return mCopiedBuffer;
- }
-
- /**
- * Returns the paragraph direction.
- *
- * This is always available.
- */
- public @Layout.Direction int getParagraphDir() {
- return mParaDir;
- }
-
- /**
- * Returns the directions.
- *
- * This is always available.
- */
- public Directions getDirections(@IntRange(from = 0) int start, // inclusive
- @IntRange(from = 0) int end) { // exclusive
- if (mLtrWithoutBidi) {
- return Layout.DIRS_ALL_LEFT_TO_RIGHT;
- }
-
- final int length = end - start;
- return AndroidBidi.directions(mParaDir, mLevels.getRawArray(), start, mCopiedBuffer, start,
- length);
- }
-
- /**
- * Returns the whole text width.
- *
- * This is available only if the MeasureText is computed with computeForMeasurement.
- * Returns 0 in other cases.
- */
- public @FloatRange(from = 0.0f) float getWholeWidth() {
- return mWholeWidth;
- }
-
- /**
- * Returns the individual character's width.
- *
- * This is available only if the MeasureText is computed with computeForMeasurement.
- * Returns empty array in other cases.
- */
- public @NonNull FloatArray getWidths() {
- return mWidths;
- }
-
- /**
- * Returns the MetricsAffectingSpan end indices.
- *
- * If the input text is not a spanned string, this has one value that is the length of the text.
- *
- * This is available only if the MeasureText is computed with computeForStaticLayout.
- * Returns empty array in other cases.
- */
- public @NonNull IntArray getSpanEndCache() {
- return mSpanEndCache;
- }
-
- /**
- * Returns the int array which holds FontMetrics.
- *
- * This array holds the repeat of top, bottom, ascent, descent of font metrics value.
- *
- * This is available only if the MeasureText is computed with computeForStaticLayout.
- * Returns empty array in other cases.
- */
- public @NonNull IntArray getFontMetrics() {
- return mFontMetrics;
- }
-
- /**
- * Returns the native ptr of the MeasuredText.
- *
- * This is available only if the MeasureText is computed with computeForStaticLayout.
- * Returns 0 in other cases.
- */
- public /* Maybe Zero */ long getNativePtr() {
- return mNativePtr;
- }
-
- /**
- * Generates new MeasuredText for Bidi computation.
- *
- * If recycle is null, this returns new instance. If recycle is not null, this fills computed
- * result to recycle and returns recycle.
- *
- * @param text the character sequence to be measured
- * @param start the inclusive start offset of the target region in the text
- * @param end the exclusive end offset of the target region in the text
- * @param textDir the text direction
- * @param recycle pass existing MeasuredText if you want to recycle it.
- *
- * @return measured text
- */
- public static @NonNull MeasuredText buildForBidi(@NonNull CharSequence text,
- @IntRange(from = 0) int start,
- @IntRange(from = 0) int end,
- @NonNull TextDirectionHeuristic textDir,
- @Nullable MeasuredText recycle) {
- final MeasuredText mt = recycle == null ? obtain() : recycle;
- mt.resetAndAnalyzeBidi(text, start, end, textDir);
- return mt;
- }
-
- /**
- * Generates new MeasuredText for measuring texts.
- *
- * If recycle is null, this returns new instance. If recycle is not null, this fills computed
- * result to recycle and returns recycle.
- *
- * @param paint the paint to be used for rendering the text.
- * @param text the character sequence to be measured
- * @param start the inclusive start offset of the target region in the text
- * @param end the exclusive end offset of the target region in the text
- * @param textDir the text direction
- * @param recycle pass existing MeasuredText if you want to recycle it.
- *
- * @return measured text
- */
- public static @NonNull MeasuredText buildForMeasurement(@NonNull TextPaint paint,
- @NonNull CharSequence text,
- @IntRange(from = 0) int start,
- @IntRange(from = 0) int end,
- @NonNull TextDirectionHeuristic textDir,
- @Nullable MeasuredText recycle) {
- final MeasuredText mt = recycle == null ? obtain() : recycle;
- mt.resetAndAnalyzeBidi(text, start, end, textDir);
-
- mt.mWidths.resize(mt.mTextLength);
- if (mt.mTextLength == 0) {
- return mt;
- }
-
- if (mt.mSpanned == null) {
- // No style change by MetricsAffectingSpan. Just measure all text.
- mt.applyMetricsAffectingSpan(
- paint, null /* spans */, start, end, 0 /* native static layout ptr */);
- } else {
- // There may be a MetricsAffectingSpan. Split into span transitions and apply styles.
- int spanEnd;
- for (int spanStart = start; spanStart < end; spanStart = spanEnd) {
- spanEnd = mt.mSpanned.nextSpanTransition(spanStart, end, MetricAffectingSpan.class);
- MetricAffectingSpan[] spans = mt.mSpanned.getSpans(spanStart, spanEnd,
- MetricAffectingSpan.class);
- spans = TextUtils.removeEmptySpans(spans, mt.mSpanned, MetricAffectingSpan.class);
- mt.applyMetricsAffectingSpan(
- paint, spans, spanStart, spanEnd, 0 /* native static layout ptr */);
- }
- }
- return mt;
- }
-
- /**
- * Generates new MeasuredText for StaticLayout.
- *
- * If recycle is null, this returns new instance. If recycle is not null, this fills computed
- * result to recycle and returns recycle.
- *
- * @param paint the paint to be used for rendering the text.
- * @param text the character sequence to be measured
- * @param start the inclusive start offset of the target region in the text
- * @param end the exclusive end offset of the target region in the text
- * @param textDir the text direction
- * @param recycle pass existing MeasuredText if you want to recycle it.
- *
- * @return measured text
- */
- public static @NonNull MeasuredText buildForStaticLayout(
- @NonNull TextPaint paint,
- @NonNull CharSequence text,
- @IntRange(from = 0) int start,
- @IntRange(from = 0) int end,
- @NonNull TextDirectionHeuristic textDir,
- @Nullable MeasuredText recycle) {
- final MeasuredText mt = recycle == null ? obtain() : recycle;
- mt.resetAndAnalyzeBidi(text, start, end, textDir);
- if (mt.mTextLength == 0) {
- // Need to build empty native measured text for StaticLayout.
- // TODO: Stop creating empty measured text for empty lines.
- long nativeBuilderPtr = nInitBuilder();
- try {
- mt.bindNativeObject(nBuildNativeMeasuredText(nativeBuilderPtr, mt.mCopiedBuffer));
- } finally {
- nFreeBuilder(nativeBuilderPtr);
- }
- return mt;
- }
-
- long nativeBuilderPtr = nInitBuilder();
- try {
- if (mt.mSpanned == null) {
- // No style change by MetricsAffectingSpan. Just measure all text.
- mt.applyMetricsAffectingSpan(paint, null /* spans */, start, end, nativeBuilderPtr);
- mt.mSpanEndCache.append(end);
- } else {
- // There may be a MetricsAffectingSpan. Split into span transitions and apply
- // styles.
- int spanEnd;
- for (int spanStart = start; spanStart < end; spanStart = spanEnd) {
- spanEnd = mt.mSpanned.nextSpanTransition(spanStart, end,
- MetricAffectingSpan.class);
- MetricAffectingSpan[] spans = mt.mSpanned.getSpans(spanStart, spanEnd,
- MetricAffectingSpan.class);
- spans = TextUtils.removeEmptySpans(spans, mt.mSpanned,
- MetricAffectingSpan.class);
- mt.applyMetricsAffectingSpan(paint, spans, spanStart, spanEnd,
- nativeBuilderPtr);
- mt.mSpanEndCache.append(spanEnd);
- }
- }
- mt.bindNativeObject(nBuildNativeMeasuredText(nativeBuilderPtr, mt.mCopiedBuffer));
- } finally {
- nFreeBuilder(nativeBuilderPtr);
- }
-
- return mt;
- }
-
- /**
- * Reset internal state and analyzes text for bidirectional runs.
- *
- * @param text the character sequence to be measured
- * @param start the inclusive start offset of the target region in the text
- * @param end the exclusive end offset of the target region in the text
- * @param textDir the text direction
- */
- private void resetAndAnalyzeBidi(@NonNull CharSequence text,
- @IntRange(from = 0) int start, // inclusive
- @IntRange(from = 0) int end, // exclusive
- @NonNull TextDirectionHeuristic textDir) {
- reset();
- mSpanned = text instanceof Spanned ? (Spanned) text : null;
- mTextStart = start;
- mTextLength = end - start;
-
- if (mCopiedBuffer == null || mCopiedBuffer.length != mTextLength) {
- mCopiedBuffer = new char[mTextLength];
- }
- TextUtils.getChars(text, start, end, mCopiedBuffer, 0);
-
- // Replace characters associated with ReplacementSpan to U+FFFC.
- if (mSpanned != null) {
- ReplacementSpan[] spans = mSpanned.getSpans(start, end, ReplacementSpan.class);
-
- for (int i = 0; i < spans.length; i++) {
- int startInPara = mSpanned.getSpanStart(spans[i]) - start;
- int endInPara = mSpanned.getSpanEnd(spans[i]) - start;
- // The span interval may be larger and must be restricted to [start, end)
- if (startInPara < 0) startInPara = 0;
- if (endInPara > mTextLength) endInPara = mTextLength;
- Arrays.fill(mCopiedBuffer, startInPara, endInPara, OBJECT_REPLACEMENT_CHARACTER);
- }
- }
-
- if ((textDir == TextDirectionHeuristics.LTR ||
- textDir == TextDirectionHeuristics.FIRSTSTRONG_LTR ||
- textDir == TextDirectionHeuristics.ANYRTL_LTR) &&
- TextUtils.doesNotNeedBidi(mCopiedBuffer, 0, mTextLength)) {
- mLevels.clear();
- mParaDir = Layout.DIR_LEFT_TO_RIGHT;
- mLtrWithoutBidi = true;
- } else {
- final int bidiRequest;
- if (textDir == TextDirectionHeuristics.LTR) {
- bidiRequest = Layout.DIR_REQUEST_LTR;
- } else if (textDir == TextDirectionHeuristics.RTL) {
- bidiRequest = Layout.DIR_REQUEST_RTL;
- } else if (textDir == TextDirectionHeuristics.FIRSTSTRONG_LTR) {
- bidiRequest = Layout.DIR_REQUEST_DEFAULT_LTR;
- } else if (textDir == TextDirectionHeuristics.FIRSTSTRONG_RTL) {
- bidiRequest = Layout.DIR_REQUEST_DEFAULT_RTL;
- } else {
- final boolean isRtl = textDir.isRtl(mCopiedBuffer, 0, mTextLength);
- bidiRequest = isRtl ? Layout.DIR_REQUEST_RTL : Layout.DIR_REQUEST_LTR;
- }
- mLevels.resize(mTextLength);
- mParaDir = AndroidBidi.bidi(bidiRequest, mCopiedBuffer, mLevels.getRawArray());
- mLtrWithoutBidi = false;
- }
- }
-
- private void applyReplacementRun(@NonNull ReplacementSpan replacement,
- @IntRange(from = 0) int start, // inclusive, in copied buffer
- @IntRange(from = 0) int end, // exclusive, in copied buffer
- /* Maybe Zero */ long nativeBuilderPtr) {
- // Use original text. Shouldn't matter.
- // TODO: passing uninitizlied FontMetrics to developers. Do we need to keep this for
- // backward compatibility? or Should we initialize them for getFontMetricsInt?
- final float width = replacement.getSize(
- mCachedPaint, mSpanned, start + mTextStart, end + mTextStart, mCachedFm);
- if (nativeBuilderPtr == 0) {
- // Assigns all width to the first character. This is the same behavior as minikin.
- mWidths.set(start, width);
- if (end > start + 1) {
- Arrays.fill(mWidths.getRawArray(), start + 1, end, 0.0f);
- }
- mWholeWidth += width;
- } else {
- nAddReplacementRun(nativeBuilderPtr, mCachedPaint.getNativeInstance(), start, end,
- width);
- }
- }
-
- private void applyStyleRun(@IntRange(from = 0) int start, // inclusive, in copied buffer
- @IntRange(from = 0) int end, // exclusive, in copied buffer
- /* Maybe Zero */ long nativeBuilderPtr) {
- if (nativeBuilderPtr != 0) {
- mCachedPaint.getFontMetricsInt(mCachedFm);
- }
-
- if (mLtrWithoutBidi) {
- // If the whole text is LTR direction, just apply whole region.
- if (nativeBuilderPtr == 0) {
- mWholeWidth += mCachedPaint.getTextRunAdvances(
- mCopiedBuffer, start, end - start, start, end - start, false /* isRtl */,
- mWidths.getRawArray(), start);
- } else {
- nAddStyleRun(nativeBuilderPtr, mCachedPaint.getNativeInstance(), start, end,
- false /* isRtl */);
- }
- } else {
- // If there is multiple bidi levels, split into individual bidi level and apply style.
- byte level = mLevels.get(start);
- // Note that the empty text or empty range won't reach this method.
- // Safe to search from start + 1.
- for (int levelStart = start, levelEnd = start + 1;; ++levelEnd) {
- if (levelEnd == end || mLevels.get(levelEnd) != level) { // transition point
- final boolean isRtl = (level & 0x1) != 0;
- if (nativeBuilderPtr == 0) {
- final int levelLength = levelEnd - levelStart;
- mWholeWidth += mCachedPaint.getTextRunAdvances(
- mCopiedBuffer, levelStart, levelLength, levelStart, levelLength,
- isRtl, mWidths.getRawArray(), levelStart);
- } else {
- nAddStyleRun(nativeBuilderPtr, mCachedPaint.getNativeInstance(), levelStart,
- levelEnd, isRtl);
- }
- if (levelEnd == end) {
- break;
- }
- levelStart = levelEnd;
- level = mLevels.get(levelEnd);
- }
- }
- }
- }
-
- private void applyMetricsAffectingSpan(
- @NonNull TextPaint paint,
- @Nullable MetricAffectingSpan[] spans,
- @IntRange(from = 0) int start, // inclusive, in original text buffer
- @IntRange(from = 0) int end, // exclusive, in original text buffer
- /* Maybe Zero */ long nativeBuilderPtr) {
- mCachedPaint.set(paint);
- // XXX paint should not have a baseline shift, but...
- mCachedPaint.baselineShift = 0;
-
- final boolean needFontMetrics = nativeBuilderPtr != 0;
-
- if (needFontMetrics && mCachedFm == null) {
- mCachedFm = new Paint.FontMetricsInt();
- }
-
- ReplacementSpan replacement = null;
- if (spans != null) {
- for (int i = 0; i < spans.length; i++) {
- MetricAffectingSpan span = spans[i];
- if (span instanceof ReplacementSpan) {
- // The last ReplacementSpan is effective for backward compatibility reasons.
- replacement = (ReplacementSpan) span;
- } else {
- // TODO: No need to call updateMeasureState for ReplacementSpan as well?
- span.updateMeasureState(mCachedPaint);
- }
- }
- }
-
- final int startInCopiedBuffer = start - mTextStart;
- final int endInCopiedBuffer = end - mTextStart;
-
- if (replacement != null) {
- applyReplacementRun(replacement, startInCopiedBuffer, endInCopiedBuffer,
- nativeBuilderPtr);
- } else {
- applyStyleRun(startInCopiedBuffer, endInCopiedBuffer, nativeBuilderPtr);
- }
-
- if (needFontMetrics) {
- if (mCachedPaint.baselineShift < 0) {
- mCachedFm.ascent += mCachedPaint.baselineShift;
- mCachedFm.top += mCachedPaint.baselineShift;
- } else {
- mCachedFm.descent += mCachedPaint.baselineShift;
- mCachedFm.bottom += mCachedPaint.baselineShift;
- }
-
- mFontMetrics.append(mCachedFm.top);
- mFontMetrics.append(mCachedFm.bottom);
- mFontMetrics.append(mCachedFm.ascent);
- mFontMetrics.append(mCachedFm.descent);
- }
- }
-
- /**
- * Returns the maximum index that the accumulated width not exceeds the width.
- *
- * If forward=false is passed, returns the minimum index from the end instead.
- *
- * This only works if the MeasuredText is computed with computeForMeasurement.
- * Undefined behavior in other case.
- */
- @IntRange(from = 0) int breakText(int limit, boolean forwards, float width) {
- float[] w = mWidths.getRawArray();
- if (forwards) {
- int i = 0;
- while (i < limit) {
- width -= w[i];
- if (width < 0.0f) break;
- i++;
- }
- while (i > 0 && mCopiedBuffer[i - 1] == ' ') i--;
- return i;
- } else {
- int i = limit - 1;
- while (i >= 0) {
- width -= w[i];
- if (width < 0.0f) break;
- i--;
- }
- while (i < limit - 1 && (mCopiedBuffer[i + 1] == ' ' || w[i + 1] == 0.0f)) {
- i++;
- }
- return limit - i - 1;
- }
- }
-
- /**
- * Returns the length of the substring.
- *
- * This only works if the MeasuredText is computed with computeForMeasurement.
- * Undefined behavior in other case.
- */
- @FloatRange(from = 0.0f) float measure(int start, int limit) {
- float width = 0;
- float[] w = mWidths.getRawArray();
- for (int i = start; i < limit; ++i) {
- width += w[i];
- }
- return width;
- }
-
- private static native /* Non Zero */ long nInitBuilder();
-
- /**
- * Apply style to make native measured text.
- *
- * @param nativeBuilderPtr The native MeasuredText builder pointer.
- * @param paintPtr The native paint pointer to be applied.
- * @param start The start offset in the copied buffer.
- * @param end The end offset in the copied buffer.
- * @param isRtl True if the text is RTL.
- */
- private static native void nAddStyleRun(/* Non Zero */ long nativeBuilderPtr,
- /* Non Zero */ long paintPtr,
- @IntRange(from = 0) int start,
- @IntRange(from = 0) int end,
- boolean isRtl);
-
- /**
- * Apply ReplacementRun to make native measured text.
- *
- * @param nativeBuilderPtr The native MeasuredText builder pointer.
- * @param paintPtr The native paint pointer to be applied.
- * @param start The start offset in the copied buffer.
- * @param end The end offset in the copied buffer.
- * @param width The width of the replacement.
- */
- private static native void nAddReplacementRun(/* Non Zero */ long nativeBuilderPtr,
- /* Non Zero */ long paintPtr,
- @IntRange(from = 0) int start,
- @IntRange(from = 0) int end,
- @FloatRange(from = 0) float width);
-
- private static native long nBuildNativeMeasuredText(/* Non Zero */ long nativeBuilderPtr,
- @NonNull char[] text);
-
- private static native void nFreeBuilder(/* Non Zero */ long nativeBuilderPtr);
-
- @CriticalNative
- private static native /* Non Zero */ long nGetReleaseFunc();
}
diff --git a/core/java/android/text/PremeasuredText.java b/core/java/android/text/PremeasuredText.java
deleted file mode 100644
index 465314d..0000000
--- a/core/java/android/text/PremeasuredText.java
+++ /dev/null
@@ -1,272 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.text;
-
-import android.annotation.IntRange;
-import android.annotation.NonNull;
-import android.util.IntArray;
-
-import com.android.internal.util.ArrayUtils;
-import com.android.internal.util.Preconditions;
-
-import java.util.ArrayList;
-
-/**
- * A text which has already been measured.
- *
- * TODO: Rename to better name? e.g. MeasuredText, FrozenText etc.
- */
-public class PremeasuredText implements Spanned {
- private static final char LINE_FEED = '\n';
-
- // The original text.
- private final @NonNull CharSequence mText;
-
- // The inclusive start offset of the measuring target.
- private final @IntRange(from = 0) int mStart;
-
- // The exclusive end offset of the measuring target.
- private final @IntRange(from = 0) int mEnd;
-
- // The TextPaint used for measurement.
- private final @NonNull TextPaint mPaint;
-
- // The requested text direction.
- private final @NonNull TextDirectionHeuristic mTextDir;
-
- // The measured paragraph texts.
- private final @NonNull MeasuredText[] mMeasuredTexts;
-
- // The sorted paragraph end offsets.
- private final @NonNull int[] mParagraphBreakPoints;
-
- /**
- * Build PremeasuredText from the text.
- *
- * @param text The text to be measured.
- * @param paint The paint to be used for drawing.
- * @param textDir The text direction.
- * @return The measured text.
- */
- public static @NonNull PremeasuredText build(@NonNull CharSequence text,
- @NonNull TextPaint paint,
- @NonNull TextDirectionHeuristic textDir) {
- return PremeasuredText.build(text, paint, textDir, 0, text.length());
- }
-
- /**
- * Build PremeasuredText from the specific range of the text..
- *
- * @param text The text to be measured.
- * @param paint The paint to be used for drawing.
- * @param textDir The text direction.
- * @param start The inclusive start offset of the text.
- * @param end The exclusive start offset of the text.
- * @return The measured text.
- */
- public static @NonNull PremeasuredText build(@NonNull CharSequence text,
- @NonNull TextPaint paint,
- @NonNull TextDirectionHeuristic textDir,
- @IntRange(from = 0) int start,
- @IntRange(from = 0) int end) {
- Preconditions.checkNotNull(text);
- Preconditions.checkNotNull(paint);
- Preconditions.checkNotNull(textDir);
- Preconditions.checkArgumentInRange(start, 0, text.length(), "start");
- Preconditions.checkArgumentInRange(end, 0, text.length(), "end");
-
- final IntArray paragraphEnds = new IntArray();
- final ArrayList<MeasuredText> measuredTexts = new ArrayList<>();
-
- int paraEnd = 0;
- for (int paraStart = start; paraStart < end; paraStart = paraEnd) {
- paraEnd = TextUtils.indexOf(text, LINE_FEED, paraStart, end);
- if (paraEnd < 0) {
- // No LINE_FEED(U+000A) character found. Use end of the text as the paragraph end.
- paraEnd = end;
- } else {
- paraEnd++; // Includes LINE_FEED(U+000A) to the prev paragraph.
- }
-
- paragraphEnds.add(paraEnd);
- measuredTexts.add(MeasuredText.buildForStaticLayout(
- paint, text, paraStart, paraEnd, textDir, null /* no recycle */));
- }
-
- return new PremeasuredText(text, start, end, paint, textDir,
- measuredTexts.toArray(new MeasuredText[measuredTexts.size()]),
- paragraphEnds.toArray());
- }
-
- // Use PremeasuredText.build instead.
- private PremeasuredText(@NonNull CharSequence text,
- @IntRange(from = 0) int start,
- @IntRange(from = 0) int end,
- @NonNull TextPaint paint,
- @NonNull TextDirectionHeuristic textDir,
- @NonNull MeasuredText[] measuredTexts,
- @NonNull int[] paragraphBreakPoints) {
- mText = text;
- mStart = start;
- mEnd = end;
- mPaint = paint;
- mMeasuredTexts = measuredTexts;
- mParagraphBreakPoints = paragraphBreakPoints;
- mTextDir = textDir;
- }
-
- /**
- * Return the underlying text.
- */
- public @NonNull CharSequence getText() {
- return mText;
- }
-
- /**
- * Returns the inclusive start offset of measured region.
- */
- public @IntRange(from = 0) int getStart() {
- return mStart;
- }
-
- /**
- * Returns the exclusive end offset of measured region.
- */
- public @IntRange(from = 0) int getEnd() {
- return mEnd;
- }
-
- /**
- * Returns the text direction associated with char sequence.
- */
- public @NonNull TextDirectionHeuristic getTextDir() {
- return mTextDir;
- }
-
- /**
- * Returns the paint used to measure this text.
- */
- public @NonNull TextPaint getPaint() {
- return mPaint;
- }
-
- /**
- * Returns the length of the paragraph of this text.
- */
- public @IntRange(from = 0) int getParagraphCount() {
- return mParagraphBreakPoints.length;
- }
-
- /**
- * Returns the paragraph start offset of the text.
- */
- public @IntRange(from = 0) int getParagraphStart(@IntRange(from = 0) int paraIndex) {
- Preconditions.checkArgumentInRange(paraIndex, 0, getParagraphCount(), "paraIndex");
- return paraIndex == 0 ? mStart : mParagraphBreakPoints[paraIndex - 1];
- }
-
- /**
- * Returns the paragraph end offset of the text.
- */
- public @IntRange(from = 0) int getParagraphEnd(@IntRange(from = 0) int paraIndex) {
- Preconditions.checkArgumentInRange(paraIndex, 0, getParagraphCount(), "paraIndex");
- return mParagraphBreakPoints[paraIndex];
- }
-
- /** @hide */
- public @NonNull MeasuredText getMeasuredText(@IntRange(from = 0) int paraIndex) {
- return mMeasuredTexts[paraIndex];
- }
-
- ///////////////////////////////////////////////////////////////////////////////////////////////
- // Spanned overrides
- //
- // Just proxy for underlying mText if appropriate.
-
- @Override
- public <T> T[] getSpans(int start, int end, Class<T> type) {
- if (mText instanceof Spanned) {
- return ((Spanned) mText).getSpans(start, end, type);
- } else {
- return ArrayUtils.emptyArray(type);
- }
- }
-
- @Override
- public int getSpanStart(Object tag) {
- if (mText instanceof Spanned) {
- return ((Spanned) mText).getSpanStart(tag);
- } else {
- return -1;
- }
- }
-
- @Override
- public int getSpanEnd(Object tag) {
- if (mText instanceof Spanned) {
- return ((Spanned) mText).getSpanEnd(tag);
- } else {
- return -1;
- }
- }
-
- @Override
- public int getSpanFlags(Object tag) {
- if (mText instanceof Spanned) {
- return ((Spanned) mText).getSpanFlags(tag);
- } else {
- return 0;
- }
- }
-
- @Override
- public int nextSpanTransition(int start, int limit, Class type) {
- if (mText instanceof Spanned) {
- return ((Spanned) mText).nextSpanTransition(start, limit, type);
- } else {
- return mText.length();
- }
- }
-
- ///////////////////////////////////////////////////////////////////////////////////////////////
- // CharSequence overrides.
- //
- // Just proxy for underlying mText.
-
- @Override
- public int length() {
- return mText.length();
- }
-
- @Override
- public char charAt(int index) {
- // TODO: Should this be index + mStart ?
- return mText.charAt(index);
- }
-
- @Override
- public CharSequence subSequence(int start, int end) {
- // TODO: return PremeasuredText.
- // TODO: Should this be index + mStart, end + mStart ?
- return mText.subSequence(start, end);
- }
-
- @Override
- public String toString() {
- return mText.toString();
- }
-}
diff --git a/core/java/android/text/StaticLayout.java b/core/java/android/text/StaticLayout.java
index d69b119..36bec86 100644
--- a/core/java/android/text/StaticLayout.java
+++ b/core/java/android/text/StaticLayout.java
@@ -55,7 +55,8 @@
* First, call nInit to setup native line breaker object. Then, for each paragraph, do the
* following:
*
- * - Create MeasuredText by MeasuredText.buildForStaticLayout which measures in native.
+ * - Create MeasuredParagraph by MeasuredParagraph.buildForStaticLayout which measures in
+ * native.
* - Run nComputeLineBreaks() to obtain line breaks for the paragraph.
*
* After all paragraphs, call finish() to release expensive buffers.
@@ -650,34 +651,34 @@
b.mJustificationMode != Layout.JUSTIFICATION_MODE_NONE,
indents, mLeftPaddings, mRightPaddings);
- PremeasuredText premeasured = null;
+ MeasuredText measured = null;
final Spanned spanned;
- if (source instanceof PremeasuredText) {
- premeasured = (PremeasuredText) source;
+ if (source instanceof MeasuredText) {
+ measured = (MeasuredText) source;
- final CharSequence original = premeasured.getText();
+ final CharSequence original = measured.getText();
spanned = (original instanceof Spanned) ? (Spanned) original : null;
- if (bufStart != premeasured.getStart() || bufEnd != premeasured.getEnd()) {
+ if (bufStart != measured.getStart() || bufEnd != measured.getEnd()) {
// The buffer position has changed. Re-measure here.
- premeasured = PremeasuredText.build(original, paint, textDir, bufStart, bufEnd);
+ measured = MeasuredText.build(original, paint, textDir, bufStart, bufEnd);
} else {
- // We can use premeasured information.
+ // We can use measured information.
- // Overwrite with the one when premeasured.
+ // Overwrite with the one when emeasured.
// TODO: Give an option for developer not to overwrite and measure again here?
- textDir = premeasured.getTextDir();
- paint = premeasured.getPaint();
+ textDir = measured.getTextDir();
+ paint = measured.getPaint();
}
} else {
- premeasured = PremeasuredText.build(source, paint, textDir, bufStart, bufEnd);
+ measured = MeasuredText.build(source, paint, textDir, bufStart, bufEnd);
spanned = (source instanceof Spanned) ? (Spanned) source : null;
}
try {
- for (int paraIndex = 0; paraIndex < premeasured.getParagraphCount(); paraIndex++) {
- final int paraStart = premeasured.getParagraphStart(paraIndex);
- final int paraEnd = premeasured.getParagraphEnd(paraIndex);
+ for (int paraIndex = 0; paraIndex < measured.getParagraphCount(); paraIndex++) {
+ final int paraStart = measured.getParagraphStart(paraIndex);
+ final int paraEnd = measured.getParagraphEnd(paraIndex);
int firstWidthLineCount = 1;
int firstWidth = outerWidth;
@@ -743,10 +744,10 @@
}
}
- final MeasuredText measured = premeasured.getMeasuredText(paraIndex);
- final char[] chs = measured.getChars();
- final int[] spanEndCache = measured.getSpanEndCache().getRawArray();
- final int[] fmCache = measured.getFontMetrics().getRawArray();
+ final MeasuredParagraph measuredPara = measured.getMeasuredParagraph(paraIndex);
+ final char[] chs = measuredPara.getChars();
+ final int[] spanEndCache = measuredPara.getSpanEndCache().getRawArray();
+ final int[] fmCache = measuredPara.getFontMetrics().getRawArray();
// TODO: Stop keeping duplicated width copy in native and Java.
widths.resize(chs.length);
@@ -759,7 +760,7 @@
// Inputs
chs,
- measured.getNativePtr(),
+ measuredPara.getNativePtr(),
paraEnd - paraStart,
firstWidth,
firstWidthLineCount,
@@ -863,7 +864,7 @@
v = out(source, here, endPos,
ascent, descent, fmTop, fmBottom,
v, spacingmult, spacingadd, chooseHt, chooseHtv, fm,
- flags[breakIndex], needMultiply, measured, bufEnd,
+ flags[breakIndex], needMultiply, measuredPara, bufEnd,
includepad, trackpad, addLastLineSpacing, chs, widths.getRawArray(),
paraStart, ellipsize, ellipsizedWidth, lineWidths[breakIndex],
paint, moreChars);
@@ -894,8 +895,8 @@
if ((bufEnd == bufStart || source.charAt(bufEnd - 1) == CHAR_NEW_LINE)
&& mLineCount < mMaximumVisibleLineCount) {
- final MeasuredText measured =
- MeasuredText.buildForBidi(source, bufEnd, bufEnd, textDir, null);
+ final MeasuredParagraph measuredPara =
+ MeasuredParagraph.buildForBidi(source, bufEnd, bufEnd, textDir, null);
paint.getFontMetricsInt(fm);
v = out(source,
bufEnd, bufEnd, fm.ascent, fm.descent,
@@ -903,7 +904,7 @@
v,
spacingmult, spacingadd, null,
null, fm, 0,
- needMultiply, measured, bufEnd,
+ needMultiply, measuredPara, bufEnd,
includepad, trackpad, addLastLineSpacing, null,
null, bufStart, ellipsize,
ellipsizedWidth, 0, paint, false);
@@ -918,7 +919,7 @@
private int out(final CharSequence text, final int start, final int end, int above, int below,
int top, int bottom, int v, final float spacingmult, final float spacingadd,
final LineHeightSpan[] chooseHt, final int[] chooseHtv, final Paint.FontMetricsInt fm,
- final int flags, final boolean needMultiply, @NonNull final MeasuredText measured,
+ final int flags, final boolean needMultiply, @NonNull final MeasuredParagraph measured,
final int bufEnd, final boolean includePad, final boolean trackPad,
final boolean addLastLineLineSpacing, final char[] chs, final float[] widths,
final int widthStart, final TextUtils.TruncateAt ellipsize, final float ellipsisWidth,
diff --git a/core/java/android/text/TextUtils.java b/core/java/android/text/TextUtils.java
index 9c9fbf2..409e514 100644
--- a/core/java/android/text/TextUtils.java
+++ b/core/java/android/text/TextUtils.java
@@ -1250,10 +1250,10 @@
@NonNull String ellipsis) {
final int len = text.length();
- MeasuredText mt = null;
- MeasuredText resultMt = null;
+ MeasuredParagraph mt = null;
+ MeasuredParagraph resultMt = null;
try {
- mt = MeasuredText.buildForMeasurement(paint, text, 0, text.length(), textDir, mt);
+ mt = MeasuredParagraph.buildForMeasurement(paint, text, 0, text.length(), textDir, mt);
float width = mt.getWholeWidth();
if (width <= avail) {
@@ -1332,7 +1332,7 @@
if (remaining == 0) { // All text is gone.
textFits = true;
} else {
- resultMt = MeasuredText.buildForMeasurement(
+ resultMt = MeasuredParagraph.buildForMeasurement(
paint, result, 0, result.length(), textDir, resultMt);
width = resultMt.getWholeWidth();
if (width <= avail) {
@@ -1479,11 +1479,11 @@
public static CharSequence commaEllipsize(CharSequence text, TextPaint p,
float avail, String oneMore, String more, TextDirectionHeuristic textDir) {
- MeasuredText mt = null;
- MeasuredText tempMt = null;
+ MeasuredParagraph mt = null;
+ MeasuredParagraph tempMt = null;
try {
int len = text.length();
- mt = MeasuredText.buildForMeasurement(p, text, 0, len, textDir, mt);
+ mt = MeasuredParagraph.buildForMeasurement(p, text, 0, len, textDir, mt);
final float width = mt.getWholeWidth();
if (width <= avail) {
return text;
@@ -1523,7 +1523,7 @@
}
// XXX this is probably ok, but need to look at it more
- tempMt = MeasuredText.buildForMeasurement(
+ tempMt = MeasuredParagraph.buildForMeasurement(
p, format, 0, format.length(), textDir, tempMt);
float moreWid = tempMt.getWholeWidth();
diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java
index 1d5392e..62f9717 100644
--- a/core/java/android/util/FeatureFlagUtils.java
+++ b/core/java/android/util/FeatureFlagUtils.java
@@ -39,11 +39,12 @@
DEFAULT_FLAGS = new HashMap<>();
DEFAULT_FLAGS.put("device_info_v2", "true");
DEFAULT_FLAGS.put("settings_search_v2", "true");
- DEFAULT_FLAGS.put("settings_app_info_v2", "false");
+ DEFAULT_FLAGS.put("settings_app_info_v2", "true");
DEFAULT_FLAGS.put("settings_connected_device_v2", "true");
DEFAULT_FLAGS.put("settings_battery_v2", "false");
DEFAULT_FLAGS.put("settings_battery_display_app_list", "false");
DEFAULT_FLAGS.put("settings_security_settings_v2", "true");
+ DEFAULT_FLAGS.put("settings_zone_picker_v2", "false");
}
/**
diff --git a/core/java/android/util/TimeUtils.java b/core/java/android/util/TimeUtils.java
index cc4a0b6..84ae20b 100644
--- a/core/java/android/util/TimeUtils.java
+++ b/core/java/android/util/TimeUtils.java
@@ -18,30 +18,18 @@
import android.os.SystemClock;
-import java.io.PrintWriter;
-import java.text.SimpleDateFormat;
-import java.util.ArrayList;
-import java.util.Calendar;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.Date;
-import java.util.List;
import libcore.util.TimeZoneFinder;
import libcore.util.ZoneInfoDB;
+import java.io.PrintWriter;
+import java.text.SimpleDateFormat;
+import java.util.Calendar;
+import java.util.Date;
/**
* A class containing utility methods related to time zones.
*/
public class TimeUtils {
/** @hide */ public TimeUtils() {}
- private static final boolean DBG = false;
- private static final String TAG = "TimeUtils";
-
- /** Cached results of getTimeZonesWithUniqueOffsets */
- private static final Object sLastUniqueLockObj = new Object();
- private static List<String> sLastUniqueZoneOffsets = null;
- private static String sLastUniqueCountry = null;
-
/** {@hide} */
private static SimpleDateFormat sLoggingFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
@@ -76,86 +64,6 @@
}
/**
- * Returns an immutable list of unique time zone IDs for the country.
- *
- * @param country to find
- * @return unmodifiable list of unique time zones, maybe empty but never null.
- * @hide
- */
- public static List<String> getTimeZoneIdsWithUniqueOffsets(String country) {
- synchronized(sLastUniqueLockObj) {
- if ((country != null) && country.equals(sLastUniqueCountry)) {
- if (DBG) {
- Log.d(TAG, "getTimeZonesWithUniqueOffsets(" +
- country + "): return cached version");
- }
- return sLastUniqueZoneOffsets;
- }
- }
-
- Collection<android.icu.util.TimeZone> zones = getIcuTimeZones(country);
- ArrayList<android.icu.util.TimeZone> uniqueTimeZones = new ArrayList<>();
- for (android.icu.util.TimeZone zone : zones) {
- // See if we already have this offset,
- // Using slow but space efficient and these are small.
- boolean found = false;
- for (int i = 0; i < uniqueTimeZones.size(); i++) {
- if (uniqueTimeZones.get(i).getRawOffset() == zone.getRawOffset()) {
- found = true;
- break;
- }
- }
- if (!found) {
- if (DBG) {
- Log.d(TAG, "getTimeZonesWithUniqueOffsets: add unique offset=" +
- zone.getRawOffset() + " zone.getID=" + zone.getID());
- }
- uniqueTimeZones.add(zone);
- }
- }
-
- synchronized(sLastUniqueLockObj) {
- // Cache the last result
- sLastUniqueZoneOffsets = extractZoneIds(uniqueTimeZones);
- sLastUniqueCountry = country;
-
- return sLastUniqueZoneOffsets;
- }
- }
-
- private static List<String> extractZoneIds(List<android.icu.util.TimeZone> timeZones) {
- List<String> ids = new ArrayList<>(timeZones.size());
- for (android.icu.util.TimeZone timeZone : timeZones) {
- ids.add(timeZone.getID());
- }
- return Collections.unmodifiableList(ids);
- }
-
- /**
- * Returns an immutable list of frozen ICU time zones for the country.
- *
- * @param countryIso is a two character country code.
- * @return TimeZone list, maybe empty but never null.
- * @hide
- */
- private static List<android.icu.util.TimeZone> getIcuTimeZones(String countryIso) {
- if (countryIso == null) {
- if (DBG) Log.d(TAG, "getIcuTimeZones(null): return empty list");
- return Collections.emptyList();
- }
- List<android.icu.util.TimeZone> timeZones =
- TimeZoneFinder.getInstance().lookupTimeZonesByCountry(countryIso);
- if (timeZones == null) {
- if (DBG) {
- Log.d(TAG, "getIcuTimeZones(" + countryIso
- + "): returned null, converting to empty list");
- }
- return Collections.emptyList();
- }
- return timeZones;
- }
-
- /**
* Returns a String indicating the version of the time zone database currently
* in use. The format of the string is dependent on the underlying time zone
* database implementation, but will typically contain the year in which the database
diff --git a/core/java/android/util/apk/ApkSignatureVerifier.java b/core/java/android/util/apk/ApkSignatureVerifier.java
index 8146729..555c474 100644
--- a/core/java/android/util/apk/ApkSignatureVerifier.java
+++ b/core/java/android/util/apk/ApkSignatureVerifier.java
@@ -25,6 +25,7 @@
import android.content.pm.PackageParser;
import android.content.pm.PackageParser.PackageParserException;
+import android.content.pm.PackageParser.SigningDetails.SignatureSchemeVersion;
import android.content.pm.Signature;
import android.os.Trace;
import android.util.jar.StrictJarFile;
@@ -52,10 +53,6 @@
*/
public class ApkSignatureVerifier {
- public static final int VERSION_JAR_SIGNATURE_SCHEME = 1;
- public static final int VERSION_APK_SIGNATURE_SCHEME_V2 = 2;
- public static final int VERSION_APK_SIGNATURE_SCHEME_V3 = 3;
-
private static final AtomicReference<byte[]> sBuffer = new AtomicReference<>();
/**
@@ -63,10 +60,11 @@
*
* @throws PackageParserException if the APK's signature failed to verify.
*/
- public static Result verify(String apkPath, int minSignatureSchemeVersion)
+ public static PackageParser.SigningDetails verify(String apkPath,
+ @SignatureSchemeVersion int minSignatureSchemeVersion)
throws PackageParserException {
- if (minSignatureSchemeVersion > VERSION_APK_SIGNATURE_SCHEME_V3) {
+ if (minSignatureSchemeVersion > SignatureSchemeVersion.SIGNING_BLOCK_V3) {
// V3 and before are older than the requested minimum signing version
throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
"No signature found in package of version " + minSignatureSchemeVersion
@@ -80,10 +78,11 @@
ApkSignatureSchemeV3Verifier.verify(apkPath);
Certificate[][] signerCerts = new Certificate[][] { vSigner.certs };
Signature[] signerSigs = convertToSignatures(signerCerts);
- return new Result(signerCerts, signerSigs, VERSION_APK_SIGNATURE_SCHEME_V3);
+ return new PackageParser.SigningDetails(signerSigs,
+ SignatureSchemeVersion.SIGNING_BLOCK_V3);
} catch (SignatureNotFoundException e) {
// not signed with v2, try older if allowed
- if (minSignatureSchemeVersion >= VERSION_APK_SIGNATURE_SCHEME_V3) {
+ if (minSignatureSchemeVersion >= SignatureSchemeVersion.SIGNING_BLOCK_V3) {
throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
"No APK Signature Scheme v3 signature in package " + apkPath, e);
}
@@ -97,7 +96,7 @@
}
// redundant, protective version check
- if (minSignatureSchemeVersion > VERSION_APK_SIGNATURE_SCHEME_V2) {
+ if (minSignatureSchemeVersion > SignatureSchemeVersion.SIGNING_BLOCK_V2) {
// V2 and before are older than the requested minimum signing version
throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
"No signature found in package of version " + minSignatureSchemeVersion
@@ -110,10 +109,11 @@
Certificate[][] signerCerts = ApkSignatureSchemeV2Verifier.verify(apkPath);
Signature[] signerSigs = convertToSignatures(signerCerts);
- return new Result(signerCerts, signerSigs, VERSION_APK_SIGNATURE_SCHEME_V2);
+ return new PackageParser.SigningDetails(
+ signerSigs, SignatureSchemeVersion.SIGNING_BLOCK_V2);
} catch (SignatureNotFoundException e) {
// not signed with v2, try older if allowed
- if (minSignatureSchemeVersion >= VERSION_APK_SIGNATURE_SCHEME_V2) {
+ if (minSignatureSchemeVersion >= SignatureSchemeVersion.SIGNING_BLOCK_V2) {
throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
"No APK Signature Scheme v2 signature in package " + apkPath, e);
}
@@ -127,7 +127,7 @@
}
// redundant, protective version check
- if (minSignatureSchemeVersion > VERSION_JAR_SIGNATURE_SCHEME) {
+ if (minSignatureSchemeVersion > SignatureSchemeVersion.JAR) {
// V1 and is older than the requested minimum signing version
throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
"No signature found in package of version " + minSignatureSchemeVersion
@@ -145,7 +145,8 @@
*
* @throws PackageParserException if there was a problem collecting certificates
*/
- private static Result verifyV1Signature(String apkPath, boolean verifyFull)
+ private static PackageParser.SigningDetails verifyV1Signature(
+ String apkPath, boolean verifyFull)
throws PackageParserException {
StrictJarFile jarFile = null;
@@ -211,7 +212,7 @@
}
}
}
- return new Result(lastCerts, lastSigs, VERSION_JAR_SIGNATURE_SCHEME);
+ return new PackageParser.SigningDetails(lastSigs, SignatureSchemeVersion.JAR);
} catch (GeneralSecurityException e) {
throw new PackageParserException(INSTALL_PARSE_FAILED_CERTIFICATE_ENCODING,
"Failed to collect certificates from " + apkPath, e);
@@ -289,10 +290,11 @@
* @throws PackageParserException if the APK's signature failed to verify.
* or greater is not found, except in the case of no JAR signature.
*/
- public static Result plsCertsNoVerifyOnlyCerts(String apkPath, int minSignatureSchemeVersion)
+ public static PackageParser.SigningDetails plsCertsNoVerifyOnlyCerts(
+ String apkPath, int minSignatureSchemeVersion)
throws PackageParserException {
- if (minSignatureSchemeVersion > VERSION_APK_SIGNATURE_SCHEME_V3) {
+ if (minSignatureSchemeVersion > SignatureSchemeVersion.SIGNING_BLOCK_V3) {
// V3 and before are older than the requested minimum signing version
throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
"No signature found in package of version " + minSignatureSchemeVersion
@@ -306,10 +308,11 @@
ApkSignatureSchemeV3Verifier.plsCertsNoVerifyOnlyCerts(apkPath);
Certificate[][] signerCerts = new Certificate[][] { vSigner.certs };
Signature[] signerSigs = convertToSignatures(signerCerts);
- return new Result(signerCerts, signerSigs, VERSION_APK_SIGNATURE_SCHEME_V3);
+ return new PackageParser.SigningDetails(signerSigs,
+ SignatureSchemeVersion.SIGNING_BLOCK_V3);
} catch (SignatureNotFoundException e) {
// not signed with v2, try older if allowed
- if (minSignatureSchemeVersion >= VERSION_APK_SIGNATURE_SCHEME_V3) {
+ if (minSignatureSchemeVersion >= SignatureSchemeVersion.SIGNING_BLOCK_V3) {
throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
"No APK Signature Scheme v3 signature in package " + apkPath, e);
}
@@ -323,7 +326,7 @@
}
// redundant, protective version check
- if (minSignatureSchemeVersion > VERSION_APK_SIGNATURE_SCHEME_V2) {
+ if (minSignatureSchemeVersion > SignatureSchemeVersion.SIGNING_BLOCK_V2) {
// V2 and before are older than the requested minimum signing version
throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
"No signature found in package of version " + minSignatureSchemeVersion
@@ -336,10 +339,11 @@
Certificate[][] signerCerts =
ApkSignatureSchemeV2Verifier.plsCertsNoVerifyOnlyCerts(apkPath);
Signature[] signerSigs = convertToSignatures(signerCerts);
- return new Result(signerCerts, signerSigs, VERSION_APK_SIGNATURE_SCHEME_V2);
+ return new PackageParser.SigningDetails(signerSigs,
+ SignatureSchemeVersion.SIGNING_BLOCK_V2);
} catch (SignatureNotFoundException e) {
// not signed with v2, try older if allowed
- if (minSignatureSchemeVersion >= VERSION_APK_SIGNATURE_SCHEME_V2) {
+ if (minSignatureSchemeVersion >= SignatureSchemeVersion.SIGNING_BLOCK_V2) {
throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
"No APK Signature Scheme v2 signature in package " + apkPath, e);
}
@@ -353,7 +357,7 @@
}
// redundant, protective version check
- if (minSignatureSchemeVersion > VERSION_JAR_SIGNATURE_SCHEME) {
+ if (minSignatureSchemeVersion > SignatureSchemeVersion.JAR) {
// V1 and is older than the requested minimum signing version
throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
"No signature found in package of version " + minSignatureSchemeVersion
@@ -363,19 +367,4 @@
// v2 didn't work, try jarsigner
return verifyV1Signature(apkPath, false);
}
-
- /**
- * Result of a successful APK verification operation.
- */
- public static class Result {
- public final Certificate[][] certs;
- public final Signature[] sigs;
- public final int signatureSchemeVersion;
-
- public Result(Certificate[][] certs, Signature[] sigs, int signingVersion) {
- this.certs = certs;
- this.sigs = sigs;
- this.signatureSchemeVersion = signingVersion;
- }
- }
}
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index 7001067..8c70322 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -147,7 +147,7 @@
void exitKeyguardSecurely(IOnKeyguardExitResult callback);
boolean isKeyguardLocked();
boolean isKeyguardSecure();
- void dismissKeyguard(IKeyguardDismissCallback callback);
+ void dismissKeyguard(IKeyguardDismissCallback callback, CharSequence message);
// Requires INTERACT_ACROSS_USERS_FULL permission
void setSwitchingUser(boolean switching);
diff --git a/core/java/android/view/KeyEvent.java b/core/java/android/view/KeyEvent.java
index a2147b7..ed2b8b6 100644
--- a/core/java/android/view/KeyEvent.java
+++ b/core/java/android/view/KeyEvent.java
@@ -804,8 +804,7 @@
public static final int KEYCODE_SYSTEM_NAVIGATION_LEFT = 282;
/** Key code constant: Consumed by the system for navigation right */
public static final int KEYCODE_SYSTEM_NAVIGATION_RIGHT = 283;
- /** Key code constant: Show all apps
- * @hide */
+ /** Key code constant: Show all apps */
public static final int KEYCODE_ALL_APPS = 284;
private static final int LAST_KEYCODE = KEYCODE_ALL_APPS;
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index f62189e..ad71b58 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -4045,6 +4045,12 @@
private CharSequence mContentDescription;
/**
+ * If this view represents a distinct part of the window, it can have a title that labels the
+ * area.
+ */
+ private CharSequence mAccessibilityPaneTitle;
+
+ /**
* Specifies the id of a view for which this view serves as a label for
* accessibility purposes.
*/
@@ -4412,7 +4418,6 @@
private CheckForLongPress mPendingCheckForLongPress;
private CheckForTap mPendingCheckForTap = null;
private PerformClick mPerformClick;
- private SendViewScrolledAccessibilityEvent mSendViewScrolledAccessibilityEvent;
private UnsetPressedState mUnsetPressedState;
@@ -5409,6 +5414,11 @@
setScreenReaderFocusable(a.getBoolean(attr, false));
}
break;
+ case R.styleable.View_accessibilityPaneTitle:
+ if (a.peekValue(attr) != null) {
+ setAccessibilityPaneTitle(a.getString(attr));
+ }
+ break;
}
}
@@ -7164,7 +7174,7 @@
if (gainFocus) {
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED);
} else {
- notifyViewAccessibilityStateChangedIfNeeded(
+ notifyAccessibilityStateChanged(
AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
}
@@ -7218,6 +7228,34 @@
}
/**
+ * If this view is a visually distinct portion of a window, for example the content view of
+ * a fragment that is replaced, it is considered a pane for accessibility purposes. In order
+ * for accessibility services to understand the views role, and to announce its title as
+ * appropriate, such views should have pane titles.
+ *
+ * @param accessibilityPaneTitle The pane's title.
+ *
+ * {@see AccessibilityNodeInfo#setPaneTitle(CharSequence)}
+ */
+ public void setAccessibilityPaneTitle(CharSequence accessibilityPaneTitle) {
+ if (!TextUtils.equals(accessibilityPaneTitle, mAccessibilityPaneTitle)) {
+ mAccessibilityPaneTitle = accessibilityPaneTitle;
+ notifyAccessibilityStateChanged(AccessibilityEvent.CONTENT_CHANGE_TYPE_PANE_TITLE);
+ }
+ }
+
+ /**
+ * Get the title of the pane for purposes of accessibility.
+ *
+ * @return The current pane title.
+ *
+ * {@see #setAccessibilityPaneTitle}.
+ */
+ public CharSequence getAccessibilityPaneTitle() {
+ return mAccessibilityPaneTitle;
+ }
+
+ /**
* Sends an accessibility event of the given type. If accessibility is
* not enabled this method has no effect. The default implementation calls
* {@link #onInitializeAccessibilityEvent(AccessibilityEvent)} first
@@ -8514,6 +8552,7 @@
info.addAction(AccessibilityAction.ACTION_SHOW_ON_SCREEN);
populateAccessibilityNodeInfoDrawingOrderInParent(info);
+ info.setPaneTitle(mAccessibilityPaneTitle);
}
/**
@@ -8834,9 +8873,9 @@
final boolean nonEmptyDesc = contentDescription != null && contentDescription.length() > 0;
if (nonEmptyDesc && getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
- notifySubtreeAccessibilityStateChangedIfNeeded();
+ notifyAccessibilitySubtreeChanged();
} else {
- notifyViewAccessibilityStateChangedIfNeeded(
+ notifyAccessibilityStateChanged(
AccessibilityEvent.CONTENT_CHANGE_TYPE_CONTENT_DESCRIPTION);
}
}
@@ -8869,7 +8908,7 @@
return;
}
mAccessibilityTraversalBeforeId = beforeId;
- notifyViewAccessibilityStateChangedIfNeeded(
+ notifyAccessibilityStateChanged(
AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
}
@@ -8913,7 +8952,7 @@
return;
}
mAccessibilityTraversalAfterId = afterId;
- notifyViewAccessibilityStateChangedIfNeeded(
+ notifyAccessibilityStateChanged(
AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
}
@@ -8956,7 +8995,7 @@
&& mID == View.NO_ID) {
mID = generateViewId();
}
- notifyViewAccessibilityStateChangedIfNeeded(
+ notifyAccessibilityStateChanged(
AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
}
@@ -10457,8 +10496,7 @@
if (pflags3 != mPrivateFlags3) {
mPrivateFlags3 = pflags3;
- notifyViewAccessibilityStateChangedIfNeeded(
- AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
+ notifyAccessibilityStateChanged(AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
}
}
@@ -11286,7 +11324,7 @@
mPrivateFlags2 &= ~PFLAG2_ACCESSIBILITY_LIVE_REGION_MASK;
mPrivateFlags2 |= (mode << PFLAG2_ACCESSIBILITY_LIVE_REGION_SHIFT)
& PFLAG2_ACCESSIBILITY_LIVE_REGION_MASK;
- notifyViewAccessibilityStateChangedIfNeeded(
+ notifyAccessibilityStateChanged(
AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
}
}
@@ -11344,9 +11382,9 @@
mPrivateFlags2 |= (mode << PFLAG2_IMPORTANT_FOR_ACCESSIBILITY_SHIFT)
& PFLAG2_IMPORTANT_FOR_ACCESSIBILITY_MASK;
if (!maySkipNotify || oldIncludeForAccessibility != includeForAccessibility()) {
- notifySubtreeAccessibilityStateChangedIfNeeded();
+ notifyAccessibilitySubtreeChanged();
} else {
- notifyViewAccessibilityStateChangedIfNeeded(
+ notifyAccessibilityStateChanged(
AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
}
}
@@ -11405,6 +11443,7 @@
* {@link #getAccessibilityLiveRegion()} is not
* {@link #ACCESSIBILITY_LIVE_REGION_NONE}.
* </ul>
+ * <li>Has an accessibility pane title, see {@link #setAccessibilityPaneTitle}</li>
* </ol>
*
* @return Whether the view is exposed for accessibility.
@@ -11431,7 +11470,8 @@
return mode == IMPORTANT_FOR_ACCESSIBILITY_YES || isActionableForAccessibility()
|| hasListenersForAccessibility() || getAccessibilityNodeProvider() != null
- || getAccessibilityLiveRegion() != ACCESSIBILITY_LIVE_REGION_NONE;
+ || getAccessibilityLiveRegion() != ACCESSIBILITY_LIVE_REGION_NONE
+ || (mAccessibilityPaneTitle != null);
}
/**
@@ -11521,25 +11561,8 @@
*
* @hide
*/
- public void notifyViewAccessibilityStateChangedIfNeeded(int changeType) {
- if (!AccessibilityManager.getInstance(mContext).isEnabled() || mAttachInfo == null) {
- return;
- }
- // If this is a live region, we should send a subtree change event
- // from this view immediately. Otherwise, we can let it propagate up.
- if (getAccessibilityLiveRegion() != ACCESSIBILITY_LIVE_REGION_NONE) {
- final AccessibilityEvent event = AccessibilityEvent.obtain();
- event.setEventType(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
- event.setContentChangeTypes(changeType);
- sendAccessibilityEventUnchecked(event);
- } else if (mParent != null) {
- try {
- mParent.notifySubtreeAccessibilityStateChanged(this, this, changeType);
- } catch (AbstractMethodError e) {
- Log.e(VIEW_LOG_TAG, mParent.getClass().getSimpleName() +
- " does not fully implement ViewParent", e);
- }
- }
+ public void notifyAccessibilityStateChanged(int changeType) {
+ notifyAccessibilityStateChanged(this, changeType);
}
/**
@@ -11553,20 +11576,23 @@
*
* @hide
*/
- public void notifySubtreeAccessibilityStateChangedIfNeeded() {
+ public void notifyAccessibilitySubtreeChanged() {
+ if ((mPrivateFlags2 & PFLAG2_SUBTREE_ACCESSIBILITY_STATE_CHANGED) == 0) {
+ mPrivateFlags2 |= PFLAG2_SUBTREE_ACCESSIBILITY_STATE_CHANGED;
+ notifyAccessibilityStateChanged(AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE);
+ }
+ }
+
+ void notifyAccessibilityStateChanged(View source, int changeType) {
if (!AccessibilityManager.getInstance(mContext).isEnabled() || mAttachInfo == null) {
return;
}
- if ((mPrivateFlags2 & PFLAG2_SUBTREE_ACCESSIBILITY_STATE_CHANGED) == 0) {
- mPrivateFlags2 |= PFLAG2_SUBTREE_ACCESSIBILITY_STATE_CHANGED;
- if (mParent != null) {
- try {
- mParent.notifySubtreeAccessibilityStateChanged(
- this, this, AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE);
- } catch (AbstractMethodError e) {
- Log.e(VIEW_LOG_TAG, mParent.getClass().getSimpleName() +
- " does not fully implement ViewParent", e);
- }
+ if (mParent != null) {
+ try {
+ mParent.notifySubtreeAccessibilityStateChanged(this, source, changeType);
+ } catch (AbstractMethodError e) {
+ Log.e(VIEW_LOG_TAG, mParent.getClass().getSimpleName()
+ + " does not fully implement ViewParent", e);
}
}
}
@@ -11588,8 +11614,10 @@
/**
* Reset the flag indicating the accessibility state of the subtree rooted
* at this view changed.
+ *
+ * @hide
*/
- void resetSubtreeAccessibilityStateChanged() {
+ public void resetSubtreeAccessibilityStateChanged() {
mPrivateFlags2 &= ~PFLAG2_SUBTREE_ACCESSIBILITY_STATE_CHANGED;
}
@@ -11750,7 +11778,7 @@
|| getAccessibilitySelectionEnd() != end)
&& (start == end)) {
setAccessibilitySelection(start, end);
- notifyViewAccessibilityStateChangedIfNeeded(
+ notifyAccessibilityStateChanged(
AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
return true;
}
@@ -13744,7 +13772,7 @@
((!(mParent instanceof ViewGroup)) || ((ViewGroup) mParent).isShown())) {
dispatchVisibilityAggregated(newVisibility == VISIBLE);
}
- notifySubtreeAccessibilityStateChangedIfNeeded();
+ notifyAccessibilitySubtreeChanged();
}
}
@@ -13790,13 +13818,13 @@
|| (changed & CLICKABLE) != 0 || (changed & LONG_CLICKABLE) != 0
|| (changed & CONTEXT_CLICKABLE) != 0) {
if (oldIncludeForAccessibility != includeForAccessibility()) {
- notifySubtreeAccessibilityStateChangedIfNeeded();
+ notifyAccessibilitySubtreeChanged();
} else {
- notifyViewAccessibilityStateChangedIfNeeded(
+ notifyAccessibilityStateChanged(
AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
}
} else if ((changed & ENABLED_MASK) != 0) {
- notifyViewAccessibilityStateChangedIfNeeded(
+ notifyAccessibilityStateChanged(
AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
}
}
@@ -13831,10 +13859,13 @@
* @param oldt Previous vertical scroll origin.
*/
protected void onScrollChanged(int l, int t, int oldl, int oldt) {
- notifySubtreeAccessibilityStateChangedIfNeeded();
+ notifyAccessibilitySubtreeChanged();
- if (AccessibilityManager.getInstance(mContext).isEnabled()) {
- postSendViewScrolledAccessibilityEventCallback(l - oldl, t - oldt);
+ ViewRootImpl root = getViewRootImpl();
+ if (root != null) {
+ root.getAccessibilityState()
+ .getSendViewScrolledAccessibilityEvent()
+ .post(this, /* dx */ l - oldl, /* dy */ t - oldt);
}
mBackgroundSizeChanged = true;
@@ -14230,7 +14261,7 @@
invalidateViewProperty(false, true);
invalidateParentIfNeededAndWasQuickRejected();
- notifySubtreeAccessibilityStateChangedIfNeeded();
+ notifyAccessibilitySubtreeChanged();
}
}
@@ -14274,7 +14305,7 @@
invalidateViewProperty(false, true);
invalidateParentIfNeededAndWasQuickRejected();
- notifySubtreeAccessibilityStateChangedIfNeeded();
+ notifyAccessibilitySubtreeChanged();
}
}
@@ -14318,7 +14349,7 @@
invalidateViewProperty(false, true);
invalidateParentIfNeededAndWasQuickRejected();
- notifySubtreeAccessibilityStateChangedIfNeeded();
+ notifyAccessibilitySubtreeChanged();
}
}
@@ -14355,7 +14386,7 @@
invalidateViewProperty(false, true);
invalidateParentIfNeededAndWasQuickRejected();
- notifySubtreeAccessibilityStateChangedIfNeeded();
+ notifyAccessibilitySubtreeChanged();
}
}
@@ -14392,7 +14423,7 @@
invalidateViewProperty(false, true);
invalidateParentIfNeededAndWasQuickRejected();
- notifySubtreeAccessibilityStateChangedIfNeeded();
+ notifyAccessibilitySubtreeChanged();
}
}
@@ -14595,7 +14626,7 @@
if (mTransformationInfo.mAlpha != alpha) {
// Report visibility changes, which can affect children, to accessibility
if ((alpha == 0) ^ (mTransformationInfo.mAlpha == 0)) {
- notifySubtreeAccessibilityStateChangedIfNeeded();
+ notifyAccessibilitySubtreeChanged();
}
mTransformationInfo.mAlpha = alpha;
if (onSetAlpha((int) (alpha * 255))) {
@@ -15097,7 +15128,7 @@
invalidateViewProperty(false, true);
invalidateParentIfNeededAndWasQuickRejected();
- notifySubtreeAccessibilityStateChangedIfNeeded();
+ notifyAccessibilitySubtreeChanged();
}
}
@@ -15131,7 +15162,7 @@
invalidateViewProperty(false, true);
invalidateParentIfNeededAndWasQuickRejected();
- notifySubtreeAccessibilityStateChangedIfNeeded();
+ notifyAccessibilitySubtreeChanged();
}
}
@@ -15301,7 +15332,7 @@
public void invalidateOutline() {
rebuildOutline();
- notifySubtreeAccessibilityStateChangedIfNeeded();
+ notifyAccessibilitySubtreeChanged();
invalidateViewProperty(false, false);
}
@@ -15496,7 +15527,7 @@
}
invalidateParentIfNeeded();
}
- notifySubtreeAccessibilityStateChangedIfNeeded();
+ notifyAccessibilitySubtreeChanged();
}
}
@@ -15544,7 +15575,7 @@
}
invalidateParentIfNeeded();
}
- notifySubtreeAccessibilityStateChangedIfNeeded();
+ notifyAccessibilitySubtreeChanged();
}
}
@@ -16422,18 +16453,6 @@
}
/**
- * Post a callback to send a {@link AccessibilityEvent#TYPE_VIEW_SCROLLED} event.
- * This event is sent at most once every
- * {@link ViewConfiguration#getSendRecurringAccessibilityEventsInterval()}.
- */
- private void postSendViewScrolledAccessibilityEventCallback(int dx, int dy) {
- if (mSendViewScrolledAccessibilityEvent == null) {
- mSendViewScrolledAccessibilityEvent = new SendViewScrolledAccessibilityEvent();
- }
- mSendViewScrolledAccessibilityEvent.post(dx, dy);
- }
-
- /**
* Called by a parent to request that a child update its values for mScrollX
* and mScrollY if necessary. This will typically be done if the child is
* animating a scroll using a {@link android.widget.Scroller Scroller}
@@ -17688,7 +17707,13 @@
removeUnsetPressCallback();
removeLongPressCallback();
removePerformClickCallback();
- cancel(mSendViewScrolledAccessibilityEvent);
+ if (mAttachInfo != null
+ && mAttachInfo.mViewRootImpl.mAccessibilityState != null
+ && mAttachInfo.mViewRootImpl.mAccessibilityState.isScrollEventSenderInitialized()) {
+ mAttachInfo.mViewRootImpl.mAccessibilityState
+ .getSendViewScrolledAccessibilityEvent()
+ .cancelIfPendingFor(this);
+ }
stopNestedScroll();
// Anything that started animating right before detach should already
@@ -20307,7 +20332,7 @@
mForegroundInfo.mBoundsChanged = true;
}
- notifySubtreeAccessibilityStateChangedIfNeeded();
+ notifyAccessibilitySubtreeChanged();
}
return changed;
}
@@ -21751,7 +21776,7 @@
if (selected) {
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED);
} else {
- notifyViewAccessibilityStateChangedIfNeeded(
+ notifyAccessibilityStateChanged(
AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
}
}
@@ -26308,53 +26333,6 @@
}
/**
- * Resuable callback for sending
- * {@link AccessibilityEvent#TYPE_VIEW_SCROLLED} accessibility event.
- */
- private class SendViewScrolledAccessibilityEvent implements Runnable {
- public volatile boolean mIsPending;
- public int mDeltaX;
- public int mDeltaY;
-
- public void post(int dx, int dy) {
- mDeltaX += dx;
- mDeltaY += dy;
- if (!mIsPending) {
- mIsPending = true;
- postDelayed(this, ViewConfiguration.getSendRecurringAccessibilityEventsInterval());
- }
- }
-
- @Override
- public void run() {
- if (AccessibilityManager.getInstance(mContext).isEnabled()) {
- AccessibilityEvent event = AccessibilityEvent.obtain(
- AccessibilityEvent.TYPE_VIEW_SCROLLED);
- event.setScrollDeltaX(mDeltaX);
- event.setScrollDeltaY(mDeltaY);
- sendAccessibilityEventUnchecked(event);
- }
- reset();
- }
-
- private void reset() {
- mIsPending = false;
- mDeltaX = 0;
- mDeltaY = 0;
- }
- }
-
- /**
- * Remove the pending callback for sending a
- * {@link AccessibilityEvent#TYPE_VIEW_SCROLLED} accessibility event.
- */
- private void cancel(@Nullable SendViewScrolledAccessibilityEvent callback) {
- if (callback == null || !callback.mIsPending) return;
- removeCallbacks(callback);
- callback.reset();
- }
-
- /**
* <p>
* This class represents a delegate that can be registered in a {@link View}
* to enhance accessibility support via composition rather via inheritance.
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index 703364f..e0864bd 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -3646,44 +3646,34 @@
return ViewGroup.class.getName();
}
- @Override
- public void notifySubtreeAccessibilityStateChanged(View child, View source, int changeType) {
- // If this is a live region, we should send a subtree change event
- // from this view. Otherwise, we can let it propagate up.
- if (getAccessibilityLiveRegion() != ACCESSIBILITY_LIVE_REGION_NONE) {
- notifyViewAccessibilityStateChangedIfNeeded(
- AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE);
- } else if (mParent != null) {
- try {
- mParent.notifySubtreeAccessibilityStateChanged(this, source, changeType);
- } catch (AbstractMethodError e) {
- Log.e(VIEW_LOG_TAG, mParent.getClass().getSimpleName() +
- " does not fully implement ViewParent", e);
- }
- }
- }
-
/** @hide */
@Override
- public void notifySubtreeAccessibilityStateChangedIfNeeded() {
+ public void notifyAccessibilitySubtreeChanged() {
if (!AccessibilityManager.getInstance(mContext).isEnabled() || mAttachInfo == null) {
return;
}
// If something important for a11y is happening in this subtree, make sure it's dispatched
// from a view that is important for a11y so it doesn't get lost.
- if ((getImportantForAccessibility() != IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS)
- && !isImportantForAccessibility() && (getChildCount() > 0)) {
+ if (getImportantForAccessibility() != IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
+ && !isImportantForAccessibility()
+ && getChildCount() > 0) {
ViewParent a11yParent = getParentForAccessibility();
if (a11yParent instanceof View) {
- ((View) a11yParent).notifySubtreeAccessibilityStateChangedIfNeeded();
+ ((View) a11yParent).notifyAccessibilitySubtreeChanged();
return;
}
}
- super.notifySubtreeAccessibilityStateChangedIfNeeded();
+ super.notifyAccessibilitySubtreeChanged();
}
@Override
- void resetSubtreeAccessibilityStateChanged() {
+ public void notifySubtreeAccessibilityStateChanged(View child, View source, int changeType) {
+ notifyAccessibilityStateChanged(source, changeType);
+ }
+
+ /** @hide */
+ @Override
+ public void resetSubtreeAccessibilityStateChanged() {
super.resetSubtreeAccessibilityStateChanged();
View[] children = mChildren;
final int childCount = mChildrenCount;
@@ -5095,7 +5085,7 @@
}
if (child.getVisibility() != View.GONE) {
- notifySubtreeAccessibilityStateChangedIfNeeded();
+ notifyAccessibilitySubtreeChanged();
}
if (mTransientIndices != null) {
@@ -5365,7 +5355,7 @@
dispatchViewRemoved(view);
if (view.getVisibility() != View.GONE) {
- notifySubtreeAccessibilityStateChangedIfNeeded();
+ notifyAccessibilitySubtreeChanged();
}
int transientCount = mTransientIndices == null ? 0 : mTransientIndices.size();
@@ -6084,7 +6074,7 @@
if (invalidate) {
invalidateViewProperty(false, false);
}
- notifySubtreeAccessibilityStateChangedIfNeeded();
+ notifyAccessibilitySubtreeChanged();
}
@Override
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 6c5091c..f81a4c3 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -89,9 +89,11 @@
import android.view.accessibility.AccessibilityNodeInfo;
import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
import android.view.accessibility.AccessibilityNodeProvider;
+import android.view.accessibility.AccessibilityViewHierarchyState;
import android.view.accessibility.AccessibilityWindowInfo;
import android.view.accessibility.IAccessibilityInteractionConnection;
import android.view.accessibility.IAccessibilityInteractionConnectionCallback;
+import android.view.accessibility.ThrottlingAccessibilityEventSender;
import android.view.animation.AccelerateDecelerateInterpolator;
import android.view.animation.Interpolator;
import android.view.inputmethod.InputMethodManager;
@@ -113,7 +115,6 @@
import java.io.PrintWriter;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
-import java.util.HashSet;
import java.util.concurrent.CountDownLatch;
/**
@@ -460,10 +461,6 @@
new AccessibilityInteractionConnectionManager();
final HighContrastTextManager mHighContrastTextManager;
- SendWindowContentChangedAccessibilityEvent mSendWindowContentChangedAccessibilityEvent;
-
- HashSet<View> mTempHashSet;
-
private final int mDensity;
private final int mNoncompatDensity;
@@ -478,6 +475,8 @@
private boolean mNeedsRendererSetup;
+ protected AccessibilityViewHierarchyState mAccessibilityState;
+
/**
* Consistency verifier for debugging purposes.
*/
@@ -7262,11 +7261,9 @@
* {@link ViewConfiguration#getSendRecurringAccessibilityEventsInterval()}.
*/
private void postSendWindowContentChangedCallback(View source, int changeType) {
- if (mSendWindowContentChangedAccessibilityEvent == null) {
- mSendWindowContentChangedAccessibilityEvent =
- new SendWindowContentChangedAccessibilityEvent();
- }
- mSendWindowContentChangedAccessibilityEvent.runOrPost(source, changeType);
+ getAccessibilityState()
+ .getSendWindowContentChangedAccessibilityEvent()
+ .runOrPost(source, changeType);
}
/**
@@ -7274,11 +7271,20 @@
* {@link AccessibilityEvent#TYPE_WINDOW_CONTENT_CHANGED} event.
*/
private void removeSendWindowContentChangedCallback() {
- if (mSendWindowContentChangedAccessibilityEvent != null) {
- mHandler.removeCallbacks(mSendWindowContentChangedAccessibilityEvent);
+ if (mAccessibilityState != null
+ && mAccessibilityState.isWindowContentChangedEventSenderInitialized()) {
+ ThrottlingAccessibilityEventSender.cancelIfPending(
+ mAccessibilityState.getSendWindowContentChangedAccessibilityEvent());
}
}
+ AccessibilityViewHierarchyState getAccessibilityState() {
+ if (mAccessibilityState == null) {
+ mAccessibilityState = new AccessibilityViewHierarchyState();
+ }
+ return mAccessibilityState;
+ }
+
@Override
public boolean showContextMenuForChild(View originalView) {
return false;
@@ -7314,12 +7320,8 @@
return false;
}
- // Immediately flush pending content changed event (if any) to preserve event order
- if (event.getEventType() != AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED
- && mSendWindowContentChangedAccessibilityEvent != null
- && mSendWindowContentChangedAccessibilityEvent.mSource != null) {
- mSendWindowContentChangedAccessibilityEvent.removeCallbacksAndRun();
- }
+ // Send any pending event to prevent reordering
+ flushPendingAccessibilityEvents();
// Intercept accessibility focus events fired by virtual nodes to keep
// track of accessibility focus position in such nodes.
@@ -7363,6 +7365,19 @@
return true;
}
+ /** @hide */
+ public void flushPendingAccessibilityEvents() {
+ if (mAccessibilityState != null) {
+ if (mAccessibilityState.isScrollEventSenderInitialized()) {
+ mAccessibilityState.getSendViewScrolledAccessibilityEvent().sendNowIfPending();
+ }
+ if (mAccessibilityState.isWindowContentChangedEventSenderInitialized()) {
+ mAccessibilityState.getSendWindowContentChangedAccessibilityEvent()
+ .sendNowIfPending();
+ }
+ }
+ }
+
/**
* Updates the focused virtual view, when necessary, in response to a
* content changed event.
@@ -7497,39 +7512,6 @@
return View.TEXT_ALIGNMENT_RESOLVED_DEFAULT;
}
- private View getCommonPredecessor(View first, View second) {
- if (mTempHashSet == null) {
- mTempHashSet = new HashSet<View>();
- }
- HashSet<View> seen = mTempHashSet;
- seen.clear();
- View firstCurrent = first;
- while (firstCurrent != null) {
- seen.add(firstCurrent);
- ViewParent firstCurrentParent = firstCurrent.mParent;
- if (firstCurrentParent instanceof View) {
- firstCurrent = (View) firstCurrentParent;
- } else {
- firstCurrent = null;
- }
- }
- View secondCurrent = second;
- while (secondCurrent != null) {
- if (seen.contains(secondCurrent)) {
- seen.clear();
- return secondCurrent;
- }
- ViewParent secondCurrentParent = secondCurrent.mParent;
- if (secondCurrentParent instanceof View) {
- secondCurrent = (View) secondCurrentParent;
- } else {
- secondCurrent = null;
- }
- }
- seen.clear();
- return null;
- }
-
void checkThread() {
if (mThread != Thread.currentThread()) {
throw new CalledFromWrongThreadException(
@@ -8140,80 +8122,6 @@
}
}
- private class SendWindowContentChangedAccessibilityEvent implements Runnable {
- private int mChangeTypes = 0;
-
- public View mSource;
- public long mLastEventTimeMillis;
-
- @Override
- public void run() {
- // Protect against re-entrant code and attempt to do the right thing in the case that
- // we're multithreaded.
- View source = mSource;
- mSource = null;
- if (source == null) {
- Log.e(TAG, "Accessibility content change has no source");
- return;
- }
- // The accessibility may be turned off while we were waiting so check again.
- if (AccessibilityManager.getInstance(mContext).isEnabled()) {
- mLastEventTimeMillis = SystemClock.uptimeMillis();
- AccessibilityEvent event = AccessibilityEvent.obtain();
- event.setEventType(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
- event.setContentChangeTypes(mChangeTypes);
- source.sendAccessibilityEventUnchecked(event);
- } else {
- mLastEventTimeMillis = 0;
- }
- // In any case reset to initial state.
- source.resetSubtreeAccessibilityStateChanged();
- mChangeTypes = 0;
- }
-
- public void runOrPost(View source, int changeType) {
- if (mHandler.getLooper() != Looper.myLooper()) {
- CalledFromWrongThreadException e = new CalledFromWrongThreadException("Only the "
- + "original thread that created a view hierarchy can touch its views.");
- // TODO: Throw the exception
- Log.e(TAG, "Accessibility content change on non-UI thread. Future Android "
- + "versions will throw an exception.", e);
- // Attempt to recover. This code does not eliminate the thread safety issue, but
- // it should force any issues to happen near the above log.
- mHandler.removeCallbacks(this);
- if (mSource != null) {
- // Dispatch whatever was pending. It's still possible that the runnable started
- // just before we removed the callbacks, and bad things will happen, but at
- // least they should happen very close to the logged error.
- run();
- }
- }
- if (mSource != null) {
- // If there is no common predecessor, then mSource points to
- // a removed view, hence in this case always prefer the source.
- View predecessor = getCommonPredecessor(mSource, source);
- mSource = (predecessor != null) ? predecessor : source;
- mChangeTypes |= changeType;
- return;
- }
- mSource = source;
- mChangeTypes = changeType;
- final long timeSinceLastMillis = SystemClock.uptimeMillis() - mLastEventTimeMillis;
- final long minEventIntevalMillis =
- ViewConfiguration.getSendRecurringAccessibilityEventsInterval();
- if (timeSinceLastMillis >= minEventIntevalMillis) {
- removeCallbacksAndRun();
- } else {
- mHandler.postDelayed(this, minEventIntevalMillis - timeSinceLastMillis);
- }
- }
-
- public void removeCallbacksAndRun() {
- mHandler.removeCallbacks(this);
- run();
- }
- }
-
private static class KeyFallbackManager {
// This is used to ensure that key-fallback events are only dispatched once. We attempt
diff --git a/core/java/android/view/accessibility/AccessibilityEvent.java b/core/java/android/view/accessibility/AccessibilityEvent.java
index 6c2d349..aa61926 100644
--- a/core/java/android/view/accessibility/AccessibilityEvent.java
+++ b/core/java/android/view/accessibility/AccessibilityEvent.java
@@ -565,6 +565,12 @@
public static final int CONTENT_CHANGE_TYPE_CONTENT_DESCRIPTION = 0x00000004;
/**
+ * Change type for {@link #TYPE_WINDOW_CONTENT_CHANGED} event:
+ * The node's pane title changed.
+ */
+ public static final int CONTENT_CHANGE_TYPE_PANE_TITLE = 0x00000008;
+
+ /**
* Change type for {@link #TYPE_WINDOWS_CHANGED} event:
* The window was added.
*/
@@ -654,7 +660,8 @@
CONTENT_CHANGE_TYPE_UNDEFINED,
CONTENT_CHANGE_TYPE_SUBTREE,
CONTENT_CHANGE_TYPE_TEXT,
- CONTENT_CHANGE_TYPE_CONTENT_DESCRIPTION
+ CONTENT_CHANGE_TYPE_CONTENT_DESCRIPTION,
+ CONTENT_CHANGE_TYPE_PANE_TITLE
})
public @interface ContentChangeTypes {}
@@ -1244,43 +1251,33 @@
builder.append("EventType: ").append(eventTypeToString(mEventType));
builder.append("; EventTime: ").append(mEventTime);
builder.append("; PackageName: ").append(mPackageName);
- builder.append("; MovementGranularity: ").append(mMovementGranularity);
- builder.append("; Action: ").append(mAction);
- builder.append("; ContentChangeTypes: ").append(
- contentChangeTypesToString(mContentChangeTypes));
- builder.append("; WindowChangeTypes: ").append(
- windowChangeTypesToString(mWindowChangeTypes));
- builder.append(super.toString());
- if (DEBUG) {
- builder.append("\n");
- builder.append("; sourceWindowId: ").append(mSourceWindowId);
- builder.append("; mSourceNodeId: ").append(mSourceNodeId);
- for (int i = 0; i < getRecordCount(); i++) {
- final AccessibilityRecord record = getRecord(i);
- builder.append(" Record ");
- builder.append(i);
- builder.append(":");
- builder.append(" [ ClassName: " + record.mClassName);
- builder.append("; Text: " + record.mText);
- builder.append("; ContentDescription: " + record.mContentDescription);
- builder.append("; ItemCount: " + record.mItemCount);
- builder.append("; CurrentItemIndex: " + record.mCurrentItemIndex);
- builder.append("; IsEnabled: " + record.isEnabled());
- builder.append("; IsPassword: " + record.isPassword());
- builder.append("; IsChecked: " + record.isChecked());
- builder.append("; IsFullScreen: " + record.isFullScreen());
- builder.append("; Scrollable: " + record.isScrollable());
- builder.append("; BeforeText: " + record.mBeforeText);
- builder.append("; FromIndex: " + record.mFromIndex);
- builder.append("; ToIndex: " + record.mToIndex);
- builder.append("; ScrollX: " + record.mScrollX);
- builder.append("; ScrollY: " + record.mScrollY);
- builder.append("; AddedCount: " + record.mAddedCount);
- builder.append("; RemovedCount: " + record.mRemovedCount);
- builder.append("; ParcelableData: " + record.mParcelableData);
- builder.append(" ]");
+ if (!DEBUG_CONCISE_TOSTRING || mMovementGranularity != 0) {
+ builder.append("; MovementGranularity: ").append(mMovementGranularity);
+ }
+ if (!DEBUG_CONCISE_TOSTRING || mAction != 0) {
+ builder.append("; Action: ").append(mAction);
+ }
+ if (!DEBUG_CONCISE_TOSTRING || mContentChangeTypes != 0) {
+ builder.append("; ContentChangeTypes: ").append(
+ contentChangeTypesToString(mContentChangeTypes));
+ }
+ if (!DEBUG_CONCISE_TOSTRING || mWindowChangeTypes != 0) {
+ builder.append("; WindowChangeTypes: ").append(
+ contentChangeTypesToString(mWindowChangeTypes));
+ }
+ super.appendTo(builder);
+ if (DEBUG || DEBUG_CONCISE_TOSTRING) {
+ if (!DEBUG_CONCISE_TOSTRING) {
builder.append("\n");
}
+ if (DEBUG) {
+ builder.append("; SourceWindowId: ").append(mSourceWindowId);
+ builder.append("; SourceNodeId: ").append(mSourceNodeId);
+ }
+ for (int i = 0; i < getRecordCount(); i++) {
+ builder.append(" Record ").append(i).append(":");
+ getRecord(i).appendTo(builder).append("\n");
+ }
} else {
builder.append("; recordCount: ").append(getRecordCount());
}
diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
index 28ef697..311dd4b 100644
--- a/core/java/android/view/accessibility/AccessibilityNodeInfo.java
+++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
@@ -723,6 +723,7 @@
private CharSequence mText;
private CharSequence mHintText;
private CharSequence mError;
+ private CharSequence mPaneTitle;
private CharSequence mContentDescription;
private String mViewIdResourceName;
private ArrayList<String> mExtraDataKeys;
@@ -2033,6 +2034,33 @@
}
/**
+ * If this node represents a visually distinct region of the screen that may update separately
+ * from the rest of the window, it is considered a pane. Set the pane title to indicate that
+ * the node is a pane, and to provide a title for it.
+ * <p>
+ * <strong>Note:</strong> Cannot be called from an
+ * {@link android.accessibilityservice.AccessibilityService}.
+ * This class is made immutable before being delivered to an AccessibilityService.
+ * </p>
+ * @param paneTitle The title of the pane represented by this node.
+ */
+ public void setPaneTitle(@Nullable CharSequence paneTitle) {
+ enforceNotSealed();
+ mPaneTitle = (paneTitle == null)
+ ? null : paneTitle.subSequence(0, paneTitle.length());
+ }
+
+ /**
+ * Get the title of the pane represented by this node.
+ *
+ * @return The title of the pane represented by this node, or {@code null} if this node does
+ * not represent a pane.
+ */
+ public @Nullable CharSequence getPaneTitle() {
+ return mPaneTitle;
+ }
+
+ /**
* Get the drawing order of the view corresponding it this node.
* <p>
* Drawing order is determined only within the node's parent, so this index is only relative
@@ -3151,6 +3179,10 @@
nonDefaultFields |= bitAt(fieldIndex);
}
fieldIndex++;
+ if (!Objects.equals(mPaneTitle, DEFAULT.mPaneTitle)) {
+ nonDefaultFields |= bitAt(fieldIndex);
+ }
+ fieldIndex++;
if (!Objects.equals(mViewIdResourceName, DEFAULT.mViewIdResourceName)) {
nonDefaultFields |= bitAt(fieldIndex);
}
@@ -3270,6 +3302,7 @@
if (isBitSet(nonDefaultFields, fieldIndex++)) {
parcel.writeCharSequence(mContentDescription);
}
+ if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeCharSequence(mPaneTitle);
if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeString(mViewIdResourceName);
if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeInt(mTextSelectionStart);
@@ -3341,6 +3374,7 @@
mHintText = other.mHintText;
mError = other.mError;
mContentDescription = other.mContentDescription;
+ mPaneTitle = other.mPaneTitle;
mViewIdResourceName = other.mViewIdResourceName;
if (mActions != null) mActions.clear();
@@ -3461,6 +3495,7 @@
if (isBitSet(nonDefaultFields, fieldIndex++)) {
mContentDescription = parcel.readCharSequence();
}
+ if (isBitSet(nonDefaultFields, fieldIndex++)) mPaneTitle = parcel.readString();
if (isBitSet(nonDefaultFields, fieldIndex++)) mViewIdResourceName = parcel.readString();
if (isBitSet(nonDefaultFields, fieldIndex++)) mTextSelectionStart = parcel.readInt();
diff --git a/core/java/android/view/accessibility/AccessibilityRecord.java b/core/java/android/view/accessibility/AccessibilityRecord.java
index fa505c9..0a709f8 100644
--- a/core/java/android/view/accessibility/AccessibilityRecord.java
+++ b/core/java/android/view/accessibility/AccessibilityRecord.java
@@ -16,6 +16,8 @@
package android.view.accessibility;
+import static com.android.internal.util.CollectionUtils.isEmpty;
+
import android.annotation.Nullable;
import android.os.Parcelable;
import android.view.View;
@@ -55,6 +57,8 @@
* @see AccessibilityNodeInfo
*/
public class AccessibilityRecord {
+ /** @hide */
+ protected static final boolean DEBUG_CONCISE_TOSTRING = false;
private static final int UNDEFINED = -1;
@@ -888,28 +892,69 @@
@Override
public String toString() {
- StringBuilder builder = new StringBuilder();
- builder.append(" [ ClassName: " + mClassName);
- builder.append("; Text: " + mText);
- builder.append("; ContentDescription: " + mContentDescription);
- builder.append("; ItemCount: " + mItemCount);
- builder.append("; CurrentItemIndex: " + mCurrentItemIndex);
- builder.append("; IsEnabled: " + getBooleanProperty(PROPERTY_ENABLED));
- builder.append("; IsPassword: " + getBooleanProperty(PROPERTY_PASSWORD));
- builder.append("; IsChecked: " + getBooleanProperty(PROPERTY_CHECKED));
- builder.append("; IsFullScreen: " + getBooleanProperty(PROPERTY_FULL_SCREEN));
- builder.append("; Scrollable: " + getBooleanProperty(PROPERTY_SCROLLABLE));
- builder.append("; BeforeText: " + mBeforeText);
- builder.append("; FromIndex: " + mFromIndex);
- builder.append("; ToIndex: " + mToIndex);
- builder.append("; ScrollX: " + mScrollX);
- builder.append("; ScrollY: " + mScrollY);
- builder.append("; MaxScrollX: " + mMaxScrollX);
- builder.append("; MaxScrollY: " + mMaxScrollY);
- builder.append("; AddedCount: " + mAddedCount);
- builder.append("; RemovedCount: " + mRemovedCount);
- builder.append("; ParcelableData: " + mParcelableData);
+ return appendTo(new StringBuilder()).toString();
+ }
+
+ StringBuilder appendTo(StringBuilder builder) {
+ builder.append(" [ ClassName: ").append(mClassName);
+ if (!DEBUG_CONCISE_TOSTRING || !isEmpty(mText)) {
+ appendPropName(builder, "Text").append(mText);
+ }
+ append(builder, "ContentDescription", mContentDescription);
+ append(builder, "ItemCount", mItemCount);
+ append(builder, "CurrentItemIndex", mCurrentItemIndex);
+
+ appendUnless(true, PROPERTY_ENABLED, builder);
+ appendUnless(false, PROPERTY_PASSWORD, builder);
+ appendUnless(false, PROPERTY_CHECKED, builder);
+ appendUnless(false, PROPERTY_FULL_SCREEN, builder);
+ appendUnless(false, PROPERTY_SCROLLABLE, builder);
+
+ append(builder, "BeforeText", mBeforeText);
+ append(builder, "FromIndex", mFromIndex);
+ append(builder, "ToIndex", mToIndex);
+ append(builder, "ScrollX", mScrollX);
+ append(builder, "ScrollY", mScrollY);
+ append(builder, "MaxScrollX", mMaxScrollX);
+ append(builder, "MaxScrollY", mMaxScrollY);
+ append(builder, "AddedCount", mAddedCount);
+ append(builder, "RemovedCount", mRemovedCount);
+ append(builder, "ParcelableData", mParcelableData);
builder.append(" ]");
- return builder.toString();
+ return builder;
+ }
+
+ private void appendUnless(boolean defValue, int prop, StringBuilder builder) {
+ boolean value = getBooleanProperty(prop);
+ if (DEBUG_CONCISE_TOSTRING && value == defValue) return;
+ appendPropName(builder, singleBooleanPropertyToString(prop))
+ .append(value);
+ }
+
+ private static String singleBooleanPropertyToString(int prop) {
+ switch (prop) {
+ case PROPERTY_CHECKED: return "Checked";
+ case PROPERTY_ENABLED: return "Enabled";
+ case PROPERTY_PASSWORD: return "Password";
+ case PROPERTY_FULL_SCREEN: return "FullScreen";
+ case PROPERTY_SCROLLABLE: return "Scrollable";
+ case PROPERTY_IMPORTANT_FOR_ACCESSIBILITY:
+ return "ImportantForAccessibility";
+ default: return Integer.toHexString(prop);
+ }
+ }
+
+ private void append(StringBuilder builder, String propName, int propValue) {
+ if (DEBUG_CONCISE_TOSTRING && propValue == UNDEFINED) return;
+ appendPropName(builder, propName).append(propValue);
+ }
+
+ private void append(StringBuilder builder, String propName, Object propValue) {
+ if (DEBUG_CONCISE_TOSTRING && propValue == null) return;
+ appendPropName(builder, propName).append(propValue);
+ }
+
+ private StringBuilder appendPropName(StringBuilder builder, String propName) {
+ return builder.append("; ").append(propName).append(": ");
}
}
diff --git a/core/java/android/view/accessibility/AccessibilityViewHierarchyState.java b/core/java/android/view/accessibility/AccessibilityViewHierarchyState.java
new file mode 100644
index 0000000..447fafa
--- /dev/null
+++ b/core/java/android/view/accessibility/AccessibilityViewHierarchyState.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.accessibility;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+/**
+ * Accessibility-related state of a {@link android.view.ViewRootImpl}
+ *
+ * @hide
+ */
+public class AccessibilityViewHierarchyState {
+ private @Nullable SendViewScrolledAccessibilityEvent mSendViewScrolledAccessibilityEvent;
+ private @Nullable SendWindowContentChangedAccessibilityEvent
+ mSendWindowContentChangedAccessibilityEvent;
+
+ /**
+ * @return a {@link SendViewScrolledAccessibilityEvent}, creating one if needed
+ */
+ public @NonNull SendViewScrolledAccessibilityEvent getSendViewScrolledAccessibilityEvent() {
+ if (mSendViewScrolledAccessibilityEvent == null) {
+ mSendViewScrolledAccessibilityEvent = new SendViewScrolledAccessibilityEvent();
+ }
+ return mSendViewScrolledAccessibilityEvent;
+ }
+
+ public boolean isScrollEventSenderInitialized() {
+ return mSendViewScrolledAccessibilityEvent != null;
+ }
+
+ /**
+ * @return a {@link SendWindowContentChangedAccessibilityEvent}, creating one if needed
+ */
+ public @NonNull SendWindowContentChangedAccessibilityEvent
+ getSendWindowContentChangedAccessibilityEvent() {
+ if (mSendWindowContentChangedAccessibilityEvent == null) {
+ mSendWindowContentChangedAccessibilityEvent =
+ new SendWindowContentChangedAccessibilityEvent();
+ }
+ return mSendWindowContentChangedAccessibilityEvent;
+ }
+
+ public boolean isWindowContentChangedEventSenderInitialized() {
+ return mSendWindowContentChangedAccessibilityEvent != null;
+ }
+}
diff --git a/core/java/android/view/accessibility/SendViewScrolledAccessibilityEvent.java b/core/java/android/view/accessibility/SendViewScrolledAccessibilityEvent.java
new file mode 100644
index 0000000..40a1b6a
--- /dev/null
+++ b/core/java/android/view/accessibility/SendViewScrolledAccessibilityEvent.java
@@ -0,0 +1,58 @@
+/*
+ * 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.accessibility;
+
+
+import android.annotation.NonNull;
+import android.view.View;
+
+/**
+ * Sender for {@link AccessibilityEvent#TYPE_VIEW_SCROLLED} accessibility event.
+ *
+ * @hide
+ */
+public class SendViewScrolledAccessibilityEvent extends ThrottlingAccessibilityEventSender {
+
+ public int mDeltaX;
+ public int mDeltaY;
+
+ /**
+ * Post a scroll event to be sent for the given view
+ */
+ public void post(View source, int dx, int dy) {
+ if (!isPendingFor(source)) sendNowIfPending();
+
+ mDeltaX += dx;
+ mDeltaY += dy;
+
+ if (!isPendingFor(source)) scheduleFor(source);
+ }
+
+ @Override
+ protected void performSendEvent(@NonNull View source) {
+ AccessibilityEvent event = AccessibilityEvent.obtain(AccessibilityEvent.TYPE_VIEW_SCROLLED);
+ event.setScrollDeltaX(mDeltaX);
+ event.setScrollDeltaY(mDeltaY);
+ source.sendAccessibilityEventUnchecked(event);
+ }
+
+ @Override
+ protected void resetState(@NonNull View source) {
+ mDeltaX = 0;
+ mDeltaY = 0;
+ }
+}
diff --git a/core/java/android/view/accessibility/SendWindowContentChangedAccessibilityEvent.java b/core/java/android/view/accessibility/SendWindowContentChangedAccessibilityEvent.java
new file mode 100644
index 0000000..df38fba
--- /dev/null
+++ b/core/java/android/view/accessibility/SendWindowContentChangedAccessibilityEvent.java
@@ -0,0 +1,111 @@
+/*
+ * 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.accessibility;
+
+
+import static com.android.internal.util.ObjectUtils.firstNotNull;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.view.View;
+import android.view.ViewParent;
+
+import java.util.HashSet;
+
+/**
+ * @hide
+ */
+public class SendWindowContentChangedAccessibilityEvent
+ extends ThrottlingAccessibilityEventSender {
+
+ private int mChangeTypes = 0;
+
+ private HashSet<View> mTempHashSet;
+
+ @Override
+ protected void performSendEvent(@NonNull View source) {
+ AccessibilityEvent event = AccessibilityEvent.obtain();
+ event.setEventType(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
+ event.setContentChangeTypes(mChangeTypes);
+ source.sendAccessibilityEventUnchecked(event);
+ }
+
+ @Override
+ protected void resetState(@Nullable View source) {
+ if (source != null) {
+ source.resetSubtreeAccessibilityStateChanged();
+ }
+ mChangeTypes = 0;
+ }
+
+ /**
+ * Post the {@link AccessibilityEvent#TYPE_WINDOW_CONTENT_CHANGED} event with the given
+ * {@link AccessibilityEvent#getContentChangeTypes change type} for the given view
+ */
+ public void runOrPost(View source, int changeType) {
+ if (source.getAccessibilityLiveRegion() != View.ACCESSIBILITY_LIVE_REGION_NONE) {
+ sendNowIfPending();
+ mChangeTypes = changeType;
+ sendNow(source);
+ } else {
+ mChangeTypes |= changeType;
+ scheduleFor(source);
+ }
+ }
+
+ @Override
+ protected @Nullable View tryMerge(@NonNull View oldSource, @NonNull View newSource) {
+ // If there is no common predecessor, then oldSource points to
+ // a removed view, hence in this case always prefer the newSource.
+ return firstNotNull(
+ getCommonPredecessor(oldSource, newSource),
+ newSource);
+ }
+
+ private View getCommonPredecessor(View first, View second) {
+ if (mTempHashSet == null) {
+ mTempHashSet = new HashSet<>();
+ }
+ HashSet<View> seen = mTempHashSet;
+ seen.clear();
+ View firstCurrent = first;
+ while (firstCurrent != null) {
+ seen.add(firstCurrent);
+ ViewParent firstCurrentParent = firstCurrent.getParent();
+ if (firstCurrentParent instanceof View) {
+ firstCurrent = (View) firstCurrentParent;
+ } else {
+ firstCurrent = null;
+ }
+ }
+ View secondCurrent = second;
+ while (secondCurrent != null) {
+ if (seen.contains(secondCurrent)) {
+ seen.clear();
+ return secondCurrent;
+ }
+ ViewParent secondCurrentParent = secondCurrent.getParent();
+ if (secondCurrentParent instanceof View) {
+ secondCurrent = (View) secondCurrentParent;
+ } else {
+ secondCurrent = null;
+ }
+ }
+ seen.clear();
+ return null;
+ }
+}
diff --git a/core/java/android/view/accessibility/ThrottlingAccessibilityEventSender.java b/core/java/android/view/accessibility/ThrottlingAccessibilityEventSender.java
new file mode 100644
index 0000000..66fa301
--- /dev/null
+++ b/core/java/android/view/accessibility/ThrottlingAccessibilityEventSender.java
@@ -0,0 +1,248 @@
+/*
+ * 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.accessibility;
+
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.SystemClock;
+import android.util.Log;
+import android.view.View;
+import android.view.ViewConfiguration;
+import android.view.ViewRootImpl;
+import android.view.ViewRootImpl.CalledFromWrongThreadException;
+
+/**
+ * A throttling {@link AccessibilityEvent} sender that relies on its currently associated
+ * 'source' view's {@link View#postDelayed delayed execution} to delay and possibly
+ * {@link #tryMerge merge} together any events that come in less than
+ * {@link ViewConfiguration#getSendRecurringAccessibilityEventsInterval
+ * the configured amount of milliseconds} apart.
+ *
+ * The suggested usage is to create a singleton extending this class, holding any state specific to
+ * the particular event type that the subclass represents, and have an 'entrypoint' method that
+ * delegates to {@link #scheduleFor(View)}.
+ * For example:
+ *
+ * {@code
+ * public void post(View view, String text, int resId) {
+ * mText = text;
+ * mId = resId;
+ * scheduleFor(view);
+ * }
+ * }
+ *
+ * @see #scheduleFor(View)
+ * @see #tryMerge(View, View)
+ * @see #performSendEvent(View)
+ * @hide
+ */
+public abstract class ThrottlingAccessibilityEventSender {
+
+ private static final boolean DEBUG = false;
+ private static final String LOG_TAG = "ThrottlingA11ySender";
+
+ View mSource;
+ private long mLastSendTimeMillis = Long.MIN_VALUE;
+ private boolean mIsPending = false;
+
+ private final Runnable mWorker = () -> {
+ View source = mSource;
+ if (DEBUG) Log.d(LOG_TAG, thisClass() + ".run(mSource = " + source + ")");
+
+ if (!checkAndResetIsPending() || source == null) {
+ resetStateInternal();
+ return;
+ }
+
+ // Accessibility may be turned off while we were waiting
+ if (isAccessibilityEnabled(source)) {
+ mLastSendTimeMillis = SystemClock.uptimeMillis();
+ performSendEvent(source);
+ }
+ resetStateInternal();
+ };
+
+ /**
+ * Populate and send an {@link AccessibilityEvent} using the given {@code source} view, as well
+ * as any extra data from this instance's state.
+ *
+ * Send the event via {@link View#sendAccessibilityEventUnchecked(AccessibilityEvent)} or
+ * {@link View#sendAccessibilityEvent(int)} on the provided {@code source} view to allow for
+ * overrides of those methods on {@link View} subclasses to take effect, and/or make sure that
+ * an {@link View#getAccessibilityDelegate() accessibility delegate} is not ignored if any.
+ */
+ protected abstract void performSendEvent(@NonNull View source);
+
+ /**
+ * Perform optional cleanup after {@link #performSendEvent}
+ *
+ * @param source the view this event was associated with
+ */
+ protected abstract void resetState(@Nullable View source);
+
+ /**
+ * Attempt to merge the pending events for source views {@code oldSource} and {@code newSource}
+ * into one, with source set to the resulting {@link View}
+ *
+ * A result of {@code null} means merger is not possible, resulting in the currently pending
+ * event being flushed before proceeding.
+ */
+ protected @Nullable View tryMerge(@NonNull View oldSource, @NonNull View newSource) {
+ return null;
+ }
+
+ /**
+ * Schedules a {@link #performSendEvent} with the source {@link View} set to given
+ * {@code source}
+ *
+ * If an event is already scheduled a {@link #tryMerge merge} will be attempted.
+ * If merging is not possible (as indicated by the null result from {@link #tryMerge}),
+ * the currently scheduled event will be {@link #sendNow sent immediately} and the new one
+ * will be scheduled afterwards.
+ */
+ protected final void scheduleFor(@NonNull View source) {
+ if (DEBUG) Log.d(LOG_TAG, thisClass() + ".scheduleFor(source = " + source + ")");
+
+ Handler uiHandler = source.getHandler();
+ if (uiHandler == null || uiHandler.getLooper() != Looper.myLooper()) {
+ CalledFromWrongThreadException e = new CalledFromWrongThreadException(
+ "Expected to be called from main thread but was called from "
+ + Thread.currentThread());
+ // TODO: Throw the exception
+ Log.e(LOG_TAG, "Accessibility content change on non-UI thread. Future Android "
+ + "versions will throw an exception.", e);
+ }
+
+ if (!isAccessibilityEnabled(source)) return;
+
+ if (mIsPending) {
+ View merged = tryMerge(mSource, source);
+ if (merged != null) {
+ setSource(merged);
+ return;
+ } else {
+ sendNow();
+ }
+ }
+
+ setSource(source);
+
+ final long timeSinceLastMillis = SystemClock.uptimeMillis() - mLastSendTimeMillis;
+ final long minEventIntervalMillis =
+ ViewConfiguration.getSendRecurringAccessibilityEventsInterval();
+ if (timeSinceLastMillis >= minEventIntervalMillis) {
+ sendNow();
+ } else {
+ mSource.postDelayed(mWorker, minEventIntervalMillis - timeSinceLastMillis);
+ }
+ }
+
+ static boolean isAccessibilityEnabled(@NonNull View contextProvider) {
+ return AccessibilityManager.getInstance(contextProvider.getContext()).isEnabled();
+ }
+
+ protected final void sendNow(View source) {
+ setSource(source);
+ sendNow();
+ }
+
+ private void sendNow() {
+ mSource.removeCallbacks(mWorker);
+ mWorker.run();
+ }
+
+ /**
+ * Flush the event if one is pending
+ */
+ public void sendNowIfPending() {
+ if (mIsPending) sendNow();
+ }
+
+ /**
+ * Cancel the event if one is pending and is for the given view
+ */
+ public final void cancelIfPendingFor(@NonNull View source) {
+ if (isPendingFor(source)) cancelIfPending(this);
+ }
+
+ /**
+ * @return whether an event is currently pending for the given source view
+ */
+ protected final boolean isPendingFor(@Nullable View source) {
+ return mIsPending && mSource == source;
+ }
+
+ /**
+ * Cancel the event if one is not null and pending
+ */
+ public static void cancelIfPending(@Nullable ThrottlingAccessibilityEventSender sender) {
+ if (sender == null || !sender.checkAndResetIsPending()) return;
+ sender.mSource.removeCallbacks(sender.mWorker);
+ sender.resetStateInternal();
+ }
+
+ void resetStateInternal() {
+ if (DEBUG) Log.d(LOG_TAG, thisClass() + ".resetStateInternal()");
+
+ resetState(mSource);
+ setSource(null);
+ }
+
+ boolean checkAndResetIsPending() {
+ if (mIsPending) {
+ mIsPending = false;
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ private void setSource(@Nullable View source) {
+ if (DEBUG) Log.d(LOG_TAG, thisClass() + ".setSource(" + source + ")");
+
+ if (source == null && mIsPending) {
+ Log.e(LOG_TAG, "mSource nullified while callback still pending: " + this);
+ return;
+ }
+
+ if (source != null && !mIsPending) {
+ // At most one can be pending at any given time
+ View oldSource = mSource;
+ if (oldSource != null) {
+ ViewRootImpl viewRootImpl = oldSource.getViewRootImpl();
+ if (viewRootImpl != null) {
+ viewRootImpl.flushPendingAccessibilityEvents();
+ }
+ }
+ mIsPending = true;
+ }
+ mSource = source;
+ }
+
+ String thisClass() {
+ return getClass().getSimpleName();
+ }
+
+ @Override
+ public String toString() {
+ return thisClass() + "(" + mSource + ")";
+ }
+
+}
diff --git a/core/java/android/view/inputmethod/InputConnection.java b/core/java/android/view/inputmethod/InputConnection.java
index 57f9895..e554540 100644
--- a/core/java/android/view/inputmethod/InputConnection.java
+++ b/core/java/android/view/inputmethod/InputConnection.java
@@ -1,17 +1,17 @@
/*
- * Copyright (C) 2007-2008 The Android Open Source Project
+ * Copyright (C) 2007 The Android Open Source Project
*
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.inputmethod;
@@ -131,13 +131,13 @@
* spans. <strong>Editor authors</strong>: you should strive to
* send text with styles if possible, but it is not required.
*/
- static final int GET_TEXT_WITH_STYLES = 0x0001;
+ int GET_TEXT_WITH_STYLES = 0x0001;
/**
* Flag for use with {@link #getExtractedText} to indicate you
* would like to receive updates when the extracted text changes.
*/
- public static final int GET_EXTRACTED_TEXT_MONITOR = 0x0001;
+ int GET_EXTRACTED_TEXT_MONITOR = 0x0001;
/**
* Get <var>n</var> characters of text before the current cursor
@@ -176,7 +176,7 @@
* @return the text before the cursor position; the length of the
* returned text might be less than <var>n</var>.
*/
- public CharSequence getTextBeforeCursor(int n, int flags);
+ CharSequence getTextBeforeCursor(int n, int flags);
/**
* Get <var>n</var> characters of text after the current cursor
@@ -215,7 +215,7 @@
* @return the text after the cursor position; the length of the
* returned text might be less than <var>n</var>.
*/
- public CharSequence getTextAfterCursor(int n, int flags);
+ CharSequence getTextAfterCursor(int n, int flags);
/**
* Gets the selected text, if any.
@@ -249,7 +249,7 @@
* later, returns false when the target application does not implement
* this method.
*/
- public CharSequence getSelectedText(int flags);
+ CharSequence getSelectedText(int flags);
/**
* Retrieve the current capitalization mode in effect at the
@@ -279,7 +279,7 @@
* @return the caps mode flags that are in effect at the current
* cursor position. See TYPE_TEXT_FLAG_CAPS_* in {@link android.text.InputType}.
*/
- public int getCursorCapsMode(int reqModes);
+ int getCursorCapsMode(int reqModes);
/**
* Retrieve the current text in the input connection's editor, and
@@ -314,8 +314,7 @@
* longer valid of the editor can't comply with the request for
* some reason.
*/
- public ExtractedText getExtractedText(ExtractedTextRequest request,
- int flags);
+ ExtractedText getExtractedText(ExtractedTextRequest request, int flags);
/**
* Delete <var>beforeLength</var> characters of text before the
@@ -342,8 +341,8 @@
* delete more characters than are in the editor, as that may have
* ill effects on the application. Calling this method will cause
* the editor to call
- * {@link android.inputmethodservice.InputMethodService#onUpdateSelection(int, int, int, int, int, int)}
- * on your service after the batch input is over.</p>
+ * {@link android.inputmethodservice.InputMethodService#onUpdateSelection(int, int, int, int,
+ * int, int)} on your service after the batch input is over.</p>
*
* <p><strong>Editor authors:</strong> please be careful of race
* conditions in implementing this call. An IME can make a change
@@ -369,7 +368,7 @@
* that range.
* @return true on success, false if the input connection is no longer valid.
*/
- public boolean deleteSurroundingText(int beforeLength, int afterLength);
+ boolean deleteSurroundingText(int beforeLength, int afterLength);
/**
* A variant of {@link #deleteSurroundingText(int, int)}. Major differences are:
@@ -397,7 +396,7 @@
* @return true on success, false if the input connection is no longer valid. Returns
* {@code false} when the target application does not implement this method.
*/
- public boolean deleteSurroundingTextInCodePoints(int beforeLength, int afterLength);
+ boolean deleteSurroundingTextInCodePoints(int beforeLength, int afterLength);
/**
* Replace the currently composing text with the given text, and
@@ -416,8 +415,8 @@
* <p>This is usually called by IMEs to add or remove or change
* characters in the composing span. Calling this method will
* cause the editor to call
- * {@link android.inputmethodservice.InputMethodService#onUpdateSelection(int, int, int, int, int, int)}
- * on the current IME after the batch input is over.</p>
+ * {@link android.inputmethodservice.InputMethodService#onUpdateSelection(int, int, int, int,
+ * int, int)} on the current IME after the batch input is over.</p>
*
* <p><strong>Editor authors:</strong> please keep in mind the
* text may be very similar or completely different than what was
@@ -455,7 +454,7 @@
* @return true on success, false if the input connection is no longer
* valid.
*/
- public boolean setComposingText(CharSequence text, int newCursorPosition);
+ boolean setComposingText(CharSequence text, int newCursorPosition);
/**
* Mark a certain region of text as composing text. If there was a
@@ -474,8 +473,8 @@
* <p>Since this does not change the contents of the text, editors should not call
* {@link InputMethodManager#updateSelection(View, int, int, int, int)} and
* IMEs should not receive
- * {@link android.inputmethodservice.InputMethodService#onUpdateSelection(int, int, int, int, int, int)}.
- * </p>
+ * {@link android.inputmethodservice.InputMethodService#onUpdateSelection(int, int, int, int,
+ * int, int)}.</p>
*
* <p>This has no impact on the cursor/selection position. It may
* result in the cursor being anywhere inside or outside the
@@ -488,7 +487,7 @@
* valid. In {@link android.os.Build.VERSION_CODES#N} and later, false is returned when the
* target application does not implement this method.
*/
- public boolean setComposingRegion(int start, int end);
+ boolean setComposingRegion(int start, int end);
/**
* Have the text editor finish whatever composing text is
@@ -507,7 +506,7 @@
* @return true on success, false if the input connection
* is no longer valid.
*/
- public boolean finishComposingText();
+ boolean finishComposingText();
/**
* Commit text to the text box and set the new cursor position.
@@ -522,8 +521,8 @@
* then {@link #finishComposingText()}.</p>
*
* <p>Calling this method will cause the editor to call
- * {@link android.inputmethodservice.InputMethodService#onUpdateSelection(int, int, int, int, int, int)}
- * on the current IME after the batch input is over.
+ * {@link android.inputmethodservice.InputMethodService#onUpdateSelection(int, int, int, int,
+ * int, int)} on the current IME after the batch input is over.
* <strong>Editor authors</strong>, for this to happen you need to
* make the changes known to the input method by calling
* {@link InputMethodManager#updateSelection(View, int, int, int, int)},
@@ -543,7 +542,7 @@
* @return true on success, false if the input connection is no longer
* valid.
*/
- public boolean commitText(CharSequence text, int newCursorPosition);
+ boolean commitText(CharSequence text, int newCursorPosition);
/**
* Commit a completion the user has selected from the possible ones
@@ -569,8 +568,8 @@
*
* <p>Calling this method (with a valid {@link CompletionInfo} object)
* will cause the editor to call
- * {@link android.inputmethodservice.InputMethodService#onUpdateSelection(int, int, int, int, int, int)}
- * on the current IME after the batch input is over.
+ * {@link android.inputmethodservice.InputMethodService#onUpdateSelection(int, int, int, int,
+ * int, int)} on the current IME after the batch input is over.
* <strong>Editor authors</strong>, for this to happen you need to
* make the changes known to the input method by calling
* {@link InputMethodManager#updateSelection(View, int, int, int, int)},
@@ -581,15 +580,15 @@
* @return true on success, false if the input connection is no longer
* valid.
*/
- public boolean commitCompletion(CompletionInfo text);
+ boolean commitCompletion(CompletionInfo text);
/**
* Commit a correction automatically performed on the raw user's input. A
* typical example would be to correct typos using a dictionary.
*
* <p>Calling this method will cause the editor to call
- * {@link android.inputmethodservice.InputMethodService#onUpdateSelection(int, int, int, int, int, int)}
- * on the current IME after the batch input is over.
+ * {@link android.inputmethodservice.InputMethodService#onUpdateSelection(int, int, int, int,
+ * int, int)} on the current IME after the batch input is over.
* <strong>Editor authors</strong>, for this to happen you need to
* make the changes known to the input method by calling
* {@link InputMethodManager#updateSelection(View, int, int, int, int)},
@@ -601,7 +600,7 @@
* In {@link android.os.Build.VERSION_CODES#N} and later, returns false
* when the target application does not implement this method.
*/
- public boolean commitCorrection(CorrectionInfo correctionInfo);
+ boolean commitCorrection(CorrectionInfo correctionInfo);
/**
* Set the selection of the text editor. To set the cursor
@@ -609,8 +608,8 @@
*
* <p>Since this moves the cursor, calling this method will cause
* the editor to call
- * {@link android.inputmethodservice.InputMethodService#onUpdateSelection(int, int, int, int, int, int)}
- * on the current IME after the batch input is over.
+ * {@link android.inputmethodservice.InputMethodService#onUpdateSelection(int, int, int, int,
+ * int, int)} on the current IME after the batch input is over.
* <strong>Editor authors</strong>, for this to happen you need to
* make the changes known to the input method by calling
* {@link InputMethodManager#updateSelection(View, int, int, int, int)},
@@ -628,7 +627,7 @@
* @return true on success, false if the input connection is no longer
* valid.
*/
- public boolean setSelection(int start, int end);
+ boolean setSelection(int start, int end);
/**
* Have the editor perform an action it has said it can do.
@@ -642,7 +641,7 @@
* @return true on success, false if the input connection is no longer
* valid.
*/
- public boolean performEditorAction(int editorAction);
+ boolean performEditorAction(int editorAction);
/**
* Perform a context menu action on the field. The given id may be one of:
@@ -652,7 +651,7 @@
* {@link android.R.id#paste}, {@link android.R.id#copyUrl},
* or {@link android.R.id#switchInputMethod}
*/
- public boolean performContextMenuAction(int id);
+ boolean performContextMenuAction(int id);
/**
* Tell the editor that you are starting a batch of editor
@@ -662,8 +661,8 @@
*
* <p><strong>IME authors:</strong> use this to avoid getting
* calls to
- * {@link android.inputmethodservice.InputMethodService#onUpdateSelection(int, int, int, int, int, int)}
- * corresponding to intermediate state. Also, use this to avoid
+ * {@link android.inputmethodservice.InputMethodService#onUpdateSelection(int, int, int, int,
+ * int, int)} corresponding to intermediate state. Also, use this to avoid
* flickers that may arise from displaying intermediate state. Be
* sure to call {@link #endBatchEdit} for each call to this, or
* you may block updates in the editor.</p>
@@ -678,7 +677,7 @@
* this method starts a batch edit, that means it will always return true
* unless the input connection is no longer valid.
*/
- public boolean beginBatchEdit();
+ boolean beginBatchEdit();
/**
* Tell the editor that you are done with a batch edit previously
@@ -696,7 +695,7 @@
* the latest one (in other words, if the nesting count is > 0), false
* otherwise or if the input connection is no longer valid.
*/
- public boolean endBatchEdit();
+ boolean endBatchEdit();
/**
* Send a key event to the process that is currently attached
@@ -734,7 +733,7 @@
* @see KeyCharacterMap#PREDICTIVE
* @see KeyCharacterMap#ALPHA
*/
- public boolean sendKeyEvent(KeyEvent event);
+ boolean sendKeyEvent(KeyEvent event);
/**
* Clear the given meta key pressed states in the given input
@@ -749,7 +748,7 @@
* @return true on success, false if the input connection is no longer
* valid.
*/
- public boolean clearMetaKeyStates(int states);
+ boolean clearMetaKeyStates(int states);
/**
* Called back when the connected IME switches between fullscreen and normal modes.
@@ -766,7 +765,7 @@
* devices.
* @see InputMethodManager#isFullscreenMode()
*/
- public boolean reportFullscreenMode(boolean enabled);
+ boolean reportFullscreenMode(boolean enabled);
/**
* API to send private commands from an input method to its
@@ -786,7 +785,7 @@
* associated editor understood it), false if the input connection is no longer
* valid.
*/
- public boolean performPrivateCommand(String action, Bundle data);
+ boolean performPrivateCommand(String action, Bundle data);
/**
* The editor is requested to call
@@ -794,7 +793,7 @@
* once, as soon as possible, regardless of cursor/anchor position changes. This flag can be
* used together with {@link #CURSOR_UPDATE_MONITOR}.
*/
- public static final int CURSOR_UPDATE_IMMEDIATE = 1 << 0;
+ int CURSOR_UPDATE_IMMEDIATE = 1 << 0;
/**
* The editor is requested to call
@@ -805,7 +804,7 @@
* This flag can be used together with {@link #CURSOR_UPDATE_IMMEDIATE}.
* </p>
*/
- public static final int CURSOR_UPDATE_MONITOR = 1 << 1;
+ int CURSOR_UPDATE_MONITOR = 1 << 1;
/**
* Called by the input method to ask the editor for calling back
@@ -821,7 +820,7 @@
* In {@link android.os.Build.VERSION_CODES#N} and later, returns {@code false} also when the
* target application does not implement this method.
*/
- public boolean requestCursorUpdates(int cursorUpdateMode);
+ boolean requestCursorUpdates(int cursorUpdateMode);
/**
* Called by the {@link InputMethodManager} to enable application developers to specify a
@@ -832,7 +831,7 @@
*
* @return {@code null} to use the default {@link Handler}.
*/
- public Handler getHandler();
+ Handler getHandler();
/**
* Called by the system up to only once to notify that the system is about to invalidate
@@ -846,7 +845,7 @@
*
* <p>Note: This does nothing when called from input methods.</p>
*/
- public void closeConnection();
+ void closeConnection();
/**
* When this flag is used, the editor will be able to request read access to the content URI
@@ -863,7 +862,7 @@
* client is able to request a temporary read-only access even after the current IME is switched
* to any other IME as long as the client keeps {@link InputContentInfo} object.</p>
**/
- public static int INPUT_CONTENT_GRANT_READ_URI_PERMISSION =
+ int INPUT_CONTENT_GRANT_READ_URI_PERMISSION =
android.content.Intent.FLAG_GRANT_READ_URI_PERMISSION; // 0x00000001
/**
@@ -897,6 +896,6 @@
* @return {@code true} if this request is accepted by the application, whether the request
* is already handled or still being handled in background, {@code false} otherwise.
*/
- public boolean commitContent(@NonNull InputContentInfo inputContentInfo, int flags,
+ boolean commitContent(@NonNull InputContentInfo inputContentInfo, int flags,
@Nullable Bundle opts);
}
diff --git a/core/java/android/view/inputmethod/InputConnectionWrapper.java b/core/java/android/view/inputmethod/InputConnectionWrapper.java
index 317730c..f671e22 100644
--- a/core/java/android/view/inputmethod/InputConnectionWrapper.java
+++ b/core/java/android/view/inputmethod/InputConnectionWrapper.java
@@ -1,17 +1,17 @@
/*
- * Copyright (C) 2007-2008 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
* Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.inputmethod;
@@ -74,6 +74,7 @@
* {@inheritDoc}
* @throws NullPointerException if the target is {@code null}.
*/
+ @Override
public CharSequence getTextBeforeCursor(int n, int flags) {
return mTarget.getTextBeforeCursor(n, flags);
}
@@ -82,6 +83,7 @@
* {@inheritDoc}
* @throws NullPointerException if the target is {@code null}.
*/
+ @Override
public CharSequence getTextAfterCursor(int n, int flags) {
return mTarget.getTextAfterCursor(n, flags);
}
@@ -90,6 +92,7 @@
* {@inheritDoc}
* @throws NullPointerException if the target is {@code null}.
*/
+ @Override
public CharSequence getSelectedText(int flags) {
return mTarget.getSelectedText(flags);
}
@@ -98,6 +101,7 @@
* {@inheritDoc}
* @throws NullPointerException if the target is {@code null}.
*/
+ @Override
public int getCursorCapsMode(int reqModes) {
return mTarget.getCursorCapsMode(reqModes);
}
@@ -106,6 +110,7 @@
* {@inheritDoc}
* @throws NullPointerException if the target is {@code null}.
*/
+ @Override
public ExtractedText getExtractedText(ExtractedTextRequest request, int flags) {
return mTarget.getExtractedText(request, flags);
}
@@ -114,6 +119,7 @@
* {@inheritDoc}
* @throws NullPointerException if the target is {@code null}.
*/
+ @Override
public boolean deleteSurroundingTextInCodePoints(int beforeLength, int afterLength) {
return mTarget.deleteSurroundingTextInCodePoints(beforeLength, afterLength);
}
@@ -122,6 +128,7 @@
* {@inheritDoc}
* @throws NullPointerException if the target is {@code null}.
*/
+ @Override
public boolean deleteSurroundingText(int beforeLength, int afterLength) {
return mTarget.deleteSurroundingText(beforeLength, afterLength);
}
@@ -130,6 +137,7 @@
* {@inheritDoc}
* @throws NullPointerException if the target is {@code null}.
*/
+ @Override
public boolean setComposingText(CharSequence text, int newCursorPosition) {
return mTarget.setComposingText(text, newCursorPosition);
}
@@ -138,6 +146,7 @@
* {@inheritDoc}
* @throws NullPointerException if the target is {@code null}.
*/
+ @Override
public boolean setComposingRegion(int start, int end) {
return mTarget.setComposingRegion(start, end);
}
@@ -146,6 +155,7 @@
* {@inheritDoc}
* @throws NullPointerException if the target is {@code null}.
*/
+ @Override
public boolean finishComposingText() {
return mTarget.finishComposingText();
}
@@ -154,6 +164,7 @@
* {@inheritDoc}
* @throws NullPointerException if the target is {@code null}.
*/
+ @Override
public boolean commitText(CharSequence text, int newCursorPosition) {
return mTarget.commitText(text, newCursorPosition);
}
@@ -162,6 +173,7 @@
* {@inheritDoc}
* @throws NullPointerException if the target is {@code null}.
*/
+ @Override
public boolean commitCompletion(CompletionInfo text) {
return mTarget.commitCompletion(text);
}
@@ -170,6 +182,7 @@
* {@inheritDoc}
* @throws NullPointerException if the target is {@code null}.
*/
+ @Override
public boolean commitCorrection(CorrectionInfo correctionInfo) {
return mTarget.commitCorrection(correctionInfo);
}
@@ -178,6 +191,7 @@
* {@inheritDoc}
* @throws NullPointerException if the target is {@code null}.
*/
+ @Override
public boolean setSelection(int start, int end) {
return mTarget.setSelection(start, end);
}
@@ -186,6 +200,7 @@
* {@inheritDoc}
* @throws NullPointerException if the target is {@code null}.
*/
+ @Override
public boolean performEditorAction(int editorAction) {
return mTarget.performEditorAction(editorAction);
}
@@ -194,6 +209,7 @@
* {@inheritDoc}
* @throws NullPointerException if the target is {@code null}.
*/
+ @Override
public boolean performContextMenuAction(int id) {
return mTarget.performContextMenuAction(id);
}
@@ -202,6 +218,7 @@
* {@inheritDoc}
* @throws NullPointerException if the target is {@code null}.
*/
+ @Override
public boolean beginBatchEdit() {
return mTarget.beginBatchEdit();
}
@@ -210,6 +227,7 @@
* {@inheritDoc}
* @throws NullPointerException if the target is {@code null}.
*/
+ @Override
public boolean endBatchEdit() {
return mTarget.endBatchEdit();
}
@@ -218,6 +236,7 @@
* {@inheritDoc}
* @throws NullPointerException if the target is {@code null}.
*/
+ @Override
public boolean sendKeyEvent(KeyEvent event) {
return mTarget.sendKeyEvent(event);
}
@@ -226,6 +245,7 @@
* {@inheritDoc}
* @throws NullPointerException if the target is {@code null}.
*/
+ @Override
public boolean clearMetaKeyStates(int states) {
return mTarget.clearMetaKeyStates(states);
}
@@ -234,6 +254,7 @@
* {@inheritDoc}
* @throws NullPointerException if the target is {@code null}.
*/
+ @Override
public boolean reportFullscreenMode(boolean enabled) {
return mTarget.reportFullscreenMode(enabled);
}
@@ -242,6 +263,7 @@
* {@inheritDoc}
* @throws NullPointerException if the target is {@code null}.
*/
+ @Override
public boolean performPrivateCommand(String action, Bundle data) {
return mTarget.performPrivateCommand(action, data);
}
@@ -250,6 +272,7 @@
* {@inheritDoc}
* @throws NullPointerException if the target is {@code null}.
*/
+ @Override
public boolean requestCursorUpdates(int cursorUpdateMode) {
return mTarget.requestCursorUpdates(cursorUpdateMode);
}
@@ -258,6 +281,7 @@
* {@inheritDoc}
* @throws NullPointerException if the target is {@code null}.
*/
+ @Override
public Handler getHandler() {
return mTarget.getHandler();
}
@@ -266,6 +290,7 @@
* {@inheritDoc}
* @throws NullPointerException if the target is {@code null}.
*/
+ @Override
public void closeConnection() {
mTarget.closeConnection();
}
@@ -274,6 +299,7 @@
* {@inheritDoc}
* @throws NullPointerException if the target is {@code null}.
*/
+ @Override
public boolean commitContent(InputContentInfo inputContentInfo, int flags, Bundle opts) {
return mTarget.commitContent(inputContentInfo, flags, opts);
}
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index 80d7b6b7..7db5c32 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -337,20 +337,23 @@
int mCursorCandEnd;
/**
- * Represents an invalid action notification sequence number. {@link InputMethodManagerService}
- * always issues a positive integer for action notification sequence numbers. Thus -1 is
- * guaranteed to be different from any valid sequence number.
+ * Represents an invalid action notification sequence number.
+ * {@link com.android.server.InputMethodManagerService} always issues a positive integer for
+ * action notification sequence numbers. Thus {@code -1} is guaranteed to be different from any
+ * valid sequence number.
*/
private static final int NOT_AN_ACTION_NOTIFICATION_SEQUENCE_NUMBER = -1;
/**
- * The next sequence number that is to be sent to {@link InputMethodManagerService} via
+ * The next sequence number that is to be sent to
+ * {@link com.android.server.InputMethodManagerService} via
* {@link IInputMethodManager#notifyUserAction(int)} at once when a user action is observed.
*/
private int mNextUserActionNotificationSequenceNumber =
NOT_AN_ACTION_NOTIFICATION_SEQUENCE_NUMBER;
/**
- * The last sequence number that is already sent to {@link InputMethodManagerService}.
+ * The last sequence number that is already sent to
+ * {@link com.android.server.InputMethodManagerService}.
*/
private int mLastSentUserActionNotificationSequenceNumber =
NOT_AN_ACTION_NOTIFICATION_SEQUENCE_NUMBER;
@@ -1079,15 +1082,15 @@
}
/**
- * Flag for {@link #hideSoftInputFromWindow} to indicate that the soft
- * input window should only be hidden if it was not explicitly shown
+ * Flag for {@link #hideSoftInputFromWindow} and {@link InputMethodService#requestHideSelf(int)}
+ * to indicate that the soft input window should only be hidden if it was not explicitly shown
* by the user.
*/
public static final int HIDE_IMPLICIT_ONLY = 0x0001;
/**
- * Flag for {@link #hideSoftInputFromWindow} to indicate that the soft
- * input window should normally be hidden, unless it was originally
+ * Flag for {@link #hideSoftInputFromWindow} and {@link InputMethodService#requestShowSelf(int)}
+ * to indicate that the soft input window should normally be hidden, unless it was originally
* shown with {@link #SHOW_FORCED}.
*/
public static final int HIDE_NOT_ALWAYS = 0x0002;
@@ -1255,12 +1258,7 @@
// The view is running on a different thread than our own, so
// we need to reschedule our work for over there.
if (DEBUG) Log.v(TAG, "Starting input: reschedule to view thread");
- vh.post(new Runnable() {
- @Override
- public void run() {
- startInputInner(startInputReason, null, 0, 0, 0);
- }
- });
+ vh.post(() -> startInputInner(startInputReason, null, 0, 0, 0));
return false;
}
@@ -1871,9 +1869,9 @@
* @param flags Provides additional operating flags. Currently may be
* 0 or have the {@link #HIDE_IMPLICIT_ONLY},
* {@link #HIDE_NOT_ALWAYS} bit set.
- * @deprecated Use {@link InputMethodService#hideSoftInputFromInputMethod(int)}
- * instead. This method was intended for IME developers who should be accessing APIs through
- * the service. APIs in this class are intended for app developers interacting with the IME.
+ * @deprecated Use {@link InputMethodService#requestHideSelf(int)} instead. This method was
+ * intended for IME developers who should be accessing APIs through the service. APIs in this
+ * class are intended for app developers interacting with the IME.
*/
@Deprecated
public void hideSoftInputFromInputMethod(IBinder token, int flags) {
@@ -1903,9 +1901,9 @@
* @param flags Provides additional operating flags. Currently may be
* 0 or have the {@link #SHOW_IMPLICIT} or
* {@link #SHOW_FORCED} bit set.
- * @deprecated Use {@link InputMethodService#showSoftInputFromInputMethod(int)}
- * instead. This method was intended for IME developers who should be accessing APIs through
- * the service. APIs in this class are intended for app developers interacting with the IME.
+ * @deprecated Use {@link InputMethodService#requestShowSelf(int)} instead. This method was
+ * intended for IME developers who should be accessing APIs through the service. APIs in this
+ * class are intended for app developers interacting with the IME.
*/
@Deprecated
public void showSoftInputFromInputMethod(IBinder token, int flags) {
@@ -2429,8 +2427,8 @@
* Allow the receiver of {@link InputContentInfo} to obtain a temporary read-only access
* permission to the content.
*
- * <p>See {@link android.inputmethodservice.InputMethodService#exposeContent(InputContentInfo, EditorInfo)}
- * for details.</p>
+ * <p>See {@link android.inputmethodservice.InputMethodService#exposeContent(InputContentInfo,
+ * InputConnection)} for details.</p>
*
* @param token Supplies the identifying token given to an input method when it was started,
* which allows it to perform this operation on itself.
diff --git a/core/java/android/view/textclassifier/EntityConfidence.java b/core/java/android/view/textclassifier/EntityConfidence.java
index 19660d9..69a59a5 100644
--- a/core/java/android/view/textclassifier/EntityConfidence.java
+++ b/core/java/android/view/textclassifier/EntityConfidence.java
@@ -18,6 +18,8 @@
import android.annotation.FloatRange;
import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
import android.util.ArrayMap;
import com.android.internal.util.Preconditions;
@@ -30,17 +32,16 @@
/**
* Helper object for setting and getting entity scores for classified text.
*
- * @param <T> the entity type.
* @hide
*/
-final class EntityConfidence<T> {
+final class EntityConfidence implements Parcelable {
- private final ArrayMap<T, Float> mEntityConfidence = new ArrayMap<>();
- private final ArrayList<T> mSortedEntities = new ArrayList<>();
+ private final ArrayMap<String, Float> mEntityConfidence = new ArrayMap<>();
+ private final ArrayList<String> mSortedEntities = new ArrayList<>();
EntityConfidence() {}
- EntityConfidence(@NonNull EntityConfidence<T> source) {
+ EntityConfidence(@NonNull EntityConfidence source) {
Preconditions.checkNotNull(source);
mEntityConfidence.putAll(source.mEntityConfidence);
mSortedEntities.addAll(source.mSortedEntities);
@@ -54,24 +55,16 @@
* @param source a map from entity to a confidence value in the range 0 (low confidence) to
* 1 (high confidence).
*/
- EntityConfidence(@NonNull Map<T, Float> source) {
+ EntityConfidence(@NonNull Map<String, Float> source) {
Preconditions.checkNotNull(source);
// Prune non-existent entities and clamp to 1.
mEntityConfidence.ensureCapacity(source.size());
- for (Map.Entry<T, Float> it : source.entrySet()) {
+ for (Map.Entry<String, Float> it : source.entrySet()) {
if (it.getValue() <= 0) continue;
mEntityConfidence.put(it.getKey(), Math.min(1, it.getValue()));
}
-
- // Create a list of entities sorted by decreasing confidence for getEntities().
- mSortedEntities.ensureCapacity(mEntityConfidence.size());
- mSortedEntities.addAll(mEntityConfidence.keySet());
- mSortedEntities.sort((e1, e2) -> {
- float score1 = mEntityConfidence.get(e1);
- float score2 = mEntityConfidence.get(e2);
- return Float.compare(score2, score1);
- });
+ resetSortedEntitiesFromMap();
}
/**
@@ -79,7 +72,7 @@
* high confidence to low confidence.
*/
@NonNull
- public List<T> getEntities() {
+ public List<String> getEntities() {
return Collections.unmodifiableList(mSortedEntities);
}
@@ -89,7 +82,7 @@
* classified text.
*/
@FloatRange(from = 0.0, to = 1.0)
- public float getConfidenceScore(T entity) {
+ public float getConfidenceScore(String entity) {
if (mEntityConfidence.containsKey(entity)) {
return mEntityConfidence.get(entity);
}
@@ -100,4 +93,51 @@
public String toString() {
return mEntityConfidence.toString();
}
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(mEntityConfidence.size());
+ for (Map.Entry<String, Float> entry : mEntityConfidence.entrySet()) {
+ dest.writeString(entry.getKey());
+ dest.writeFloat(entry.getValue());
+ }
+ }
+
+ public static final Parcelable.Creator<EntityConfidence> CREATOR =
+ new Parcelable.Creator<EntityConfidence>() {
+ @Override
+ public EntityConfidence createFromParcel(Parcel in) {
+ return new EntityConfidence(in);
+ }
+
+ @Override
+ public EntityConfidence[] newArray(int size) {
+ return new EntityConfidence[size];
+ }
+ };
+
+ private EntityConfidence(Parcel in) {
+ final int numEntities = in.readInt();
+ mEntityConfidence.ensureCapacity(numEntities);
+ for (int i = 0; i < numEntities; ++i) {
+ mEntityConfidence.put(in.readString(), in.readFloat());
+ }
+ resetSortedEntitiesFromMap();
+ }
+
+ private void resetSortedEntitiesFromMap() {
+ mSortedEntities.clear();
+ mSortedEntities.ensureCapacity(mEntityConfidence.size());
+ mSortedEntities.addAll(mEntityConfidence.keySet());
+ mSortedEntities.sort((e1, e2) -> {
+ float score1 = mEntityConfidence.get(e1);
+ float score2 = mEntityConfidence.get(e2);
+ return Float.compare(score2, score1);
+ });
+ }
}
diff --git a/core/java/android/view/textclassifier/TextClassification.java b/core/java/android/view/textclassifier/TextClassification.java
index 7ffbf63..7089677 100644
--- a/core/java/android/view/textclassifier/TextClassification.java
+++ b/core/java/android/view/textclassifier/TextClassification.java
@@ -22,8 +22,13 @@
import android.annotation.Nullable;
import android.content.Context;
import android.content.Intent;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.os.LocaleList;
+import android.os.Parcel;
+import android.os.Parcelable;
import android.util.ArrayMap;
import android.view.View.OnClickListener;
import android.view.textclassifier.TextClassifier.EntityType;
@@ -52,7 +57,7 @@
* Button button = new Button(context);
* button.setCompoundDrawablesWithIntrinsicBounds(classification.getIcon(), null, null, null);
* button.setText(classification.getLabel());
- * button.setOnClickListener(classification.getOnClickListener());
+ * button.setOnClickListener(v -> context.startActivity(classification.getIntent()));
* }</pre>
*
* <p>e.g. starting an action mode with menu items that can handle the classified text:
@@ -90,7 +95,6 @@
* ...
* });
* }</pre>
- *
*/
public final class TextClassification {
@@ -99,6 +103,10 @@
*/
static final TextClassification EMPTY = new TextClassification.Builder().build();
+ // TODO(toki): investigate a way to derive this based on device properties.
+ private static final int MAX_PRIMARY_ICON_SIZE = 192;
+ private static final int MAX_SECONDARY_ICON_SIZE = 144;
+
@NonNull private final String mText;
@Nullable private final Drawable mPrimaryIcon;
@Nullable private final String mPrimaryLabel;
@@ -107,8 +115,7 @@
@NonNull private final List<Drawable> mSecondaryIcons;
@NonNull private final List<String> mSecondaryLabels;
@NonNull private final List<Intent> mSecondaryIntents;
- @NonNull private final List<OnClickListener> mSecondaryOnClickListeners;
- @NonNull private final EntityConfidence<String> mEntityConfidence;
+ @NonNull private final EntityConfidence mEntityConfidence;
@NonNull private final String mSignature;
private TextClassification(
@@ -120,12 +127,10 @@
@NonNull List<Drawable> secondaryIcons,
@NonNull List<String> secondaryLabels,
@NonNull List<Intent> secondaryIntents,
- @NonNull List<OnClickListener> secondaryOnClickListeners,
@NonNull Map<String, Float> entityConfidence,
@NonNull String signature) {
Preconditions.checkArgument(secondaryLabels.size() == secondaryIntents.size());
Preconditions.checkArgument(secondaryIcons.size() == secondaryIntents.size());
- Preconditions.checkArgument(secondaryOnClickListeners.size() == secondaryIntents.size());
mText = text;
mPrimaryIcon = primaryIcon;
mPrimaryLabel = primaryLabel;
@@ -134,8 +139,7 @@
mSecondaryIcons = secondaryIcons;
mSecondaryLabels = secondaryLabels;
mSecondaryIntents = secondaryIntents;
- mSecondaryOnClickListeners = secondaryOnClickListeners;
- mEntityConfidence = new EntityConfidence<>(entityConfidence);
+ mEntityConfidence = new EntityConfidence(entityConfidence);
mSignature = signature;
}
@@ -186,7 +190,6 @@
* @see #getSecondaryIntent(int)
* @see #getSecondaryLabel(int)
* @see #getSecondaryIcon(int)
- * @see #getSecondaryOnClickListener(int)
*/
@IntRange(from = 0)
public int getSecondaryActionsCount() {
@@ -198,13 +201,10 @@
* classified text.
*
* @param index Index of the action to get the icon for.
- *
* @throws IndexOutOfBoundsException if the specified index is out of range.
- *
* @see #getSecondaryActionsCount() for the number of actions available.
* @see #getSecondaryIntent(int)
* @see #getSecondaryLabel(int)
- * @see #getSecondaryOnClickListener(int)
* @see #getIcon()
*/
@Nullable
@@ -228,13 +228,10 @@
* the classified text.
*
* @param index Index of the action to get the label for.
- *
* @throws IndexOutOfBoundsException if the specified index is out of range.
- *
* @see #getSecondaryActionsCount()
* @see #getSecondaryIntent(int)
* @see #getSecondaryIcon(int)
- * @see #getSecondaryOnClickListener(int)
* @see #getLabel()
*/
@Nullable
@@ -257,13 +254,10 @@
* Returns one of the <i>secondary</i> intents that may be fired to act on the classified text.
*
* @param index Index of the action to get the intent for.
- *
* @throws IndexOutOfBoundsException if the specified index is out of range.
- *
* @see #getSecondaryActionsCount()
* @see #getSecondaryLabel(int)
* @see #getSecondaryIcon(int)
- * @see #getSecondaryOnClickListener(int)
* @see #getIntent()
*/
@Nullable
@@ -282,29 +276,10 @@
}
/**
- * Returns one of the <i>secondary</i> OnClickListeners that may be triggered to act on the
- * classified text.
- *
- * @param index Index of the action to get the click listener for.
- *
- * @throws IndexOutOfBoundsException if the specified index is out of range.
- *
- * @see #getSecondaryActionsCount()
- * @see #getSecondaryIntent(int)
- * @see #getSecondaryLabel(int)
- * @see #getSecondaryIcon(int)
- * @see #getOnClickListener()
- */
- @Nullable
- public OnClickListener getSecondaryOnClickListener(int index) {
- return mSecondaryOnClickListeners.get(index);
- }
-
- /**
* Returns the <i>primary</i> OnClickListener that may be triggered to act on the classified
- * text.
- *
- * @see #getSecondaryOnClickListener(int)
+ * text. This field is not parcelable and will be null for all objects read from a parcel.
+ * Instead, call Context#startActivity(Intent) with the result of #getSecondaryIntent(int).
+ * Note that this may fail if the activity doesn't have permission to send the intent.
*/
@Nullable
public OnClickListener getOnClickListener() {
@@ -334,6 +309,42 @@
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.
*
@@ -349,6 +360,68 @@
}
/**
+ * Returns a Bitmap representation of the Drawable
+ *
+ * @param drawable The drawable to convert.
+ * @param maxDims The maximum edge length of the resulting bitmap (in pixels).
+ */
+ @Nullable
+ private static Bitmap drawableToBitmap(@Nullable Drawable drawable, int maxDims) {
+ if (drawable == null) {
+ return null;
+ }
+ final int actualWidth = Math.max(1, drawable.getIntrinsicWidth());
+ final int actualHeight = Math.max(1, drawable.getIntrinsicHeight());
+ final double scaleWidth = ((double) maxDims) / actualWidth;
+ final double scaleHeight = ((double) maxDims) / actualHeight;
+ final double scale = Math.min(1.0, Math.min(scaleWidth, scaleHeight));
+ final int width = (int) (actualWidth * scale);
+ final int height = (int) (actualHeight * scale);
+ if (drawable instanceof BitmapDrawable) {
+ final BitmapDrawable bitmapDrawable = (BitmapDrawable) drawable;
+ if (actualWidth != width || actualHeight != height) {
+ return Bitmap.createScaledBitmap(
+ bitmapDrawable.getBitmap(), width, height, /*filter=*/false);
+ } else {
+ return bitmapDrawable.getBitmap();
+ }
+ } else {
+ final Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
+ final Canvas canvas = new Canvas(bitmap);
+ drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
+ drawable.draw(canvas);
+ return bitmap;
+ }
+ }
+
+ /**
+ * Returns a list of drawables converted to Bitmaps
+ *
+ * @param drawables The drawables to convert.
+ * @param maxDims The maximum edge length of the resulting bitmaps (in pixels).
+ */
+ private static List<Bitmap> drawablesToBitmaps(List<Drawable> drawables, int maxDims) {
+ final List<Bitmap> bitmaps = new ArrayList<>(drawables.size());
+ for (Drawable drawable : drawables) {
+ bitmaps.add(drawableToBitmap(drawable, maxDims));
+ }
+ return bitmaps;
+ }
+
+ /** Returns a list of drawable wrappers for a list of bitmaps. */
+ private static List<Drawable> bitmapsToDrawables(List<Bitmap> bitmaps) {
+ final List<Drawable> drawables = new ArrayList<>(bitmaps.size());
+ for (Bitmap bitmap : bitmaps) {
+ if (bitmap != null) {
+ drawables.add(new BitmapDrawable(null, bitmap));
+ } else {
+ drawables.add(null);
+ }
+ }
+ return drawables;
+ }
+
+ /**
* Builder for building {@link TextClassification} objects.
*
* <p>e.g.
@@ -358,9 +431,9 @@
* .setText(classifiedText)
* .setEntityType(TextClassifier.TYPE_EMAIL, 0.9)
* .setEntityType(TextClassifier.TYPE_OTHER, 0.1)
- * .setPrimaryAction(intent, label, icon, onClickListener)
- * .addSecondaryAction(intent1, label1, icon1, onClickListener1)
- * .addSecondaryAction(intent2, label2, icon2, onClickListener2)
+ * .setPrimaryAction(intent, label, icon)
+ * .addSecondaryAction(intent1, label1, icon1)
+ * .addSecondaryAction(intent2, label2, icon2)
* .build();
* }</pre>
*/
@@ -370,7 +443,6 @@
@NonNull private final List<Drawable> mSecondaryIcons = new ArrayList<>();
@NonNull private final List<String> mSecondaryLabels = new ArrayList<>();
@NonNull private final List<Intent> mSecondaryIntents = new ArrayList<>();
- @NonNull private final List<OnClickListener> mSecondaryOnClickListeners = new ArrayList<>();
@NonNull private final Map<String, Float> mEntityConfidence = new ArrayMap<>();
@Nullable Drawable mPrimaryIcon;
@Nullable String mPrimaryLabel;
@@ -413,16 +485,14 @@
* <p><stong>Note: </stong> If all input parameters are set to null, this method will be a
* no-op.
*
- * @see #setPrimaryAction(Intent, String, Drawable, OnClickListener)
+ * @see #setPrimaryAction(Intent, String, Drawable)
*/
public Builder addSecondaryAction(
- @Nullable Intent intent, @Nullable String label, @Nullable Drawable icon,
- @Nullable OnClickListener onClickListener) {
- if (intent != null || label != null || icon != null || onClickListener != null) {
+ @Nullable Intent intent, @Nullable String label, @Nullable Drawable icon) {
+ if (intent != null || label != null || icon != null) {
mSecondaryIntents.add(intent);
mSecondaryLabels.add(label);
mSecondaryIcons.add(icon);
- mSecondaryOnClickListeners.add(onClickListener);
}
return this;
}
@@ -432,7 +502,6 @@
*/
public Builder clearSecondaryActions() {
mSecondaryIntents.clear();
- mSecondaryOnClickListeners.clear();
mSecondaryLabels.clear();
mSecondaryIcons.clear();
return this;
@@ -440,26 +509,23 @@
/**
* Sets the <i>primary</i> action that may be performed on the classified text. This is
- * equivalent to calling {@code
- * setIntent(intent).setLabel(label).setIcon(icon).setOnClickListener(onClickListener)}.
+ * equivalent to calling {@code setIntent(intent).setLabel(label).setIcon(icon)}.
*
* <p><strong>Note: </strong>If all input parameters are null, there will be no
* <i>primary</i> action but there may still be <i>secondary</i> actions.
*
- * @see #addSecondaryAction(Intent, String, Drawable, OnClickListener)
+ * @see #addSecondaryAction(Intent, String, Drawable)
*/
public Builder setPrimaryAction(
- @Nullable Intent intent, @Nullable String label, @Nullable Drawable icon,
- @Nullable OnClickListener onClickListener) {
- return setIntent(intent).setLabel(label).setIcon(icon)
- .setOnClickListener(onClickListener);
+ @Nullable Intent intent, @Nullable String label, @Nullable Drawable icon) {
+ return setIntent(intent).setLabel(label).setIcon(icon);
}
/**
* Sets the icon for the <i>primary</i> action that may be rendered on a widget used to act
* on the classified text.
*
- * @see #setPrimaryAction(Intent, String, Drawable, OnClickListener)
+ * @see #setPrimaryAction(Intent, String, Drawable)
*/
public Builder setIcon(@Nullable Drawable icon) {
mPrimaryIcon = icon;
@@ -470,7 +536,7 @@
* Sets the label for the <i>primary</i> action that may be rendered on a widget used to
* act on the classified text.
*
- * @see #setPrimaryAction(Intent, String, Drawable, OnClickListener)
+ * @see #setPrimaryAction(Intent, String, Drawable)
*/
public Builder setLabel(@Nullable String label) {
mPrimaryLabel = label;
@@ -481,7 +547,7 @@
* Sets the intent for the <i>primary</i> action that may be fired to act on the classified
* text.
*
- * @see #setPrimaryAction(Intent, String, Drawable, OnClickListener)
+ * @see #setPrimaryAction(Intent, String, Drawable)
*/
public Builder setIntent(@Nullable Intent intent) {
mPrimaryIntent = intent;
@@ -490,9 +556,8 @@
/**
* Sets the OnClickListener for the <i>primary</i> action that may be triggered to act on
- * the classified text.
- *
- * @see #setPrimaryAction(Intent, String, Drawable, OnClickListener)
+ * the classified text. This field is not parcelable and will always be null when the
+ * object is read from a parcel.
*/
public Builder setOnClickListener(@Nullable OnClickListener onClickListener) {
mPrimaryOnClickListener = onClickListener;
@@ -515,10 +580,8 @@
public TextClassification build() {
return new TextClassification(
mText,
- mPrimaryIcon, mPrimaryLabel,
- mPrimaryIntent, mPrimaryOnClickListener,
- mSecondaryIcons, mSecondaryLabels,
- mSecondaryIntents, mSecondaryOnClickListeners,
+ mPrimaryIcon, mPrimaryLabel, mPrimaryIntent, mPrimaryOnClickListener,
+ mSecondaryIcons, mSecondaryLabels, mSecondaryIntents,
mEntityConfidence, mSignature);
}
}
@@ -526,9 +589,11 @@
/**
* Optional input parameters for generating TextClassification.
*/
- public static final class Options {
+ public static final class Options implements Parcelable {
- private LocaleList mDefaultLocales;
+ private @Nullable LocaleList mDefaultLocales;
+
+ public Options() {}
/**
* @param defaultLocales ordered list of locale preferences that may be used to disambiguate
@@ -548,5 +613,80 @@
public LocaleList getDefaultLocales() {
return mDefaultLocales;
}
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(mDefaultLocales != null ? 1 : 0);
+ if (mDefaultLocales != null) {
+ mDefaultLocales.writeToParcel(dest, flags);
+ }
+ }
+
+ public static final Parcelable.Creator<Options> CREATOR =
+ new Parcelable.Creator<Options>() {
+ @Override
+ public Options createFromParcel(Parcel in) {
+ return new Options(in);
+ }
+
+ @Override
+ public Options[] newArray(int size) {
+ return new Options[size];
+ }
+ };
+
+ private Options(Parcel in) {
+ if (in.readInt() > 0) {
+ mDefaultLocales = LocaleList.CREATOR.createFromParcel(in);
+ }
+ }
+ }
+
+ /**
+ * Parcelable wrapper for TextClassification objects.
+ * @hide
+ */
+ public static final class ParcelableWrapper implements Parcelable {
+
+ @NonNull private TextClassification mTextClassification;
+
+ public ParcelableWrapper(@NonNull TextClassification textClassification) {
+ Preconditions.checkNotNull(textClassification);
+ mTextClassification = textClassification;
+ }
+
+ @NonNull
+ public TextClassification getTextClassification() {
+ return mTextClassification;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ mTextClassification.writeToParcel(dest, flags);
+ }
+
+ 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];
+ }
+ };
+
}
}
diff --git a/core/java/android/view/textclassifier/TextClassifier.java b/core/java/android/view/textclassifier/TextClassifier.java
index ed60430..b602095 100644
--- a/core/java/android/view/textclassifier/TextClassifier.java
+++ b/core/java/android/view/textclassifier/TextClassifier.java
@@ -23,6 +23,8 @@
import android.annotation.StringDef;
import android.annotation.WorkerThread;
import android.os.LocaleList;
+import android.os.Parcel;
+import android.os.Parcelable;
import android.util.ArraySet;
import com.android.internal.util.Preconditions;
@@ -305,7 +307,7 @@
*
* Configs are initially based on a predefined preset, and can be modified from there.
*/
- final class EntityConfig {
+ final class EntityConfig implements Parcelable {
private final @TextClassifier.EntityPreset int mEntityPreset;
private final Collection<String> mExcludedEntityTypes;
private final Collection<String> mIncludedEntityTypes;
@@ -355,6 +357,37 @@
}
return Collections.unmodifiableList(entities);
}
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(mEntityPreset);
+ dest.writeStringList(new ArrayList<>(mExcludedEntityTypes));
+ dest.writeStringList(new ArrayList<>(mIncludedEntityTypes));
+ }
+
+ public static final Parcelable.Creator<EntityConfig> CREATOR =
+ new Parcelable.Creator<EntityConfig>() {
+ @Override
+ public EntityConfig createFromParcel(Parcel in) {
+ return new EntityConfig(in);
+ }
+
+ @Override
+ public EntityConfig[] newArray(int size) {
+ return new EntityConfig[size];
+ }
+ };
+
+ private EntityConfig(Parcel in) {
+ mEntityPreset = in.readInt();
+ mExcludedEntityTypes = new ArraySet<>(in.createStringArrayList());
+ mIncludedEntityTypes = new ArraySet<>(in.createStringArrayList());
+ }
}
/**
diff --git a/core/java/android/view/textclassifier/TextClassifierConstants.java b/core/java/android/view/textclassifier/TextClassifierConstants.java
index 51e6168..00695b7 100644
--- a/core/java/android/view/textclassifier/TextClassifierConstants.java
+++ b/core/java/android/view/textclassifier/TextClassifierConstants.java
@@ -45,19 +45,24 @@
"smart_selection_dark_launch";
private static final String SMART_SELECTION_ENABLED_FOR_EDIT_TEXT =
"smart_selection_enabled_for_edit_text";
+ private static final String SMART_LINKIFY_ENABLED =
+ "smart_linkify_enabled";
private static final boolean SMART_SELECTION_DARK_LAUNCH_DEFAULT = false;
private static final boolean SMART_SELECTION_ENABLED_FOR_EDIT_TEXT_DEFAULT = true;
+ private static final boolean SMART_LINKIFY_ENABLED_DEFAULT = true;
/** Default settings. */
static final TextClassifierConstants DEFAULT = new TextClassifierConstants();
private final boolean mDarkLaunch;
private final boolean mSuggestSelectionEnabledForEditableText;
+ private final boolean mSmartLinkifyEnabled;
private TextClassifierConstants() {
mDarkLaunch = SMART_SELECTION_DARK_LAUNCH_DEFAULT;
mSuggestSelectionEnabledForEditableText = SMART_SELECTION_ENABLED_FOR_EDIT_TEXT_DEFAULT;
+ mSmartLinkifyEnabled = SMART_LINKIFY_ENABLED_DEFAULT;
}
private TextClassifierConstants(@Nullable String settings) {
@@ -74,6 +79,9 @@
mSuggestSelectionEnabledForEditableText = parser.getBoolean(
SMART_SELECTION_ENABLED_FOR_EDIT_TEXT,
SMART_SELECTION_ENABLED_FOR_EDIT_TEXT_DEFAULT);
+ mSmartLinkifyEnabled = parser.getBoolean(
+ SMART_LINKIFY_ENABLED,
+ SMART_LINKIFY_ENABLED_DEFAULT);
}
static TextClassifierConstants loadFromString(String settings) {
@@ -87,4 +95,8 @@
public boolean isSuggestSelectionEnabledForEditableText() {
return mSuggestSelectionEnabledForEditableText;
}
+
+ public boolean isSmartLinkifyEnabled() {
+ return mSmartLinkifyEnabled;
+ }
}
diff --git a/core/java/android/view/textclassifier/TextClassifierImpl.java b/core/java/android/view/textclassifier/TextClassifierImpl.java
index aea3cb0..7db0e76 100644
--- a/core/java/android/view/textclassifier/TextClassifierImpl.java
+++ b/core/java/android/view/textclassifier/TextClassifierImpl.java
@@ -32,7 +32,6 @@
import android.provider.Settings;
import android.text.util.Linkify;
import android.util.Patterns;
-import android.view.View.OnClickListener;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.logging.MetricsLogger;
@@ -187,6 +186,11 @@
Utils.validateInput(text);
final String textString = text.toString();
final TextLinks.Builder builder = new TextLinks.Builder(textString);
+
+ if (!getSettings().isSmartLinkifyEnabled()) {
+ return builder.build();
+ }
+
try {
final LocaleList defaultLocales = options != null ? options.getDefaultLocales() : null;
final Collection<String> entitiesToIdentify =
@@ -457,12 +461,10 @@
}
}
final String labelString = (label != null) ? label.toString() : null;
- final OnClickListener onClickListener =
- TextClassification.createStartActivityOnClickListener(mContext, intent);
if (i == 0) {
- builder.setPrimaryAction(intent, labelString, icon, onClickListener);
+ builder.setPrimaryAction(intent, labelString, icon);
} else {
- builder.addSecondaryAction(intent, labelString, icon, onClickListener);
+ builder.addSecondaryAction(intent, labelString, icon);
}
}
}
diff --git a/core/java/android/view/textclassifier/TextLinks.java b/core/java/android/view/textclassifier/TextLinks.java
index 6c587cf..ba854e0 100644
--- a/core/java/android/view/textclassifier/TextLinks.java
+++ b/core/java/android/view/textclassifier/TextLinks.java
@@ -20,6 +20,8 @@
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;
@@ -38,7 +40,7 @@
* A collection of links, representing subsequences of text and the entity types (phone number,
* address, url, etc) they may be.
*/
-public final class TextLinks {
+public final class TextLinks implements Parcelable {
private final String mFullText;
private final List<TextLink> mLinks;
@@ -83,11 +85,40 @@
return true;
}
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeString(mFullText);
+ dest.writeTypedList(mLinks);
+ }
+
+ public static final Parcelable.Creator<TextLinks> CREATOR =
+ new Parcelable.Creator<TextLinks>() {
+ @Override
+ public TextLinks createFromParcel(Parcel in) {
+ return new TextLinks(in);
+ }
+
+ @Override
+ public TextLinks[] newArray(int size) {
+ return new TextLinks[size];
+ }
+ };
+
+ private TextLinks(Parcel in) {
+ mFullText = in.readString();
+ mLinks = in.createTypedArrayList(TextLink.CREATOR);
+ }
+
/**
* A link, identifying a substring of text and possible entity types for it.
*/
- public static final class TextLink {
- private final EntityConfidence<String> mEntityScores;
+ public static final class TextLink implements Parcelable {
+ private final EntityConfidence mEntityScores;
private final String mOriginalText;
private final int mStart;
private final int mEnd;
@@ -105,7 +136,7 @@
mOriginalText = originalText;
mStart = start;
mEnd = end;
- mEntityScores = new EntityConfidence<>(entityScores);
+ mEntityScores = new EntityConfidence(entityScores);
}
/**
@@ -153,16 +184,51 @@
@TextClassifier.EntityType String entityType) {
return mEntityScores.getConfidenceScore(entityType);
}
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ mEntityScores.writeToParcel(dest, flags);
+ dest.writeString(mOriginalText);
+ dest.writeInt(mStart);
+ dest.writeInt(mEnd);
+ }
+
+ public static final Parcelable.Creator<TextLink> CREATOR =
+ new Parcelable.Creator<TextLink>() {
+ @Override
+ public TextLink createFromParcel(Parcel in) {
+ return new TextLink(in);
+ }
+
+ @Override
+ public TextLink[] newArray(int size) {
+ return new TextLink[size];
+ }
+ };
+
+ private TextLink(Parcel in) {
+ mEntityScores = EntityConfidence.CREATOR.createFromParcel(in);
+ mOriginalText = in.readString();
+ mStart = in.readInt();
+ mEnd = in.readInt();
+ }
}
/**
* Optional input parameters for generating TextLinks.
*/
- public static final class Options {
+ public static final class Options implements Parcelable {
private LocaleList mDefaultLocales;
private TextClassifier.EntityConfig mEntityConfig;
+ public Options() {}
+
/**
* @param defaultLocales ordered list of locale preferences that may be used to
* disambiguate the provided text. If no locale preferences exist,
@@ -201,6 +267,45 @@
public TextClassifier.EntityConfig getEntityConfig() {
return mEntityConfig;
}
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(mDefaultLocales != null ? 1 : 0);
+ if (mDefaultLocales != null) {
+ mDefaultLocales.writeToParcel(dest, flags);
+ }
+ dest.writeInt(mEntityConfig != null ? 1 : 0);
+ if (mEntityConfig != null) {
+ mEntityConfig.writeToParcel(dest, flags);
+ }
+ }
+
+ public static final Parcelable.Creator<Options> CREATOR =
+ new Parcelable.Creator<Options>() {
+ @Override
+ public Options createFromParcel(Parcel in) {
+ return new Options(in);
+ }
+
+ @Override
+ public Options[] newArray(int size) {
+ return new Options[size];
+ }
+ };
+
+ private Options(Parcel in) {
+ if (in.readInt() > 0) {
+ mDefaultLocales = LocaleList.CREATOR.createFromParcel(in);
+ }
+ if (in.readInt() > 0) {
+ mEntityConfig = TextClassifier.EntityConfig.CREATOR.createFromParcel(in);
+ }
+ }
}
/**
diff --git a/core/java/android/view/textclassifier/TextSelection.java b/core/java/android/view/textclassifier/TextSelection.java
index 25e9e7e..774d42d 100644
--- a/core/java/android/view/textclassifier/TextSelection.java
+++ b/core/java/android/view/textclassifier/TextSelection.java
@@ -21,6 +21,8 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.LocaleList;
+import android.os.Parcel;
+import android.os.Parcelable;
import android.util.ArrayMap;
import android.view.textclassifier.TextClassifier.EntityType;
@@ -36,7 +38,7 @@
private final int mStartIndex;
private final int mEndIndex;
- @NonNull private final EntityConfidence<String> mEntityConfidence;
+ @NonNull private final EntityConfidence mEntityConfidence;
@NonNull private final String mSignature;
private TextSelection(
@@ -44,7 +46,7 @@
@NonNull String signature) {
mStartIndex = startIndex;
mEndIndex = endIndex;
- mEntityConfidence = new EntityConfidence<>(entityConfidence);
+ mEntityConfidence = new EntityConfidence(entityConfidence);
mSignature = signature;
}
@@ -110,6 +112,22 @@
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.
*/
@@ -170,11 +188,13 @@
/**
* Optional input parameters for generating TextSelection.
*/
- public static final class Options {
+ public static final class Options implements Parcelable {
- private LocaleList mDefaultLocales;
+ private @Nullable LocaleList mDefaultLocales;
private boolean mDarkLaunchAllowed;
+ public Options() {}
+
/**
* @param defaultLocales ordered list of locale preferences that may be used to disambiguate
* the provided text. If no locale preferences exist, set this to null or an empty
@@ -216,5 +236,82 @@
public boolean isDarkLaunchAllowed() {
return mDarkLaunchAllowed;
}
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(mDefaultLocales != null ? 1 : 0);
+ if (mDefaultLocales != null) {
+ mDefaultLocales.writeToParcel(dest, flags);
+ }
+ dest.writeInt(mDarkLaunchAllowed ? 1 : 0);
+ }
+
+ public static final Parcelable.Creator<Options> CREATOR =
+ new Parcelable.Creator<Options>() {
+ @Override
+ public Options createFromParcel(Parcel in) {
+ return new Options(in);
+ }
+
+ @Override
+ public Options[] newArray(int size) {
+ return new Options[size];
+ }
+ };
+
+ private Options(Parcel in) {
+ if (in.readInt() > 0) {
+ mDefaultLocales = LocaleList.CREATOR.createFromParcel(in);
+ }
+ mDarkLaunchAllowed = in.readInt() != 0;
+ }
+ }
+
+ /**
+ * Parcelable wrapper for TextSelection objects.
+ * @hide
+ */
+ public static final class ParcelableWrapper implements Parcelable {
+
+ @NonNull private TextSelection mTextSelection;
+
+ public ParcelableWrapper(@NonNull TextSelection textSelection) {
+ Preconditions.checkNotNull(textSelection);
+ mTextSelection = textSelection;
+ }
+
+ @NonNull
+ public TextSelection getTextSelection() {
+ return mTextSelection;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public 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];
+ }
+ };
+
}
}
diff --git a/core/java/android/webkit/WebViewFactory.java b/core/java/android/webkit/WebViewFactory.java
index b3522ec..e9fe481 100644
--- a/core/java/android/webkit/WebViewFactory.java
+++ b/core/java/android/webkit/WebViewFactory.java
@@ -27,7 +27,6 @@
import android.content.pm.Signature;
import android.os.RemoteException;
import android.os.ServiceManager;
-import android.os.StrictMode;
import android.os.Trace;
import android.util.AndroidRuntimeException;
import android.util.ArraySet;
@@ -251,7 +250,6 @@
"WebView.disableWebView() was called: WebView is disabled");
}
- StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskReads();
Trace.traceBegin(Trace.TRACE_TAG_WEBVIEW, "WebViewFactory.getProvider()");
try {
Class<WebViewFactoryProvider> providerClass = getProviderClass();
@@ -279,7 +277,6 @@
}
} finally {
Trace.traceEnd(Trace.TRACE_TAG_WEBVIEW);
- StrictMode.setThreadPolicy(oldPolicy);
}
}
}
diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java
index e0c897d..6bee58f 100644
--- a/core/java/android/widget/AbsListView.java
+++ b/core/java/android/widget/AbsListView.java
@@ -6849,7 +6849,7 @@
// detached and we do not allow detached views to fire accessibility
// events. So we are announcing that the subtree changed giving a chance
// to clients holding on to a view in this subtree to refresh it.
- notifyViewAccessibilityStateChangedIfNeeded(
+ notifyAccessibilityStateChanged(
AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE);
// Don't scrap views that have transient state.
diff --git a/core/java/android/widget/AdapterView.java b/core/java/android/widget/AdapterView.java
index 6c19256..08374cb 100644
--- a/core/java/android/widget/AdapterView.java
+++ b/core/java/android/widget/AdapterView.java
@@ -1093,7 +1093,7 @@
checkSelectionChanged();
}
- notifySubtreeAccessibilityStateChangedIfNeeded();
+ notifyAccessibilitySubtreeChanged();
}
/**
diff --git a/core/java/android/widget/CheckedTextView.java b/core/java/android/widget/CheckedTextView.java
index 92bfd56..af01a3e 100644
--- a/core/java/android/widget/CheckedTextView.java
+++ b/core/java/android/widget/CheckedTextView.java
@@ -132,7 +132,7 @@
if (mChecked != checked) {
mChecked = checked;
refreshDrawableState();
- notifyViewAccessibilityStateChangedIfNeeded(
+ notifyAccessibilityStateChanged(
AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
}
}
diff --git a/core/java/android/widget/CompoundButton.java b/core/java/android/widget/CompoundButton.java
index 0762b15..e57f153 100644
--- a/core/java/android/widget/CompoundButton.java
+++ b/core/java/android/widget/CompoundButton.java
@@ -158,7 +158,7 @@
mCheckedFromResource = false;
mChecked = checked;
refreshDrawableState();
- notifyViewAccessibilityStateChangedIfNeeded(
+ notifyAccessibilityStateChanged(
AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
// Avoid infinite recursions if setChecked() is called from a listener
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index d00aa55..247c806 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -2095,14 +2095,7 @@
if (!(mTextView.getText() instanceof Spannable)) {
return;
}
- Spannable text = (Spannable) mTextView.getText();
stopTextActionMode();
- if (mTextView.isTextSelectable()) {
- Selection.setSelection((Spannable) text, link.getStart(), link.getEnd());
- } else {
- //TODO: Nonselectable text
- }
-
getSelectionActionModeHelper().startLinkActionModeAsync(link);
}
@@ -2179,7 +2172,8 @@
return false;
}
- if (!checkField() || !mTextView.hasSelection()) {
+ if (actionMode != TextActionMode.TEXT_LINK
+ && (!checkField() || !mTextView.hasSelection())) {
return false;
}
@@ -4012,7 +4006,6 @@
if (isValidAssistMenuItem(
textClassification.getIcon(),
textClassification.getLabel(),
- textClassification.getOnClickListener(),
textClassification.getIntent())) {
final MenuItem item = menu.add(
TextView.ID_ASSIST, TextView.ID_ASSIST, MENU_ITEM_ORDER_ASSIST,
@@ -4020,14 +4013,15 @@
.setIcon(textClassification.getIcon())
.setIntent(textClassification.getIntent());
item.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
- mAssistClickHandlers.put(item, textClassification.getOnClickListener());
+ mAssistClickHandlers.put(
+ item, TextClassification.createStartActivityOnClickListener(
+ mTextView.getContext(), textClassification.getIntent()));
}
final int count = textClassification.getSecondaryActionsCount();
for (int i = 0; i < count; i++) {
if (!isValidAssistMenuItem(
textClassification.getSecondaryIcon(i),
textClassification.getSecondaryLabel(i),
- textClassification.getSecondaryOnClickListener(i),
textClassification.getSecondaryIntent(i))) {
continue;
}
@@ -4038,7 +4032,9 @@
.setIcon(textClassification.getSecondaryIcon(i))
.setIntent(textClassification.getSecondaryIntent(i));
item.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
- mAssistClickHandlers.put(item, textClassification.getSecondaryOnClickListener(i));
+ mAssistClickHandlers.put(item,
+ TextClassification.createStartActivityOnClickListener(
+ mTextView.getContext(), textClassification.getSecondaryIntent(i)));
}
}
@@ -4054,10 +4050,9 @@
}
}
- private boolean isValidAssistMenuItem(
- Drawable icon, CharSequence label, OnClickListener onClick, Intent intent) {
+ private boolean isValidAssistMenuItem(Drawable icon, CharSequence label, Intent intent) {
final boolean hasUi = icon != null || !TextUtils.isEmpty(label);
- final boolean hasAction = onClick != null || isSupportedIntent(intent);
+ final boolean hasAction = isSupportedIntent(intent);
return hasUi && hasAction;
}
diff --git a/core/java/android/widget/SelectionActionModeHelper.java b/core/java/android/widget/SelectionActionModeHelper.java
index 2c6466c..3bfa520 100644
--- a/core/java/android/widget/SelectionActionModeHelper.java
+++ b/core/java/android/widget/SelectionActionModeHelper.java
@@ -235,10 +235,13 @@
@Editor.TextActionMode int actionMode, @Nullable SelectionResult result) {
final CharSequence text = getText(mTextView);
if (result != null && text instanceof Spannable
- && (mTextView.isTextSelectable() || mTextView.isTextEditable())) {
+ && (mTextView.isTextSelectable()
+ || mTextView.isTextEditable()
+ || actionMode == Editor.TextActionMode.TEXT_LINK)) {
// Do not change the selection if TextClassifier should be dark launched.
if (!mTextView.getTextClassifier().getSettings().isDarkLaunch()) {
Selection.setSelection((Spannable) text, result.mStart, result.mEnd);
+ mTextView.invalidate();
}
mTextClassification = result.mClassification;
} else {
@@ -250,8 +253,17 @@
&& (mTextView.isTextSelectable() || mTextView.isTextEditable())) {
controller.show();
}
- if (result != null && actionMode == Editor.TextActionMode.SELECTION) {
- mSelectionTracker.onSmartSelection(result);
+ if (result != null) {
+ switch (actionMode) {
+ case Editor.TextActionMode.SELECTION:
+ mSelectionTracker.onSmartSelection(result);
+ break;
+ case Editor.TextActionMode.TEXT_LINK:
+ mSelectionTracker.onLinkSelected(result);
+ break;
+ default:
+ break;
+ }
}
}
mEditor.setRestartActionModeOnNextRefresh(false);
@@ -486,12 +498,24 @@
* Called when selection action mode is started and the results come from a classifier.
*/
public void onSmartSelection(SelectionResult result) {
+ onClassifiedSelection(result);
+ mLogger.logSelectionModified(
+ result.mStart, result.mEnd, result.mClassification, result.mSelection);
+ }
+
+ /**
+ * Called when link action mode is started and the classification comes from a classifier.
+ */
+ public void onLinkSelected(SelectionResult result) {
+ onClassifiedSelection(result);
+ // TODO: log (b/70246800)
+ }
+
+ private void onClassifiedSelection(SelectionResult result) {
if (isSelectionStarted()) {
mSelectionStart = result.mStart;
mSelectionEnd = result.mEnd;
mAllowReset = mSelectionStart != mOriginalStart || mSelectionEnd != mOriginalEnd;
- mLogger.logSelectionModified(
- result.mStart, result.mEnd, result.mClassification, result.mSelection);
}
}
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index cae2d7d9..8c4e422 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -77,8 +77,8 @@
import android.text.InputFilter;
import android.text.InputType;
import android.text.Layout;
+import android.text.MeasuredText;
import android.text.ParcelableSpan;
-import android.text.PremeasuredText;
import android.text.Selection;
import android.text.SpanWatcher;
import android.text.Spannable;
@@ -2365,7 +2365,7 @@
setText(mText);
if (hasPasswordTransformationMethod()) {
- notifyViewAccessibilityStateChangedIfNeeded(
+ notifyAccessibilityStateChanged(
AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
}
@@ -5393,7 +5393,7 @@
if (imm != null) imm.restartInput(this);
} else if (type == BufferType.SPANNABLE || mMovement != null) {
text = mSpannableFactory.newSpannable(text);
- } else if (!(text instanceof PremeasuredText || text instanceof CharWrapper)) {
+ } else if (!(text instanceof MeasuredText || text instanceof CharWrapper)) {
text = TextUtils.stringOrSpannedString(text);
}
@@ -5476,7 +5476,7 @@
sendOnTextChanged(text, 0, oldlen, textLength);
onTextChanged(text, 0, oldlen, textLength);
- notifyViewAccessibilityStateChangedIfNeeded(AccessibilityEvent.CONTENT_CHANGE_TYPE_TEXT);
+ notifyAccessibilityStateChanged(AccessibilityEvent.CONTENT_CHANGE_TYPE_TEXT);
if (needEditableForNotification) {
sendAfterTextChanged((Editable) text);
@@ -6210,7 +6210,7 @@
public void setError(CharSequence error, Drawable icon) {
createEditorIfNeeded();
mEditor.setError(error, icon);
- notifyViewAccessibilityStateChangedIfNeeded(
+ notifyAccessibilityStateChanged(
AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
}
@@ -11011,6 +11011,12 @@
return true;
case ID_COPY:
+ // For link action mode in a non-selectable/non-focusable TextView,
+ // make sure that we set the appropriate min/max.
+ final int selStart = getSelectionStart();
+ final int selEnd = getSelectionEnd();
+ min = Math.max(0, Math.min(selStart, selEnd));
+ max = Math.max(0, Math.max(selStart, selEnd));
final ClipData copyData = ClipData.newPlainText(null, getTransformedText(min, max));
if (setPrimaryClip(copyData)) {
stopTextActionMode();
@@ -11233,11 +11239,9 @@
*/
public boolean requestActionMode(@NonNull TextLinks.TextLink link) {
Preconditions.checkNotNull(link);
- if (mEditor != null) {
- mEditor.startLinkActionModeAsync(link);
- return true;
- }
- return false;
+ createEditorIfNeeded();
+ mEditor.startLinkActionModeAsync(link);
+ return true;
}
/**
* @hide
diff --git a/core/java/com/android/internal/app/HarmfulAppWarningActivity.java b/core/java/com/android/internal/app/HarmfulAppWarningActivity.java
new file mode 100644
index 0000000..042da36
--- /dev/null
+++ b/core/java/com/android/internal/app/HarmfulAppWarningActivity.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.internal.app;
+
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.IntentSender;
+import android.os.Bundle;
+import android.util.Log;
+import com.android.internal.R;
+
+/**
+ * This dialog is shown to the user before an activity in a harmful app is launched.
+ *
+ * See {@code PackageManager.setHarmfulAppInfo} for more info.
+ */
+public class HarmfulAppWarningActivity extends AlertActivity implements
+ DialogInterface.OnClickListener {
+ private static final String TAG = "HarmfulAppWarningActivity";
+
+ private static final String EXTRA_HARMFUL_APP_WARNING = "harmful_app_warning";
+
+ private String mPackageName;
+ private String mHarmfulAppWarning;
+ private IntentSender mTarget;
+
+ // [b/63909431] STOPSHIP replace placeholder UI with final Harmful App Warning UI
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ Intent intent = getIntent();
+ mPackageName = intent.getStringExtra(Intent.EXTRA_PACKAGE_NAME);
+ mTarget = intent.getParcelableExtra(Intent.EXTRA_INTENT);
+ mHarmfulAppWarning = intent.getStringExtra(EXTRA_HARMFUL_APP_WARNING);
+
+ if (mPackageName == null || mTarget == null || mHarmfulAppWarning == null) {
+ Log.wtf(TAG, "Invalid intent: " + intent.toString());
+ finish();
+ }
+
+ AlertController.AlertParams p = mAlertParams;
+ p.mTitle = getString(R.string.harmful_app_warning_title);
+ p.mMessage = mHarmfulAppWarning;
+ p.mPositiveButtonText = getString(R.string.harmful_app_warning_launch_anyway);
+ p.mPositiveButtonListener = this;
+ p.mNegativeButtonText = getString(R.string.harmful_app_warning_uninstall);
+ p.mNegativeButtonListener = this;
+
+ mAlert.installContent(mAlertParams);
+ }
+
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ switch (which) {
+ case DialogInterface.BUTTON_POSITIVE:
+ getPackageManager().setHarmfulAppWarning(mPackageName, null);
+
+ IntentSender target = getIntent().getParcelableExtra(Intent.EXTRA_INTENT);
+ try {
+ startIntentSenderForResult(target, -1, null, 0, 0, 0);
+ } catch (IntentSender.SendIntentException e) {
+ // ignore..
+ }
+ finish();
+ break;
+ case DialogInterface.BUTTON_NEGATIVE:
+ getPackageManager().deletePackage(mPackageName, null, 0);
+ finish();
+ break;
+ }
+ }
+
+ public static Intent createHarmfulAppWarningIntent(Context context, String targetPackageName,
+ IntentSender target, CharSequence harmfulAppWarning) {
+ Intent intent = new Intent();
+ intent.setClass(context, HarmfulAppWarningActivity.class);
+ intent.putExtra(Intent.EXTRA_PACKAGE_NAME, targetPackageName);
+ intent.putExtra(Intent.EXTRA_INTENT, target);
+ intent.putExtra(EXTRA_HARMFUL_APP_WARNING, harmfulAppWarning);
+ return intent;
+ }
+}
diff --git a/core/java/com/android/internal/app/IBatteryStats.aidl b/core/java/com/android/internal/app/IBatteryStats.aidl
index 5d4bccc..388180d 100644
--- a/core/java/com/android/internal/app/IBatteryStats.aidl
+++ b/core/java/com/android/internal/app/IBatteryStats.aidl
@@ -123,8 +123,6 @@
void noteWifiScanStoppedFromSource(in WorkSource ws);
void noteWifiBatchedScanStartedFromSource(in WorkSource ws, int csph);
void noteWifiBatchedScanStoppedFromSource(in WorkSource ws);
- void noteWifiMulticastEnabledFromSource(in WorkSource ws);
- void noteWifiMulticastDisabledFromSource(in WorkSource ws);
void noteWifiRadioPowerState(int powerState, long timestampNs, int uid);
void noteNetworkInterfaceType(String iface, int type);
void noteNetworkStatsEnabled();
diff --git a/core/java/com/android/internal/app/UnlaunchableAppActivity.java b/core/java/com/android/internal/app/UnlaunchableAppActivity.java
index 2eadaf3..902c8c1 100644
--- a/core/java/com/android/internal/app/UnlaunchableAppActivity.java
+++ b/core/java/com/android/internal/app/UnlaunchableAppActivity.java
@@ -111,7 +111,7 @@
@Override
public void onClick(DialogInterface dialog, int which) {
if (mReason == UNLAUNCHABLE_REASON_QUIET_MODE && which == DialogInterface.BUTTON_POSITIVE) {
- UserManager.get(this).trySetQuietModeEnabled(false, UserHandle.of(mUserId), mTarget);
+ UserManager.get(this).requestQuietModeEnabled(false, UserHandle.of(mUserId), mTarget);
}
}
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index b8ff9e4..439e5df 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -4062,7 +4062,8 @@
public void noteWakupAlarmLocked(String packageName, int uid, WorkSource workSource,
String tag) {
-
+ final int[] uids = new int[1];
+ final String[] tags = new String[1];
if (workSource != null) {
for (int i = 0; i < workSource.size(); ++i) {
uid = workSource.get(i);
@@ -4073,8 +4074,9 @@
workSourceName != null ? workSourceName : packageName);
pkg.noteWakeupAlarmLocked(tag);
}
-
- StatsLog.write(StatsLog.WAKEUP_ALARM_OCCURRED, uid, tag);
+ uids[0] = workSource.get(i);
+ tags[0] = workSource.getName(i);
+ StatsLog.write(StatsLog.WAKEUP_ALARM_OCCURRED, uids, tags, tag);
}
ArrayList<WorkChain> workChains = workSource.getWorkChains();
@@ -4087,9 +4089,7 @@
BatteryStatsImpl.Uid.Pkg pkg = getPackageStatsLocked(uid, packageName);
pkg.noteWakeupAlarmLocked(tag);
}
-
- // TODO(statsd): Log the full attribution chain here once it's available
- StatsLog.write(StatsLog.WAKEUP_ALARM_OCCURRED, uid, tag);
+ StatsLog.write(StatsLog.WAKEUP_ALARM_OCCURRED, wc.getUids(), wc.getTags(), tag);
}
}
} else {
@@ -4097,7 +4097,9 @@
BatteryStatsImpl.Uid.Pkg pkg = getPackageStatsLocked(uid, packageName);
pkg.noteWakeupAlarmLocked(tag);
}
- StatsLog.write(StatsLog.WAKEUP_ALARM_OCCURRED, uid, tag);
+ uids[0] = uid;
+ tags[0] = null;
+ StatsLog.write(StatsLog.WAKEUP_ALARM_OCCURRED, uids, tags, tag);
}
}
@@ -4221,6 +4223,10 @@
if (wc != null) {
StatsLog.write(
StatsLog.WAKELOCK_STATE_CHANGED, wc.getUids(), wc.getTags(), type, name, 1);
+ } else {
+ final int[] uids = new int[] { uid };
+ final String[] tags = new String[] { null };
+ StatsLog.write(StatsLog.WAKELOCK_STATE_CHANGED, uids, tags, type, name, 1);
}
}
}
@@ -4261,6 +4267,10 @@
if (wc != null) {
StatsLog.write(
StatsLog.WAKELOCK_STATE_CHANGED, wc.getUids(), wc.getTags(), type, name, 0);
+ } else {
+ final int[] uids = new int[] { uid };
+ final String[] tags = new String[] { null };
+ StatsLog.write(StatsLog.WAKELOCK_STATE_CHANGED, uids, tags, type, name, 0);
}
}
}
@@ -4354,18 +4364,27 @@
}
public void noteLongPartialWakelockStart(String name, String historyName, int uid) {
+ final int[] uids = new int[] { uid };
+ final String[] tags = new String[] { null };
+ StatsLog.write(StatsLog.LONG_PARTIAL_WAKELOCK_STATE_CHANGED,
+ uids, tags, name, historyName, 1);
+
uid = mapUid(uid);
noteLongPartialWakeLockStartInternal(name, historyName, uid);
- StatsLog.write(StatsLog.LONG_PARTIAL_WAKELOCK_STATE_CHANGED, uid, name, historyName, 1);
}
public void noteLongPartialWakelockStartFromSource(String name, String historyName,
WorkSource workSource) {
final int N = workSource.size();
+ final int[] uids = new int[1];
+ final String[] tags = new String[1];
for (int i = 0; i < N; ++i) {
final int uid = mapUid(workSource.get(i));
noteLongPartialWakeLockStartInternal(name, historyName, uid);
- StatsLog.write(StatsLog.LONG_PARTIAL_WAKELOCK_STATE_CHANGED, uid, name, historyName, 1);
+ uids[0] = workSource.get(i);
+ tags[0] = workSource.getName(i);
+ StatsLog.write(StatsLog.LONG_PARTIAL_WAKELOCK_STATE_CHANGED, uids, tags, name,
+ historyName, 1);
}
final ArrayList<WorkChain> workChains = workSource.getWorkChains();
@@ -4375,9 +4394,8 @@
final int uid = workChain.getAttributionUid();
noteLongPartialWakeLockStartInternal(name, historyName, uid);
- // TODO(statsd): the Log WorkChain to statds, and not just the uid.
- StatsLog.write(StatsLog.LONG_PARTIAL_WAKELOCK_STATE_CHANGED, uid, name, historyName,
- 1);
+ StatsLog.write(StatsLog.LONG_PARTIAL_WAKELOCK_STATE_CHANGED,
+ workChain.getUids(), workChain.getTags(), name, historyName, 1);
}
}
}
@@ -4397,18 +4415,27 @@
}
public void noteLongPartialWakelockFinish(String name, String historyName, int uid) {
+ int[] uids = new int[] { uid };
+ String[] tags = new String[] { null };
+ StatsLog.write(StatsLog.LONG_PARTIAL_WAKELOCK_STATE_CHANGED,
+ uids, tags, name, historyName, 0);
+
uid = mapUid(uid);
noteLongPartialWakeLockFinishInternal(name, historyName, uid);
- StatsLog.write(StatsLog.LONG_PARTIAL_WAKELOCK_STATE_CHANGED, uid, name, historyName, 0);
}
public void noteLongPartialWakelockFinishFromSource(String name, String historyName,
WorkSource workSource) {
final int N = workSource.size();
+ final int[] uids = new int[1];
+ final String[] tags = new String[1];
for (int i = 0; i < N; ++i) {
final int uid = mapUid(workSource.get(i));
noteLongPartialWakeLockFinishInternal(name, historyName, uid);
- StatsLog.write(StatsLog.LONG_PARTIAL_WAKELOCK_STATE_CHANGED, uid, name, historyName, 0);
+ uids[0] = workSource.get(i);
+ tags[0] = workSource.getName(i);
+ StatsLog.write(StatsLog.LONG_PARTIAL_WAKELOCK_STATE_CHANGED,
+ uids, tags, name, historyName, 0);
}
final ArrayList<WorkChain> workChains = workSource.getWorkChains();
@@ -4416,12 +4443,9 @@
for (int i = 0; i < workChains.size(); ++i) {
final WorkChain workChain = workChains.get(i);
final int uid = workChain.getAttributionUid();
-
noteLongPartialWakeLockFinishInternal(name, historyName, uid);
-
- // TODO(statsd): the Log WorkChain to statds, and not just the uid.
- StatsLog.write(StatsLog.LONG_PARTIAL_WAKELOCK_STATE_CHANGED, uid, name, historyName,
- 0);
+ StatsLog.write(StatsLog.LONG_PARTIAL_WAKELOCK_STATE_CHANGED,
+ workChain.getUids(), workChain.getTags(), name, historyName, 0);
}
}
}
@@ -5388,10 +5412,20 @@
}
mBluetoothScanNesting++;
- // TODO(statsd): Log WorkChain here if it's non-null.
- StatsLog.write(StatsLog.BLE_SCAN_STATE_CHANGED, uid, 1);
- if (isUnoptimized) {
- StatsLog.write(StatsLog.BLE_UNOPTIMIZED_SCAN_STATE_CHANGED, uid, 1);
+ if (workChain != null) {
+ StatsLog.write(StatsLog.BLE_SCAN_STATE_CHANGED,
+ workChain.getUids(), workChain.getTags(), 1);
+ if (isUnoptimized) {
+ StatsLog.write(StatsLog.BLE_UNOPTIMIZED_SCAN_STATE_CHANGED,
+ workChain.getUids(), workChain.getTags(), 1);
+ }
+ } else {
+ final int[] uids = new int[] {uid};
+ final String[] tags = new String[] {null};
+ StatsLog.write(StatsLog.BLE_SCAN_STATE_CHANGED, uids, tags, 1);
+ if (isUnoptimized) {
+ StatsLog.write(StatsLog.BLE_UNOPTIMIZED_SCAN_STATE_CHANGED, uids, tags, 1);
+ }
}
getUidStatsLocked(uid).noteBluetoothScanStartedLocked(elapsedRealtime, isUnoptimized);
@@ -5428,10 +5462,20 @@
mBluetoothScanTimer.stopRunningLocked(elapsedRealtime);
}
- // TODO(statsd): Log WorkChain here if it's non-null.
- StatsLog.write(StatsLog.BLE_SCAN_STATE_CHANGED, uid, 0);
- if (isUnoptimized) {
- StatsLog.write(StatsLog.BLE_UNOPTIMIZED_SCAN_STATE_CHANGED, uid, 0);
+ if (workChain != null) {
+ StatsLog.write(
+ StatsLog.BLE_SCAN_STATE_CHANGED, workChain.getUids(), workChain.getTags(), 0);
+ if (isUnoptimized) {
+ StatsLog.write(StatsLog.BLE_UNOPTIMIZED_SCAN_STATE_CHANGED,
+ workChain.getUids(), workChain.getTags(), 0);
+ }
+ } else {
+ final int[] uids = new int[] { uid };
+ final String[] tags = new String[] {null};
+ StatsLog.write(StatsLog.BLE_SCAN_STATE_CHANGED, uids, tags, 0);
+ if (isUnoptimized) {
+ StatsLog.write(StatsLog.BLE_UNOPTIMIZED_SCAN_STATE_CHANGED, uids, tags, 0);
+ }
}
getUidStatsLocked(uid).noteBluetoothScanStoppedLocked(elapsedRealtime, isUnoptimized);
@@ -5478,27 +5522,23 @@
BatteryStatsImpl.Uid uid = mUidStats.valueAt(i);
uid.noteResetBluetoothScanLocked(elapsedRealtime);
- StatsLog.write(StatsLog.BLE_SCAN_STATE_CHANGED, uid.getUid(), 0);
-
List<WorkChain> allWorkChains = uid.getAllBluetoothWorkChains();
if (allWorkChains != null) {
for (int j = 0; j < allWorkChains.size(); ++j) {
- // TODO(statsd) : Log the entire workchain here.
StatsLog.write(StatsLog.BLE_SCAN_STATE_CHANGED,
- allWorkChains.get(j).getAttributionUid(), 0);
+ allWorkChains.get(j).getUids(),
+ allWorkChains.get(j).getTags(), 0);
}
-
allWorkChains.clear();
}
List<WorkChain> unoptimizedWorkChains = uid.getUnoptimizedBluetoothWorkChains();
if (unoptimizedWorkChains != null) {
for (int j = 0; j < unoptimizedWorkChains.size(); ++j) {
- // TODO(statsd) : Log the entire workchain here.
StatsLog.write(StatsLog.BLE_UNOPTIMIZED_SCAN_STATE_CHANGED,
- unoptimizedWorkChains.get(j).getAttributionUid(), 0);
+ unoptimizedWorkChains.get(j).getUids(),
+ unoptimizedWorkChains.get(j).getTags(), 0);
}
-
unoptimizedWorkChains.clear();
}
}
@@ -5507,10 +5547,14 @@
public void noteBluetoothScanResultsFromSourceLocked(WorkSource ws, int numNewResults) {
final int N = ws.size();
+ final int[] uids = new int[1];
+ final String[] tags = new String[1];
for (int i = 0; i < N; i++) {
int uid = mapUid(ws.get(i));
getUidStatsLocked(uid).noteBluetoothScanResultsLocked(numNewResults);
- StatsLog.write(StatsLog.BLE_SCAN_RESULT_RECEIVED, uid, numNewResults);
+ uids[0] = ws.get(i);
+ tags[0] = ws.getName(i);
+ StatsLog.write(StatsLog.BLE_SCAN_RESULT_RECEIVED, uids, tags, numNewResults);
}
final List<WorkChain> workChains = ws.getWorkChains();
@@ -5519,8 +5563,8 @@
final WorkChain wc = workChains.get(i);
int uid = mapUid(wc.getAttributionUid());
getUidStatsLocked(uid).noteBluetoothScanResultsLocked(numNewResults);
- // TODO(statsd): Log the entire WorkChain here.
- StatsLog.write(StatsLog.BLE_SCAN_RESULT_RECEIVED, uid, numNewResults);
+ StatsLog.write(StatsLog.BLE_SCAN_RESULT_RECEIVED,
+ wc.getUids(), wc.getTags(), numNewResults);
}
}
}
@@ -5835,10 +5879,14 @@
public void noteFullWifiLockAcquiredFromSourceLocked(WorkSource ws) {
int N = ws.size();
+ final int[] uids = new int[1];
+ final String[] tags = new String[1];
for (int i=0; i<N; i++) {
final int uid = mapUid(ws.get(i));
noteFullWifiLockAcquiredLocked(uid);
- StatsLog.write(StatsLog.WIFI_LOCK_STATE_CHANGED, uid, 1);
+ uids[0] = ws.get(i);
+ tags[0] = ws.getName(i);
+ StatsLog.write(StatsLog.WIFI_LOCK_STATE_CHANGED, uids, tags, 1);
}
final List<WorkChain> workChains = ws.getWorkChains();
@@ -5847,22 +5895,22 @@
final WorkChain workChain = workChains.get(i);
final int uid = mapUid(workChain.getAttributionUid());
noteFullWifiLockAcquiredLocked(uid);
-
- // TODO(statsd): Log workChain instead of uid here.
- if (DEBUG) {
- Slog.v(TAG, "noteFullWifiLockAcquiredFromSourceLocked: " + workChain);
- }
- StatsLog.write(StatsLog.WIFI_LOCK_STATE_CHANGED, uid, 1);
+ StatsLog.write(StatsLog.WIFI_LOCK_STATE_CHANGED,
+ workChain.getUids(), workChain.getTags(), 1);
}
}
}
public void noteFullWifiLockReleasedFromSourceLocked(WorkSource ws) {
int N = ws.size();
+ final int[] uids = new int[1];
+ final String[] tags = new String[1];
for (int i=0; i<N; i++) {
final int uid = mapUid(ws.get(i));
noteFullWifiLockReleasedLocked(uid);
- StatsLog.write(StatsLog.WIFI_LOCK_STATE_CHANGED, uid, 0);
+ uids[0] = ws.get(i);
+ tags[0] = ws.getName(i);
+ StatsLog.write(StatsLog.WIFI_LOCK_STATE_CHANGED, uids, tags, 0);
}
final List<WorkChain> workChains = ws.getWorkChains();
@@ -5871,22 +5919,22 @@
final WorkChain workChain = workChains.get(i);
final int uid = mapUid(workChain.getAttributionUid());
noteFullWifiLockReleasedLocked(uid);
-
- // TODO(statsd): Log workChain instead of uid here.
- if (DEBUG) {
- Slog.v(TAG, "noteFullWifiLockReleasedFromSourceLocked: " + workChain);
- }
- StatsLog.write(StatsLog.WIFI_LOCK_STATE_CHANGED, uid, 0);
+ StatsLog.write(StatsLog.WIFI_LOCK_STATE_CHANGED,
+ workChain.getUids(), workChain.getTags(), 0);
}
}
}
public void noteWifiScanStartedFromSourceLocked(WorkSource ws) {
int N = ws.size();
+ final int[] uids = new int[1];
+ final String[] tags = new String[1];
for (int i=0; i<N; i++) {
final int uid = mapUid(ws.get(i));
noteWifiScanStartedLocked(uid);
- StatsLog.write(StatsLog.WIFI_SCAN_STATE_CHANGED, uid, 1);
+ uids[0] = ws.get(i);
+ tags[0] = ws.getName(i);
+ StatsLog.write(StatsLog.WIFI_SCAN_STATE_CHANGED, uids, tags, 1);
}
final List<WorkChain> workChains = ws.getWorkChains();
@@ -5895,22 +5943,22 @@
final WorkChain workChain = workChains.get(i);
final int uid = mapUid(workChain.getAttributionUid());
noteWifiScanStartedLocked(uid);
-
- // TODO(statsd): Log workChain instead of uid here.
- if (DEBUG) {
- Slog.v(TAG, "noteWifiScanStartedFromSourceLocked: " + workChain);
- }
- StatsLog.write(StatsLog.WIFI_SCAN_STATE_CHANGED, uid, 1);
+ StatsLog.write(StatsLog.WIFI_SCAN_STATE_CHANGED, workChain.getUids(),
+ workChain.getTags(), 1);
}
}
}
public void noteWifiScanStoppedFromSourceLocked(WorkSource ws) {
int N = ws.size();
+ final int[] uids = new int[1];
+ final String[] tags = new String[1];
for (int i=0; i<N; i++) {
final int uid = mapUid(ws.get(i));
noteWifiScanStoppedLocked(uid);
- StatsLog.write(StatsLog.WIFI_SCAN_STATE_CHANGED, uid, 0);
+ uids[0] = ws.get(i);
+ tags[0] = ws.getName(i);
+ StatsLog.write(StatsLog.WIFI_SCAN_STATE_CHANGED, uids, tags, 0);
}
final List<WorkChain> workChains = ws.getWorkChains();
@@ -5919,11 +5967,8 @@
final WorkChain workChain = workChains.get(i);
final int uid = mapUid(workChain.getAttributionUid());
noteWifiScanStoppedLocked(uid);
-
- if (DEBUG) {
- Slog.v(TAG, "noteWifiScanStoppedFromSourceLocked: " + workChain);
- }
- StatsLog.write(StatsLog.WIFI_SCAN_STATE_CHANGED, uid, 0);
+ StatsLog.write(StatsLog.WIFI_SCAN_STATE_CHANGED,
+ workChain.getUids(), workChain.getTags(), 0);
}
}
}
@@ -5956,34 +6001,6 @@
}
}
- public void noteWifiMulticastEnabledFromSourceLocked(WorkSource ws) {
- int N = ws.size();
- for (int i=0; i<N; i++) {
- noteWifiMulticastEnabledLocked(ws.get(i));
- }
-
- final List<WorkChain> workChains = ws.getWorkChains();
- if (workChains != null) {
- for (int i = 0; i < workChains.size(); ++i) {
- noteWifiMulticastEnabledLocked(workChains.get(i).getAttributionUid());
- }
- }
- }
-
- public void noteWifiMulticastDisabledFromSourceLocked(WorkSource ws) {
- int N = ws.size();
- for (int i=0; i<N; i++) {
- noteWifiMulticastDisabledLocked(ws.get(i));
- }
-
- final List<WorkChain> workChains = ws.getWorkChains();
- if (workChains != null) {
- for (int i = 0; i < workChains.size(); ++i) {
- noteWifiMulticastDisabledLocked(workChains.get(i).getAttributionUid());
- }
- }
- }
-
private static String[] includeInStringArray(String[] array, String str) {
if (ArrayUtils.indexOf(array, str) >= 0) {
return array;
@@ -6917,16 +6934,18 @@
public void noteAudioTurnedOnLocked(long elapsedRealtimeMs) {
createAudioTurnedOnTimerLocked().startRunningLocked(elapsedRealtimeMs);
- // TODO(statsd): Possibly use a worksource instead of a uid.
- StatsLog.write(StatsLog.AUDIO_STATE_CHANGED, getUid(), 1);
+ final int[] uids = new int[] { getUid() };
+ final String[] tags = new String[] { null };
+ StatsLog.write(StatsLog.AUDIO_STATE_CHANGED, uids, tags, 1);
}
public void noteAudioTurnedOffLocked(long elapsedRealtimeMs) {
if (mAudioTurnedOnTimer != null) {
mAudioTurnedOnTimer.stopRunningLocked(elapsedRealtimeMs);
if (!mAudioTurnedOnTimer.isRunningLocked()) { // only tell statsd if truly stopped
- // TODO(statsd): Possibly use a worksource instead of a uid.
- StatsLog.write(StatsLog.AUDIO_STATE_CHANGED, getUid(), 0);
+ final int[] uids = new int[] { getUid() };
+ final String[] tags = new String[] { null };
+ StatsLog.write(StatsLog.AUDIO_STATE_CHANGED, uids, tags, 0);
}
}
}
@@ -6934,8 +6953,9 @@
public void noteResetAudioLocked(long elapsedRealtimeMs) {
if (mAudioTurnedOnTimer != null) {
mAudioTurnedOnTimer.stopAllRunningLocked(elapsedRealtimeMs);
- // TODO(statsd): Possibly use a worksource instead of a uid.
- StatsLog.write(StatsLog.AUDIO_STATE_CHANGED, getUid(), 0);
+ final int[] uids = new int[] { getUid() };
+ final String[] tags = new String[] { null };
+ StatsLog.write(StatsLog.AUDIO_STATE_CHANGED, uids, tags, 0);
}
}
@@ -6949,16 +6969,18 @@
public void noteVideoTurnedOnLocked(long elapsedRealtimeMs) {
createVideoTurnedOnTimerLocked().startRunningLocked(elapsedRealtimeMs);
- // TODO(statsd): Possibly use a worksource instead of a uid.
- StatsLog.write(StatsLog.MEDIA_CODEC_ACTIVITY_CHANGED, getUid(), 1);
+ final int[] uids = new int[] { getUid() };
+ final String[] tags = new String[] { null };
+ StatsLog.write(StatsLog.MEDIA_CODEC_ACTIVITY_CHANGED, uids, tags, 1);
}
public void noteVideoTurnedOffLocked(long elapsedRealtimeMs) {
if (mVideoTurnedOnTimer != null) {
mVideoTurnedOnTimer.stopRunningLocked(elapsedRealtimeMs);
if (!mVideoTurnedOnTimer.isRunningLocked()) { // only tell statsd if truly stopped
- // TODO(statsd): Possibly use a worksource instead of a uid.
- StatsLog.write(StatsLog.MEDIA_CODEC_ACTIVITY_CHANGED, getUid(), 0);
+ final int[] uids = new int[] { getUid() };
+ final String[] tags = new String[] { null };
+ StatsLog.write(StatsLog.MEDIA_CODEC_ACTIVITY_CHANGED, uids, tags, 0);
}
}
}
@@ -6966,8 +6988,9 @@
public void noteResetVideoLocked(long elapsedRealtimeMs) {
if (mVideoTurnedOnTimer != null) {
mVideoTurnedOnTimer.stopAllRunningLocked(elapsedRealtimeMs);
- // TODO(statsd): Possibly use a worksource instead of a uid.
- StatsLog.write(StatsLog.MEDIA_CODEC_ACTIVITY_CHANGED, getUid(), 0);
+ final int[] uids = new int[] { getUid() };
+ final String[] tags = new String[] { null };
+ StatsLog.write(StatsLog.MEDIA_CODEC_ACTIVITY_CHANGED, uids, tags, 0);
}
}
@@ -6981,16 +7004,18 @@
public void noteFlashlightTurnedOnLocked(long elapsedRealtimeMs) {
createFlashlightTurnedOnTimerLocked().startRunningLocked(elapsedRealtimeMs);
- // TODO(statsd): Possibly use a worksource instead of a uid.
- StatsLog.write(StatsLog.FLASHLIGHT_STATE_CHANGED, getUid(), 1);
+ final int[] uids = new int[] { getUid() };
+ final String[] tags = new String[] { null };
+ StatsLog.write(StatsLog.FLASHLIGHT_STATE_CHANGED, uids, tags, 1);
}
public void noteFlashlightTurnedOffLocked(long elapsedRealtimeMs) {
if (mFlashlightTurnedOnTimer != null) {
mFlashlightTurnedOnTimer.stopRunningLocked(elapsedRealtimeMs);
if (!mFlashlightTurnedOnTimer.isRunningLocked()) {
- // TODO(statsd): Possibly use a worksource instead of a uid.
- StatsLog.write(StatsLog.FLASHLIGHT_STATE_CHANGED, getUid(), 0);
+ final int[] uids = new int[] { getUid() };
+ final String[] tags = new String[] { null };
+ StatsLog.write(StatsLog.FLASHLIGHT_STATE_CHANGED, uids, tags, 0);
}
}
}
@@ -6998,8 +7023,9 @@
public void noteResetFlashlightLocked(long elapsedRealtimeMs) {
if (mFlashlightTurnedOnTimer != null) {
mFlashlightTurnedOnTimer.stopAllRunningLocked(elapsedRealtimeMs);
- // TODO(statsd): Possibly use a worksource instead of a uid.
- StatsLog.write(StatsLog.FLASHLIGHT_STATE_CHANGED, getUid(), 0);
+ final int[] uids = new int[] { getUid() };
+ final String[] tags = new String[] { null };
+ StatsLog.write(StatsLog.FLASHLIGHT_STATE_CHANGED, uids, tags, 0);
}
}
@@ -7013,16 +7039,18 @@
public void noteCameraTurnedOnLocked(long elapsedRealtimeMs) {
createCameraTurnedOnTimerLocked().startRunningLocked(elapsedRealtimeMs);
- // TODO(statsd): Possibly use a worksource instead of a uid.
- StatsLog.write(StatsLog.CAMERA_STATE_CHANGED, getUid(), 1);
+ final int[] uids = new int[] { getUid() };
+ final String[] tags = new String[] { null };
+ StatsLog.write(StatsLog.CAMERA_STATE_CHANGED, uids, tags, 1);
}
public void noteCameraTurnedOffLocked(long elapsedRealtimeMs) {
if (mCameraTurnedOnTimer != null) {
mCameraTurnedOnTimer.stopRunningLocked(elapsedRealtimeMs);
if (!mCameraTurnedOnTimer.isRunningLocked()) { // only tell statsd if truly stopped
- // TODO(statsd): Possibly use a worksource instead of a uid.
- StatsLog.write(StatsLog.CAMERA_STATE_CHANGED, getUid(), 0);
+ final int[] uids = new int[] { getUid() };
+ final String[] tags = new String[] { null };
+ StatsLog.write(StatsLog.CAMERA_STATE_CHANGED, uids, tags, 0);
}
}
}
@@ -7030,8 +7058,9 @@
public void noteResetCameraLocked(long elapsedRealtimeMs) {
if (mCameraTurnedOnTimer != null) {
mCameraTurnedOnTimer.stopAllRunningLocked(elapsedRealtimeMs);
- // TODO(statsd): Possibly use a worksource instead of a uid.
- StatsLog.write(StatsLog.CAMERA_STATE_CHANGED, getUid(), 0);
+ final int[] uids = new int[] { getUid() };
+ final String[] tags = new String[] { null };
+ StatsLog.write(StatsLog.CAMERA_STATE_CHANGED, uids, tags, 0);
}
}
@@ -9593,8 +9622,9 @@
DualTimer t = mSyncStats.startObject(name);
if (t != null) {
t.startRunningLocked(elapsedRealtimeMs);
- // TODO(statsd): Possibly use a worksource instead of a uid.
- StatsLog.write(StatsLog.SYNC_STATE_CHANGED, getUid(), name, 1);
+ final int[] uids = new int[] { getUid() };
+ final String[] tags = new String[] { null };
+ StatsLog.write(StatsLog.SYNC_STATE_CHANGED, uids, tags, name, 1);
}
}
@@ -9603,8 +9633,9 @@
if (t != null) {
t.stopRunningLocked(elapsedRealtimeMs);
if (!t.isRunningLocked()) { // only tell statsd if truly stopped
- // TODO(statsd): Possibly use a worksource instead of a uid.
- StatsLog.write(StatsLog.SYNC_STATE_CHANGED, getUid(), name, 0);
+ final int[] uids = new int[] { getUid() };
+ final String[] tags = new String[] { null };
+ StatsLog.write(StatsLog.SYNC_STATE_CHANGED, uids, tags, name, 0);
}
}
}
@@ -9613,8 +9644,9 @@
DualTimer t = mJobStats.startObject(name);
if (t != null) {
t.startRunningLocked(elapsedRealtimeMs);
- // TODO(statsd): Possibly use a worksource instead of a uid.
- StatsLog.write(StatsLog.SCHEDULED_JOB_STATE_CHANGED, getUid(), name, 1);
+ final int[] uids = new int[] { getUid() };
+ final String[] tags = new String[] { null };
+ StatsLog.write(StatsLog.SCHEDULED_JOB_STATE_CHANGED, uids, tags, name, 1);
}
}
@@ -9623,8 +9655,9 @@
if (t != null) {
t.stopRunningLocked(elapsedRealtimeMs);
if (!t.isRunningLocked()) { // only tell statsd if truly stopped
- // TODO(statsd): Possibly use a worksource instead of a uid.
- StatsLog.write(StatsLog.SCHEDULED_JOB_STATE_CHANGED, getUid(), name, 0);
+ final int[] uids = new int[] { getUid() };
+ final String[] tags = new String[] { null };
+ StatsLog.write(StatsLog.SCHEDULED_JOB_STATE_CHANGED, uids, tags, name, 0);
}
}
if (mBsi.mOnBatteryTimeBase.isRunning()) {
@@ -9735,11 +9768,12 @@
public void noteStartSensor(int sensor, long elapsedRealtimeMs) {
DualTimer t = getSensorTimerLocked(sensor, /* create= */ true);
t.startRunningLocked(elapsedRealtimeMs);
- // TODO(statsd): Possibly use a worksource instead of a uid.
+ final int[] uids = new int[] { getUid() };
+ final String[] tags = new String[] { null };
if (sensor == Sensor.GPS) {
- StatsLog.write(StatsLog.GPS_SCAN_STATE_CHANGED, getUid(), 1);
+ StatsLog.write(StatsLog.GPS_SCAN_STATE_CHANGED, uids, tags, 1);
} else {
- StatsLog.write(StatsLog.SENSOR_STATE_CHANGED, getUid(), sensor, 1);
+ StatsLog.write(StatsLog.SENSOR_STATE_CHANGED, uids, tags, sensor, 1);
}
}
@@ -9750,10 +9784,12 @@
t.stopRunningLocked(elapsedRealtimeMs);
if (!t.isRunningLocked()) { // only tell statsd if truly stopped
// TODO(statsd): Possibly use a worksource instead of a uid.
+ final int[] uids = new int[] { getUid() };
+ final String[] tags = new String[] { null };
if (sensor == Sensor.GPS) {
- StatsLog.write(StatsLog.GPS_SCAN_STATE_CHANGED, getUid(), 0);
+ StatsLog.write(StatsLog.GPS_SCAN_STATE_CHANGED, uids, tags, 0);
} else {
- StatsLog.write(StatsLog.SENSOR_STATE_CHANGED, getUid(), sensor, 0);
+ StatsLog.write(StatsLog.SENSOR_STATE_CHANGED, uids, tags, sensor, 0);
}
}
}
diff --git a/core/java/com/android/internal/os/KernelCpuSpeedReader.java b/core/java/com/android/internal/os/KernelCpuSpeedReader.java
index 4c0370c..98fea01 100644
--- a/core/java/com/android/internal/os/KernelCpuSpeedReader.java
+++ b/core/java/com/android/internal/os/KernelCpuSpeedReader.java
@@ -38,6 +38,7 @@
private static final String TAG = "KernelCpuSpeedReader";
private final String mProcFile;
+ private final int mNumSpeedSteps;
private final long[] mLastSpeedTimesMs;
private final long[] mDeltaSpeedTimesMs;
@@ -50,6 +51,7 @@
public KernelCpuSpeedReader(int cpuNumber, int numSpeedSteps) {
mProcFile = String.format("/sys/devices/system/cpu/cpu%d/cpufreq/stats/time_in_state",
cpuNumber);
+ mNumSpeedSteps = numSpeedSteps;
mLastSpeedTimesMs = new long[numSpeedSteps];
mDeltaSpeedTimesMs = new long[numSpeedSteps];
long jiffyHz = Os.sysconf(OsConstants._SC_CLK_TCK);
@@ -90,4 +92,31 @@
}
return mDeltaSpeedTimesMs;
}
+
+ /**
+ * @return The time (in milliseconds) spent at different cpu speeds. The values should be
+ * monotonically increasing, unless the cpu was hotplugged.
+ */
+ public long[] readAbsolute() {
+ StrictMode.ThreadPolicy policy = StrictMode.allowThreadDiskReads();
+ long[] speedTimeMs = new long[mNumSpeedSteps];
+ try (BufferedReader reader = new BufferedReader(new FileReader(mProcFile))) {
+ TextUtils.SimpleStringSplitter splitter = new TextUtils.SimpleStringSplitter(' ');
+ String line;
+ int speedIndex = 0;
+ while (speedIndex < mNumSpeedSteps && (line = reader.readLine()) != null) {
+ splitter.setString(line);
+ splitter.next();
+ long time = Long.parseLong(splitter.next()) * mJiffyMillis;
+ speedTimeMs[speedIndex] = time;
+ speedIndex++;
+ }
+ } catch (IOException e) {
+ Slog.e(TAG, "Failed to read cpu-freq: " + e.getMessage());
+ Arrays.fill(speedTimeMs, 0);
+ } finally {
+ StrictMode.setThreadPolicy(policy);
+ }
+ return speedTimeMs;
+ }
}
diff --git a/core/java/com/android/internal/policy/IKeyguardService.aidl b/core/java/com/android/internal/policy/IKeyguardService.aidl
index 69184c3..e5d5685 100644
--- a/core/java/com/android/internal/policy/IKeyguardService.aidl
+++ b/core/java/com/android/internal/policy/IKeyguardService.aidl
@@ -35,7 +35,7 @@
void addStateMonitorCallback(IKeyguardStateCallback callback);
void verifyUnlock(IKeyguardExitCallback callback);
- void dismiss(IKeyguardDismissCallback callback);
+ void dismiss(IKeyguardDismissCallback callback, CharSequence message);
void onDreamingStarted();
void onDreamingStopped();
diff --git a/core/java/com/android/internal/util/ArrayUtils.java b/core/java/com/android/internal/util/ArrayUtils.java
index aa85668..7b023f4 100644
--- a/core/java/com/android/internal/util/ArrayUtils.java
+++ b/core/java/com/android/internal/util/ArrayUtils.java
@@ -184,6 +184,13 @@
}
/**
+ * Length of the given collection or 0 if it's null.
+ */
+ public static int size(@Nullable Collection<?> collection) {
+ return collection == null ? 0 : collection.size();
+ }
+
+ /**
* Checks that value is present as at least one of the elements of the array.
* @param array the array to check in
* @param value the value to check for
diff --git a/core/java/com/android/internal/util/CollectionUtils.java b/core/java/com/android/internal/util/CollectionUtils.java
index 7985e57..2f2c747 100644
--- a/core/java/com/android/internal/util/CollectionUtils.java
+++ b/core/java/com/android/internal/util/CollectionUtils.java
@@ -16,8 +16,6 @@
package com.android.internal.util;
-import static com.android.internal.util.ArrayUtils.isEmpty;
-
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.util.ArraySet;
@@ -173,13 +171,20 @@
}
/**
- * Returns the size of the given list, or 0 if the list is null
+ * Returns the size of the given collection, or 0 if null
*/
public static int size(@Nullable Collection<?> cur) {
return cur != null ? cur.size() : 0;
}
/**
+ * Returns whether the given collection {@link Collection#isEmpty is empty} or {@code null}
+ */
+ public static boolean isEmpty(@Nullable Collection<?> cur) {
+ return size(cur) == 0;
+ }
+
+ /**
* Returns the elements of the given list that are of type {@code c}
*/
public static @NonNull <T> List<T> filter(@Nullable List<?> list, Class<T> c) {
diff --git a/core/java/com/android/internal/util/ObjectUtils.java b/core/java/com/android/internal/util/ObjectUtils.java
index 59e5a64..379602a 100644
--- a/core/java/com/android/internal/util/ObjectUtils.java
+++ b/core/java/com/android/internal/util/ObjectUtils.java
@@ -29,6 +29,9 @@
return a != null ? a : Preconditions.checkNotNull(b);
}
+ /**
+ * Compares two {@link Nullable} objects with {@code null} values considered the smallest
+ */
public static <T extends Comparable> int compare(@Nullable T a, @Nullable T b) {
if (a != null) {
return (b != null) ? a.compareTo(b) : 1;
@@ -36,4 +39,13 @@
return (b != null) ? -1 : 0;
}
}
+
+ /**
+ * @return {@code null} if the given instance is not of the given calss, or the given
+ * instance otherwise
+ */
+ @Nullable
+ public static <S, T extends S> T castOrNull(@Nullable S instance, @NonNull Class<T> c) {
+ return c.isInstance(instance) ? (T) instance : null;
+ }
}
diff --git a/core/java/com/android/internal/util/ScreenshotHelper.java b/core/java/com/android/internal/util/ScreenshotHelper.java
new file mode 100644
index 0000000..f7ea787
--- /dev/null
+++ b/core/java/com/android/internal/util/ScreenshotHelper.java
@@ -0,0 +1,128 @@
+package com.android.internal.util;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Message;
+import android.os.Messenger;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.util.Log;
+
+public class ScreenshotHelper {
+ private static final String TAG = "ScreenshotHelper";
+
+ private static final String SYSUI_PACKAGE = "com.android.systemui";
+ private static final String SYSUI_SCREENSHOT_SERVICE =
+ "com.android.systemui.screenshot.TakeScreenshotService";
+ private static final String SYSUI_SCREENSHOT_ERROR_RECEIVER =
+ "com.android.systemui.screenshot.ScreenshotServiceErrorReceiver";
+
+ // Time until we give up on the screenshot & show an error instead.
+ private final int SCREENSHOT_TIMEOUT_MS = 10000;
+
+ private final Object mScreenshotLock = new Object();
+ private ServiceConnection mScreenshotConnection = null;
+ private final Context mContext;
+
+ public ScreenshotHelper(Context context) {
+ mContext = context;
+ }
+
+ public void takeScreenshot(final int screenshotType, final boolean hasStatus,
+ final boolean hasNav, Handler handler) {
+ synchronized (mScreenshotLock) {
+ if (mScreenshotConnection != null) {
+ return;
+ }
+ final ComponentName serviceComponent = new ComponentName(SYSUI_PACKAGE,
+ SYSUI_SCREENSHOT_SERVICE);
+ final Intent serviceIntent = new Intent();
+
+ final Runnable mScreenshotTimeout = new Runnable() {
+ @Override public void run() {
+ synchronized (mScreenshotLock) {
+ if (mScreenshotConnection != null) {
+ mContext.unbindService(mScreenshotConnection);
+ mScreenshotConnection = null;
+ notifyScreenshotError();
+ }
+ }
+ }
+ };
+
+ serviceIntent.setComponent(serviceComponent);
+ ServiceConnection conn = new ServiceConnection() {
+ @Override
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ synchronized (mScreenshotLock) {
+ if (mScreenshotConnection != this) {
+ return;
+ }
+ Messenger messenger = new Messenger(service);
+ Message msg = Message.obtain(null, screenshotType);
+ final ServiceConnection myConn = this;
+ Handler h = new Handler(handler.getLooper()) {
+ @Override
+ public void handleMessage(Message msg) {
+ synchronized (mScreenshotLock) {
+ if (mScreenshotConnection == myConn) {
+ mContext.unbindService(mScreenshotConnection);
+ mScreenshotConnection = null;
+ handler.removeCallbacks(mScreenshotTimeout);
+ }
+ }
+ }
+ };
+ msg.replyTo = new Messenger(h);
+ msg.arg1 = hasStatus ? 1: 0;
+ msg.arg2 = hasNav ? 1: 0;
+ try {
+ messenger.send(msg);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Couldn't take screenshot: " + e);
+ }
+ }
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName name) {
+ synchronized (mScreenshotLock) {
+ if (mScreenshotConnection != null) {
+ mContext.unbindService(mScreenshotConnection);
+ mScreenshotConnection = null;
+ handler.removeCallbacks(mScreenshotTimeout);
+ notifyScreenshotError();
+ }
+ }
+ }
+ };
+ if (mContext.bindServiceAsUser(serviceIntent, conn,
+ Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE,
+ UserHandle.CURRENT)) {
+ mScreenshotConnection = conn;
+ handler.postDelayed(mScreenshotTimeout, SCREENSHOT_TIMEOUT_MS);
+ }
+ }
+ }
+
+ /**
+ * Notifies the screenshot service to show an error.
+ */
+ private void notifyScreenshotError() {
+ // If the service process is killed, then ask it to clean up after itself
+ final ComponentName errorComponent = new ComponentName(SYSUI_PACKAGE,
+ SYSUI_SCREENSHOT_ERROR_RECEIVER);
+ // Broadcast needs to have a valid action. We'll just pick
+ // a generic one, since the receiver here doesn't care.
+ Intent errorIntent = new Intent(Intent.ACTION_USER_PRESENT);
+ errorIntent.setComponent(errorComponent);
+ errorIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT |
+ Intent.FLAG_RECEIVER_FOREGROUND);
+ mContext.sendBroadcastAsUser(errorIntent, UserHandle.CURRENT);
+ }
+
+}
diff --git a/core/java/com/android/internal/widget/ILockSettings.aidl b/core/java/com/android/internal/widget/ILockSettings.aidl
index 4e7df28..31d22e0 100644
--- a/core/java/com/android/internal/widget/ILockSettings.aidl
+++ b/core/java/com/android/internal/widget/ILockSettings.aidl
@@ -19,9 +19,9 @@
import android.app.PendingIntent;
import android.app.trust.IStrongAuthTracker;
import android.os.Bundle;
-import android.security.recoverablekeystore.KeyEntryRecoveryData;
-import android.security.recoverablekeystore.KeyStoreRecoveryData;
-import android.security.recoverablekeystore.KeyStoreRecoveryMetadata;
+import android.security.keystore.EntryRecoveryData;
+import android.security.keystore.RecoveryData;
+import android.security.keystore.RecoveryMetadata;
import com.android.internal.widget.ICheckCredentialProgressCallback;
import com.android.internal.widget.VerifyCredentialResponse;
@@ -60,25 +60,25 @@
in byte[] token, int requestedQuality, int userId);
void unlockUserWithToken(long tokenHandle, in byte[] token, int userId);
- // RecoverableKeyStoreLoader methods.
+ // Keystore RecoveryManager methods.
// {@code ServiceSpecificException} may be thrown to signal an error, which caller can
- // convert to {@code RecoverableKeyStoreLoader}.
+ // convert to {@code RecoveryManagerException}.
void initRecoveryService(in String rootCertificateAlias, in byte[] signedPublicKeyList);
- KeyStoreRecoveryData getRecoveryData(in byte[] account);
+ RecoveryData getRecoveryData(in byte[] account);
byte[] generateAndStoreKey(String alias);
void removeKey(String alias);
void setSnapshotCreatedPendingIntent(in PendingIntent intent);
Map getRecoverySnapshotVersions();
- void setServerParameters(long serverParameters);
+ void setServerParams(in byte[] serverParams);
void setRecoveryStatus(in String packageName, in String[] aliases, int status);
Map getRecoveryStatus(in String packageName);
void setRecoverySecretTypes(in int[] secretTypes);
int[] getRecoverySecretTypes();
int[] getPendingRecoverySecretTypes();
- void recoverySecretAvailable(in KeyStoreRecoveryMetadata recoverySecret);
+ void recoverySecretAvailable(in RecoveryMetadata recoverySecret);
byte[] startRecoverySession(in String sessionId,
in byte[] verifierPublicKey, in byte[] vaultParams, in byte[] vaultChallenge,
- in List<KeyStoreRecoveryMetadata> secrets);
+ in List<RecoveryMetadata> secrets);
Map/*<String, byte[]>*/ recoverKeys(in String sessionId, in byte[] recoveryKeyBlob,
- in List<KeyEntryRecoveryData> applicationKeys);
+ in List<EntryRecoveryData> applicationKeys);
}
diff --git a/core/java/com/android/internal/widget/ResolverDrawerLayout.java b/core/java/com/android/internal/widget/ResolverDrawerLayout.java
index 7635a72..6f2246a 100644
--- a/core/java/com/android/internal/widget/ResolverDrawerLayout.java
+++ b/core/java/com/android/internal/widget/ResolverDrawerLayout.java
@@ -22,9 +22,7 @@
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
-import android.graphics.Color;
import android.graphics.Rect;
-import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.os.Parcel;
@@ -504,7 +502,7 @@
}
private void onCollapsedChanged(boolean isCollapsed) {
- notifyViewAccessibilityStateChangedIfNeeded(
+ notifyAccessibilityStateChanged(
AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
if (mScrollIndicatorDrawable != null) {
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index b3f66e9..96f3308 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -84,7 +84,7 @@
"android_view_VelocityTracker.cpp",
"android_text_AndroidCharacter.cpp",
"android_text_Hyphenator.cpp",
- "android_text_MeasuredText.cpp",
+ "android_text_MeasuredParagraph.cpp",
"android_text_StaticLayout.cpp",
"android_os_Debug.cpp",
"android_os_GraphicsEnvironment.cpp",
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index 6d7fe05..6569b47 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -178,7 +178,7 @@
extern int register_android_net_NetworkUtils(JNIEnv* env);
extern int register_android_text_AndroidCharacter(JNIEnv *env);
extern int register_android_text_Hyphenator(JNIEnv *env);
-extern int register_android_text_MeasuredText(JNIEnv* env);
+extern int register_android_text_MeasuredParagraph(JNIEnv* env);
extern int register_android_text_StaticLayout(JNIEnv *env);
extern int register_android_opengl_classes(JNIEnv *env);
extern int register_android_ddm_DdmHandleNativeHeap(JNIEnv *env);
@@ -1342,7 +1342,7 @@
REG_JNI(register_android_content_XmlBlock),
REG_JNI(register_android_text_AndroidCharacter),
REG_JNI(register_android_text_Hyphenator),
- REG_JNI(register_android_text_MeasuredText),
+ REG_JNI(register_android_text_MeasuredParagraph),
REG_JNI(register_android_text_StaticLayout),
REG_JNI(register_android_view_InputDevice),
REG_JNI(register_android_view_KeyCharacterMap),
diff --git a/core/jni/android/graphics/ImageDecoder.cpp b/core/jni/android/graphics/ImageDecoder.cpp
index ec03f82..a0a4be4 100644
--- a/core/jni/android/graphics/ImageDecoder.cpp
+++ b/core/jni/android/graphics/ImageDecoder.cpp
@@ -67,8 +67,8 @@
kOpaque = -1,
};
- std::unique_ptr<SkAndroidCodec> mCodec;
NinePatchPeeker mPeeker;
+ std::unique_ptr<SkAndroidCodec> mCodec;
};
static jobject native_create(JNIEnv* env, std::unique_ptr<SkStream> stream) {
diff --git a/core/jni/android_os_VintfObject.cpp b/core/jni/android_os_VintfObject.cpp
index 1eeea51..1659168 100644
--- a/core/jni/android_os_VintfObject.cpp
+++ b/core/jni/android_os_VintfObject.cpp
@@ -146,8 +146,8 @@
return nullptr;
}
jobject jMap = env->NewObject(gHashMapClazz, gHashMapInit);
- for (const Vndk &vndk : manifest->vndks()) {
- std::string key = to_string(vndk.versionRange());
+ for (const auto &vndk : manifest->vendorNdks()) {
+ std::string key = vndk.version();
env->CallObjectMethod(jMap, gHashMapPut,
env->NewStringUTF(key.c_str()), toJavaStringArray(env, vndk.libraries()));
}
diff --git a/core/jni/android_text_MeasuredText.cpp b/core/jni/android_text_MeasuredParagraph.cpp
similarity index 83%
rename from core/jni/android_text_MeasuredText.cpp
rename to core/jni/android_text_MeasuredParagraph.cpp
index af9d131..bdae0b2 100644
--- a/core/jni/android_text_MeasuredText.cpp
+++ b/core/jni/android_text_MeasuredParagraph.cpp
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-#define LOG_TAG "MeasuredText"
+#define LOG_TAG "MeasuredParagraph"
#include "ScopedIcuLocale.h"
#include "unicode/locid.h"
@@ -49,7 +49,7 @@
return reinterpret_cast<Paint*>(ptr);
}
-static inline minikin::MeasuredText* toMeasuredText(jlong ptr) {
+static inline minikin::MeasuredText* toMeasuredParagraph(jlong ptr) {
return reinterpret_cast<minikin::MeasuredText*>(ptr);
}
@@ -57,8 +57,8 @@
return reinterpret_cast<jlong>(ptr);
}
-static void releaseMeasuredText(jlong measuredTextPtr) {
- delete toMeasuredText(measuredTextPtr);
+static void releaseMeasuredParagraph(jlong measuredTextPtr) {
+ delete toMeasuredParagraph(measuredTextPtr);
}
// Regular JNI
@@ -84,7 +84,7 @@
}
// Regular JNI
-static jlong nBuildNativeMeasuredText(JNIEnv* env, jclass /* unused */, jlong builderPtr,
+static jlong nBuildNativeMeasuredParagraph(JNIEnv* env, jclass /* unused */, jlong builderPtr,
jcharArray javaText) {
ScopedCharArrayRO text(env, javaText);
const minikin::U16StringPiece textBuffer(text.get(), text.size());
@@ -100,23 +100,23 @@
// CriticalNative
static jlong nGetReleaseFunc() {
- return toJLong(&releaseMeasuredText);
+ return toJLong(&releaseMeasuredParagraph);
}
static const JNINativeMethod gMethods[] = {
- // MeasuredTextBuilder native functions.
+ // MeasuredParagraphBuilder native functions.
{"nInitBuilder", "()J", (void*) nInitBuilder},
{"nAddStyleRun", "(JJIIZ)V", (void*) nAddStyleRun},
{"nAddReplacementRun", "(JJIIF)V", (void*) nAddReplacementRun},
- {"nBuildNativeMeasuredText", "(J[C)J", (void*) nBuildNativeMeasuredText},
+ {"nBuildNativeMeasuredParagraph", "(J[C)J", (void*) nBuildNativeMeasuredParagraph},
{"nFreeBuilder", "(J)V", (void*) nFreeBuilder},
- // MeasuredText native functions.
+ // MeasuredParagraph native functions.
{"nGetReleaseFunc", "()J", (void*) nGetReleaseFunc}, // Critical Natives
};
-int register_android_text_MeasuredText(JNIEnv* env) {
- return RegisterMethodsOrDie(env, "android/text/MeasuredText", gMethods, NELEM(gMethods));
+int register_android_text_MeasuredParagraph(JNIEnv* env) {
+ return RegisterMethodsOrDie(env, "android/text/MeasuredParagraph", gMethods, NELEM(gMethods));
}
}
diff --git a/core/jni/android_text_StaticLayout.cpp b/core/jni/android_text_StaticLayout.cpp
index b5c23df..682dc873 100644
--- a/core/jni/android_text_StaticLayout.cpp
+++ b/core/jni/android_text_StaticLayout.cpp
@@ -174,7 +174,7 @@
// Inputs
"[C" // text
- "J" // MeasuredText ptr.
+ "J" // MeasuredParagraph ptr.
"I" // length
"F" // firstWidth
"I" // firstWidthLineCount
diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp
index 5e5d59b..0ef5445 100644
--- a/core/jni/android_view_SurfaceControl.cpp
+++ b/core/jni/android_view_SurfaceControl.cpp
@@ -72,7 +72,6 @@
// Implements SkMallocPixelRef::ReleaseProc, to delete the screenshot on unref.
void DeleteScreenshot(void* addr, void* context) {
- SkASSERT(addr == ((ScreenshotClient*) context)->getPixels());
delete ((ScreenshotClient*) context);
}
diff --git a/core/proto/android/app/statusbarmanager.proto b/core/proto/android/app/statusbarmanager.proto
new file mode 100644
index 0000000..3d1447a
--- /dev/null
+++ b/core/proto/android/app/statusbarmanager.proto
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+syntax = "proto2";
+package android.app;
+option java_multiple_files = true;
+
+message StatusBarManagerProto {
+ enum WindowState {
+ WINDOW_STATE_SHOWING = 0;
+ WINDOW_STATE_HIDING = 1;
+ WINDOW_STATE_HIDDEN = 2;
+ }
+ enum TransientWindowState {
+ TRANSIENT_BAR_NONE = 0;
+ TRANSIENT_BAR_SHOW_REQUESTED = 1;
+ TRANSIENT_BAR_SHOWING = 2;
+ TRANSIENT_BAR_HIDING = 3;
+ }
+}
diff --git a/core/proto/android/content/activityinfo.proto b/core/proto/android/content/activityinfo.proto
new file mode 100644
index 0000000..012752a
--- /dev/null
+++ b/core/proto/android/content/activityinfo.proto
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+syntax = "proto2";
+package android.content;
+option java_multiple_files = true;
+
+message ActivityInfoProto {
+ enum ScreenOrientation {
+ SCREEN_ORIENTATION_UNSET = -2;
+ SCREEN_ORIENTATION_UNSPECIFIED = -1;
+ SCREEN_ORIENTATION_LANDSCAPE = 0;
+ SCREEN_ORIENTATION_PORTRAIT = 1;
+ SCREEN_ORIENTATION_USER = 2;
+ SCREEN_ORIENTATION_BEHIND = 3;
+ SCREEN_ORIENTATION_SENSOR = 4;
+ SCREEN_ORIENTATION_NOSENSOR = 5;
+ SCREEN_ORIENTATION_SENSOR_LANDSCAPE = 6;
+ SCREEN_ORIENTATION_SENSOR_PORTRAIT = 7;
+ SCREEN_ORIENTATION_REVERSE_LANDSCAPE = 8;
+ SCREEN_ORIENTATION_REVERSE_PORTRAIT = 9;
+ SCREEN_ORIENTATION_FULL_SENSOR = 10;
+ SCREEN_ORIENTATION_USER_LANDSCAPE = 11;
+ SCREEN_ORIENTATION_USER_PORTRAIT = 12;
+ SCREEN_ORIENTATION_FULL_USER = 13;
+ SCREEN_ORIENTATION_LOCKED = 14;
+ }
+}
+
diff --git a/core/proto/android/os/batterystats.proto b/core/proto/android/os/batterystats.proto
index 9999b4e..331f80f 100644
--- a/core/proto/android/os/batterystats.proto
+++ b/core/proto/android/os/batterystats.proto
@@ -674,14 +674,13 @@
// needed:
// top > foreground service > foreground > background > top sleeping > heavy weight > cache
enum State {
- // Time this uid has any processes in the top state (or above such as
- // persistent).
+ // Time this uid has any processes in the top state.
PROCESS_STATE_TOP = 0;
- // Time this uid has any process with a started out bound foreground
- // service, but none in the "top" state.
+ // Time this uid has any process with a started foreground service, but
+ // none in the "top" state.
PROCESS_STATE_FOREGROUND_SERVICE = 1;
- // Time this uid has any process in an active foreground state, but none
- // in the "top sleeping" or better state.
+ // Time this uid has any process in an active foreground state, but none in the
+ // "foreground service" or better state. Persistent and other foreground states go here.
PROCESS_STATE_FOREGROUND = 2;
// Time this uid has any process in an active background state, but none
// in the "foreground" or better state.
diff --git a/core/proto/android/providers/settings.proto b/core/proto/android/providers/settings.proto
index f5d098c..d4bdb9b 100644
--- a/core/proto/android/providers/settings.proto
+++ b/core/proto/android/providers/settings.proto
@@ -388,8 +388,9 @@
optional SettingProto enable_deletion_helper_no_threshold_toggle = 340;
optional SettingProto notification_snooze_options = 341;
optional SettingProto enable_gnss_raw_meas_full_tracking = 346;
+ optional SettingProto zram_enabled = 347;
- // Next tag = 347;
+ // Next tag = 348;
}
message SecureSettingsProto {
diff --git a/core/proto/android/server/windowmanagerservice.proto b/core/proto/android/server/windowmanagerservice.proto
index 04bdc5c..71f33c7 100644
--- a/core/proto/android/server/windowmanagerservice.proto
+++ b/core/proto/android/server/windowmanagerservice.proto
@@ -16,12 +16,15 @@
syntax = "proto2";
+import "frameworks/base/core/proto/android/app/statusbarmanager.proto";
+import "frameworks/base/core/proto/android/content/activityinfo.proto";
import "frameworks/base/core/proto/android/content/configuration.proto";
import "frameworks/base/core/proto/android/graphics/rect.proto";
import "frameworks/base/core/proto/android/server/appwindowthumbnail.proto";
import "frameworks/base/core/proto/android/server/surfaceanimator.proto";
import "frameworks/base/core/proto/android/view/displaycutout.proto";
import "frameworks/base/core/proto/android/view/displayinfo.proto";
+import "frameworks/base/core/proto/android/view/surface.proto";
import "frameworks/base/core/proto/android/view/windowlayoutparams.proto";
package com.android.server.wm.proto;
@@ -49,8 +52,62 @@
repeated IdentifierProto windows = 3;
}
+message BarControllerProto {
+ optional .android.app.StatusBarManagerProto.WindowState state = 1;
+ optional .android.app.StatusBarManagerProto.TransientWindowState transient_state = 2;
+}
+
+message WindowOrientationListenerProto {
+ optional bool enabled = 1;
+ optional .android.view.SurfaceProto.Rotation rotation = 2;
+}
+
+message KeyguardServiceDelegateProto {
+ optional bool showing = 1;
+ optional bool occluded = 2;
+ optional bool secure = 3;
+ enum ScreenState {
+ SCREEN_STATE_OFF = 0;
+ SCREEN_STATE_TURNING_ON = 1;
+ SCREEN_STATE_ON = 2;
+ SCREEN_STATE_TURNING_OFF = 3;
+ }
+ optional ScreenState screen_state = 4;
+ enum InteractiveState {
+ INTERACTIVE_STATE_SLEEP = 0;
+ INTERACTIVE_STATE_WAKING = 1;
+ INTERACTIVE_STATE_AWAKE = 2;
+ INTERACTIVE_STATE_GOING_TO_SLEEP = 3;
+ }
+ optional InteractiveState interactive_state = 5;
+}
+
/* represents PhoneWindowManager */
message WindowManagerPolicyProto {
+ optional int32 last_system_ui_flags = 1;
+ enum UserRotationMode {
+ USER_ROTATION_FREE = 0;
+ USER_ROTATION_LOCKED = 1;
+ }
+ optional UserRotationMode rotation_mode = 2;
+ optional .android.view.SurfaceProto.Rotation rotation = 3;
+ optional .android.content.ActivityInfoProto.ScreenOrientation orientation = 4;
+ optional bool screen_on_fully = 5;
+ optional bool keyguard_draw_complete = 6;
+ optional bool window_manager_draw_complete = 7;
+ optional string focused_app_token = 8;
+ optional IdentifierProto focused_window = 9;
+ optional IdentifierProto top_fullscreen_opaque_window = 10;
+ optional IdentifierProto top_fullscreen_opaque_or_dimming_window = 11;
+ optional bool keyguard_occluded = 12;
+ optional bool keyguard_occluded_changed = 13;
+ optional bool keyguard_occluded_pending = 14;
+ optional bool force_status_bar = 15;
+ optional bool force_status_bar_from_keyguard = 16;
+ optional BarControllerProto status_bar = 17;
+ optional BarControllerProto navigation_bar = 18;
+ optional WindowOrientationListenerProto orientation_listener = 19;
+ optional KeyguardServiceDelegateProto keyguard_delegate = 20;
}
/* represents AppTransition */
diff --git a/core/java/android/security/recoverablekeystore/KeyStoreRecoveryData.aidl b/core/proto/android/view/surface.proto
similarity index 67%
copy from core/java/android/security/recoverablekeystore/KeyStoreRecoveryData.aidl
copy to core/proto/android/view/surface.proto
index bd76051..8f5f695 100644
--- a/core/java/android/security/recoverablekeystore/KeyStoreRecoveryData.aidl
+++ b/core/proto/android/view/surface.proto
@@ -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,7 +14,15 @@
* limitations under the License.
*/
-package android.security.recoverablekeystore;
+syntax = "proto2";
+package android.view;
+option java_multiple_files = true;
-/* @hide */
-parcelable KeyStoreRecoveryData;
+message SurfaceProto {
+ enum Rotation {
+ ROTATION_0 = 0;
+ ROTATION_90 = 1;
+ ROTATION_180 = 2;
+ ROTATION_270 = 3;
+ }
+}
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 4bf1cb0..a3e8f1e 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -575,6 +575,7 @@
<!-- Added in P -->
<protected-broadcast android:name="android.app.action.PROFILE_OWNER_CHANGED" />
<protected-broadcast android:name="android.app.action.TRANSFER_OWNERSHIP_COMPLETE" />
+ <protected-broadcast android:name="android.app.action.DATA_SHARING_RESTRICTION_CHANGED" />
<!-- ====================================================================== -->
<!-- RUNTIME PERMISSIONS -->
@@ -2952,13 +2953,14 @@
<!-- Allows an application to collect usage infomation about brightness slider changes.
<p>Not for use by third-party applications.</p>
- TODO: make a System API
- @hide -->
+ @hide
+ @SystemApi -->
<permission android:name="android.permission.BRIGHTNESS_SLIDER_USAGE"
- android:protectionLevel="signature|privileged" />
+ android:protectionLevel="signature|privileged|development" />
<!-- Allows an application to modify the display brightness configuration
- @hide -->
+ @hide
+ @SystemApi -->
<permission android:name="android.permission.CONFIGURE_DISPLAY_BRIGHTNESS"
android:protectionLevel="signature|privileged|development" />
@@ -3308,6 +3310,10 @@
<permission android:name="android.permission.BIND_PACKAGE_VERIFIER"
android:protectionLevel="signature" />
+ <!-- @SystemApi @hide Allows an application to mark other applications as harmful -->
+ <permission android:name="android.permission.SET_HARMFUL_APP_WARNINGS"
+ android:protectionLevel="signature|verifier" />
+
<!-- @SystemApi @hide Intent filter verifier needs to have this permission before the
PackageManager will trust it to verify intent filters.
-->
@@ -3394,6 +3400,12 @@
<permission android:name="android.permission.PROVIDE_TRUST_AGENT"
android:protectionLevel="signature|privileged" />
+ <!-- @SystemApi Allows an application to show a message
+ on the keyguard when asking to dismiss it.
+ @hide For security reasons, this is a platform-only permission. -->
+ <permission android:name="android.permission.SHOW_KEYGUARD_MESSAGE"
+ android:protectionLevel="signature|privileged" />
+
<!-- Allows an application to launch the trust agent settings activity.
@hide -->
<permission android:name="android.permission.LAUNCH_TRUST_AGENT_SETTINGS"
@@ -3873,6 +3885,13 @@
android:excludeFromRecents="true">
</activity>
+ <activity android:name="com.android.internal.app.HarmfulAppWarningActivity"
+ android:theme="@style/Theme.DeviceDefault.Light.Dialog.Alert"
+ android:excludeFromRecents="true"
+ android:process=":ui"
+ android:exported="false">
+ </activity>
+
<receiver android:name="com.android.server.BootReceiver"
android:systemUserOnly="true">
<intent-filter android:priority="1000">
diff --git a/core/res/res/drawable/ic_screenshot.xml b/core/res/res/drawable/ic_screenshot.xml
new file mode 100644
index 0000000..3074b28
--- /dev/null
+++ b/core/res/res/drawable/ic_screenshot.xml
@@ -0,0 +1,33 @@
+<!--
+Copyright (C) 2018 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24.0dp"
+ android:height="24.0dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:pathData="M0,0h24v24H0V0z"
+ android:fillColor="#00000000"/>
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M17.0,1.0L7.0,1.0C5.9,1.0 5.0,1.9 5.0,3.0l0.0,18.0c0.0,1.1 0.9,2.0 2.0,2.0l10.0,0.0c1.1,0.0 2.0,-0.9 2.0,-2.0L19.0,3.0C19.0,1.9 18.1,1.0 17.0,1.0zM17.0,20.0L7.0,20.0L7.0,4.0l10.0,0.0L17.0,20.0z"/>
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M13.0,6.0l-4.0,0.0 0.0,4.0 1.5,0.0 0.0,-2.5 2.5,0.0z"/>
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M11.0,18.0l4.0,0.0 0.0,-4.0 -1.5,0.0 0.0,2.5 -2.5,0.0z"/>
+</vector>
diff --git a/core/res/res/raw/color_fade_frag.frag b/core/res/res/raw/color_fade_frag.frag
index a66a5a7..29975d5 100644
--- a/core/res/res/raw/color_fade_frag.frag
+++ b/core/res/res/raw/color_fade_frag.frag
@@ -3,40 +3,12 @@
precision mediump float;
uniform samplerExternalOES texUnit;
uniform float opacity;
-uniform float saturation;
uniform float gamma;
varying vec2 UV;
-vec3 rgb2hsl(vec3 rgb)
-{
- float e = 1.0e-7;
-
- vec4 p = rgb.g < rgb.b ? vec4(rgb.bg, -1.0, 2.0 / 3.0) : vec4(rgb.gb, 0.0, -1.0 / 3.0);
- vec4 q = rgb.r < p.x ? vec4(p.xyw, rgb.r) : vec4(rgb.r, p.yzx);
-
- float v = q.x;
- float c = v - min(q.w, q.y);
- float h = abs((q.w - q.y) / (6.0 * c + e) + q.z);
- float l = v - c * 0.5;
- float s = c / (1.0 - abs(2.0 * l - 1.0) + e);
- return clamp(vec3(h, s, l), 0.0, 1.0);
-}
-
-vec3 hsl2rgb(vec3 hsl)
-{
- vec3 h = vec3(hsl.x * 6.0);
- vec3 p = abs(h - vec3(3.0, 2.0, 4.0));
- vec3 q = 2.0 - p;
-
- vec3 rgb = clamp(vec3(p.x - 1.0, q.yz), 0.0, 1.0);
- float c = (1.0 - abs(2.0 * hsl.z - 1.0)) * hsl.y;
- return (rgb - vec3(0.5)) * c + hsl.z;
-}
-
void main()
{
vec4 color = texture2D(texUnit, UV);
- vec3 hsl = rgb2hsl(color.xyz);
- vec3 rgb = pow(hsl2rgb(vec3(hsl.x, hsl.y * saturation, hsl.z * opacity)), vec3(gamma));
+ vec3 rgb = pow(color.rgb * opacity, vec3(gamma));
gl_FragColor = vec4(rgb, 1.0);
}
diff --git a/core/res/res/values-mcc204-mnc04/config.xml b/core/res/res/values-mcc204-mnc04/config.xml
index c66ed12..4a3bf22 100755
--- a/core/res/res/values-mcc204-mnc04/config.xml
+++ b/core/res/res/values-mcc204-mnc04/config.xml
@@ -25,15 +25,5 @@
-->
<integer name="config_mobile_mtu">1358</integer>
- <!--Thresholds for LTE dbm in status bar-->
- <integer-array translatable="false" name="config_lteDbmThresholds">
- <item>-140</item> <!-- SIGNAL_STRENGTH_NONE_OR_UNKNOWN -->
- <item>-115</item> <!-- SIGNAL_STRENGTH_POOR -->
- <item>-105</item> <!-- SIGNAL_STRENGTH_MODERATE -->
- <item>-95</item> <!-- SIGNAL_STRENGTH_GOOD -->
- <item>-85</item> <!-- SIGNAL_STRENGTH_GREAT -->
- <item>-44</item>
- </integer-array>
-
<string translatable="false" name="prohibit_manual_network_selection_in_gobal_mode">true;BAE0000000000000</string>
</resources>
diff --git a/core/res/res/values-mcc311-mnc480/config.xml b/core/res/res/values-mcc311-mnc480/config.xml
index 04f182e..cc7daa8 100755
--- a/core/res/res/values-mcc311-mnc480/config.xml
+++ b/core/res/res/values-mcc311-mnc480/config.xml
@@ -51,16 +51,6 @@
<bool name="config_auto_attach_data_on_creation">false</bool>
- <!--Thresholds for LTE dbm in status bar-->
- <integer-array translatable="false" name="config_lteDbmThresholds">
- <item>-140</item> <!-- SIGNAL_STRENGTH_NONE_OR_UNKNOWN -->
- <item>-115</item> <!-- SIGNAL_STRENGTH_POOR -->
- <item>-105</item> <!-- SIGNAL_STRENGTH_MODERATE -->
- <item>-95</item> <!-- SIGNAL_STRENGTH_GOOD -->
- <item>-85</item> <!-- SIGNAL_STRENGTH_GREAT -->
- <item>-44</item>
- </integer-array>
-
<string translatable="false" name="prohibit_manual_network_selection_in_gobal_mode">true</string>
<bool name="config_use_sim_language_file">true</bool>
diff --git a/core/res/res/values-mcc505-mnc01/config.xml b/core/res/res/values-mcc505-mnc01/config.xml
index 5a5b8f7..bc088d1 100644
--- a/core/res/res/values-mcc505-mnc01/config.xml
+++ b/core/res/res/values-mcc505-mnc01/config.xml
@@ -31,16 +31,6 @@
<item>9</item>
</integer-array>
- <!--Thresholds for LTE dbm in status bar-->
- <integer-array translatable="false" name="config_lteDbmThresholds">
- <item>-140</item> <!-- SIGNAL_STRENGTH_NONE_OR_UNKNOWN -->
- <item>-120</item> <!-- SIGNAL_STRENGTH_POOR -->
- <item>-115</item> <!-- SIGNAL_STRENGTH_MODERATE -->
- <item>-100</item> <!-- SIGNAL_STRENGTH_GOOD -->
- <item>-90</item> <!-- SIGNAL_STRENGTH_GREAT -->
- <item>-44</item>
- </integer-array>
-
<!-- Configure mobile network MTU. Carrier specific value is set here.
-->
<integer name="config_mobile_mtu">1400</integer>
diff --git a/core/res/res/values-mcc505-mnc11/config.xml b/core/res/res/values-mcc505-mnc11/config.xml
deleted file mode 100644
index 6d085c1..0000000
--- a/core/res/res/values-mcc505-mnc11/config.xml
+++ /dev/null
@@ -1,32 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-** Copyright 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 my obtain a copy of the License at
-**
-** http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<!-- These resources are around just to allow their values to be customized
- for different hardware and product builds. -->
-<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <!--Thresholds for LTE dbm in status bar-->
- <integer-array translatable="false" name="config_lteDbmThresholds">
- <item>-140</item> <!-- SIGNAL_STRENGTH_NONE_OR_UNKNOWN -->
- <item>-120</item> <!-- SIGNAL_STRENGTH_POOR -->
- <item>-115</item> <!-- SIGNAL_STRENGTH_MODERATE -->
- <item>-100</item> <!-- SIGNAL_STRENGTH_GOOD -->
- <item>-90</item> <!-- SIGNAL_STRENGTH_GREAT -->
- <item>-44</item>
- </integer-array>
-</resources>
diff --git a/core/res/res/values-mcc505-mnc71/config.xml b/core/res/res/values-mcc505-mnc71/config.xml
deleted file mode 100644
index 6d085c1..0000000
--- a/core/res/res/values-mcc505-mnc71/config.xml
+++ /dev/null
@@ -1,32 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-** Copyright 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 my obtain a copy of the License at
-**
-** http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<!-- These resources are around just to allow their values to be customized
- for different hardware and product builds. -->
-<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <!--Thresholds for LTE dbm in status bar-->
- <integer-array translatable="false" name="config_lteDbmThresholds">
- <item>-140</item> <!-- SIGNAL_STRENGTH_NONE_OR_UNKNOWN -->
- <item>-120</item> <!-- SIGNAL_STRENGTH_POOR -->
- <item>-115</item> <!-- SIGNAL_STRENGTH_MODERATE -->
- <item>-100</item> <!-- SIGNAL_STRENGTH_GOOD -->
- <item>-90</item> <!-- SIGNAL_STRENGTH_GREAT -->
- <item>-44</item>
- </integer-array>
-</resources>
diff --git a/core/res/res/values-mcc505-mnc72/config.xml b/core/res/res/values-mcc505-mnc72/config.xml
deleted file mode 100644
index 6d085c1..0000000
--- a/core/res/res/values-mcc505-mnc72/config.xml
+++ /dev/null
@@ -1,32 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-** Copyright 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 my obtain a copy of the License at
-**
-** http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<!-- These resources are around just to allow their values to be customized
- for different hardware and product builds. -->
-<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <!--Thresholds for LTE dbm in status bar-->
- <integer-array translatable="false" name="config_lteDbmThresholds">
- <item>-140</item> <!-- SIGNAL_STRENGTH_NONE_OR_UNKNOWN -->
- <item>-120</item> <!-- SIGNAL_STRENGTH_POOR -->
- <item>-115</item> <!-- SIGNAL_STRENGTH_MODERATE -->
- <item>-100</item> <!-- SIGNAL_STRENGTH_GOOD -->
- <item>-90</item> <!-- SIGNAL_STRENGTH_GREAT -->
- <item>-44</item>
- </integer-array>
-</resources>
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index ee7c795..addbbf5 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -3030,6 +3030,10 @@
value, {@code false}, leaves the screen reader to consider other signals, such as
focusability or the presence of text, to decide what it focus.-->
<attr name="screenReaderFocusable" format="boolean" />
+
+ <!-- The title this view should present to accessibility as a pane title.
+ See {@link android.view.View#setAccessibilityPaneTitle(CharSequence)} -->
+ <attr name="accessibilityPaneTitle" format="string" />
</declare-styleable>
<!-- Attributes that can be assigned to a tag for a particular View. -->
@@ -7756,21 +7760,21 @@
<attr name="settingsActivity" />
</declare-styleable>
- <!-- @SystemApi Use <code>trust-agent</code> as the root tag of the XML resource that
+ <!-- Use <code>trust-agent</code> as the root tag of the XML resource that
describes an {@link android.service.trust.TrustAgentService}, which is
referenced from its {@link android.service.trust.TrustAgentService#TRUST_AGENT_META_DATA}
meta-data entry. Described here are the attributes that can be included in that tag.
@hide -->
<declare-styleable name="TrustAgent">
- <!-- @SystemApi Component name of an activity that allows the user to modify
+ <!-- Component name of an activity that allows the user to modify
the settings for this trust agent. @hide -->
<attr name="settingsActivity" />
- <!-- @SystemApi Title for a preference that allows that user to launch the
+ <!-- Title for a preference that allows that user to launch the
activity to modify trust agent settings. @hide -->
<attr name="title" />
- <!-- @SystemApi Summary for the same preference as the title. @hide -->
+ <!-- Summary for the same preference as the title. @hide -->
<attr name="summary" />
- <!-- @SystemApi Whether trust agent can unlock a user profile @hide -->
+ <!-- Whether trust agent can unlock a user profile @hide -->
<attr name="unlockProfile" format="boolean"/>
</declare-styleable>
@@ -7980,16 +7984,16 @@
by the enrollment application.
Described here are the attributes that can be included in that tag.
@hide
- @SystemApi -->
+ -->
<declare-styleable name="VoiceEnrollmentApplication">
- <!-- A globally unique ID for the keyphrase. @hide @SystemApi -->
+ <!-- A globally unique ID for the keyphrase. @hide -->
<attr name="searchKeyphraseId" format="integer" />
- <!-- The actual keyphrase/hint text, or empty if not keyphrase dependent. @hide @SystemApi -->
+ <!-- The actual keyphrase/hint text, or empty if not keyphrase dependent. @hide -->
<attr name="searchKeyphrase" format="string" />
<!-- A comma separated list of BCP-47 language tag for locales that are supported
- for this keyphrase, or empty if not locale dependent. @hide @SystemApi -->
+ for this keyphrase, or empty if not locale dependent. @hide -->
<attr name="searchKeyphraseSupportedLocales" format="string" />
- <!-- Flags for supported recognition modes. @hide @SystemApi -->
+ <!-- Flags for supported recognition modes. @hide -->
<attr name="searchKeyphraseRecognitionFlags">
<flag name="none" value="0" />
<flag name="voiceTrigger" value="0x1" />
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index efbe9c2..287f296 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -2568,9 +2568,9 @@
e.g. name=ro.oem.sku value=MKT210.
Overlay will be ignored unless system property exists and is
set to specified value -->
- <!-- @hide @SystemApi This shouldn't be public. -->
+ <!-- @hide This shouldn't be public. -->
<attr name="requiredSystemPropertyName" format="string" />
- <!-- @hide @SystemApi This shouldn't be public. -->
+ <!-- @hide This shouldn't be public. -->
<attr name="requiredSystemPropertyValue" format="string" />
</declare-styleable>
diff --git a/core/res/res/values/colors_material.xml b/core/res/res/values/colors_material.xml
index 0413100..f8a77f8 100644
--- a/core/res/res/values/colors_material.xml
+++ b/core/res/res/values/colors_material.xml
@@ -77,9 +77,9 @@
<item name="secondary_content_alpha_material_dark" format="float" type="dimen">.7</item>
<item name="secondary_content_alpha_material_light" format="float" type="dimen">0.54</item>
- <item name="highlight_alpha_material_light" format="float" type="dimen">0.12</item>
- <item name="highlight_alpha_material_dark" format="float" type="dimen">0.20</item>
- <item name="highlight_alpha_material_colored" format="float" type="dimen">0.26</item>
+ <item name="highlight_alpha_material_light" format="float" type="dimen">0.16</item>
+ <item name="highlight_alpha_material_dark" format="float" type="dimen">0.32</item>
+ <item name="highlight_alpha_material_colored" format="float" type="dimen">0.48</item>
<!-- Primary & accent colors -->
<eat-comment />
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 2440e9b..e3a910f 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -2481,6 +2481,7 @@
<string-array translatable="false" name="config_globalActionsList">
<item>power</item>
<item>restart</item>
+ <item>screenshot</item>
<item>logout</item>
<item>bugreport</item>
<item>users</item>
@@ -2667,6 +2668,13 @@
<bool name="config_sms_force_7bit_encoding">false</bool>
+ <!-- Number of physical SIM slots on the device. This includes both eSIM and pSIM slots, and
+ is not necessarily the same as the number of phones/logical modems supported by the device.
+ For example, a multi-sim device can have 2 phones/logical modems, but 3 physical slots,
+ or a single SIM device can have 1 phones/logical modems, but 2 physical slots (one eSIM
+ and one pSIM) -->
+ <integer name="config_num_physical_slots">1</integer>
+
<!--Thresholds for LTE dbm in status bar-->
<integer-array translatable="false" name="config_lteDbmThresholds">
<item>-140</item> <!-- SIGNAL_STRENGTH_NONE_OR_UNKNOWN -->
@@ -2776,6 +2784,11 @@
the display's native orientation. -->
<string translatable="false" name="config_mainBuiltInDisplayCutout"></string>
+ <!-- Whether the display cutout region of the main built-in display should be forced to
+ black in software (to avoid aliasing or emulate a cutout that is not physically existent).
+ -->
+ <bool name="config_fillMainBuiltInDisplayCutout">false</bool>
+
<!-- Ultrasound support for Mic/speaker path -->
<!-- Whether the default microphone audio source supports near-ultrasound frequencies
(range of 18 - 21 kHz). -->
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 6ec88dc..58ae76c 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -2861,6 +2861,7 @@
<public name="widgetFeatures" />
<public name="appComponentFactory" />
<public name="fallbackLineSpacing" />
+ <public name="accessibilityPaneTitle" />
</public-group>
<public-group type="style" first-id="0x010302e0">
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 0618a82..b2fa294 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -484,6 +484,9 @@
<!-- label for item that logouts the current user -->
<string name="global_action_logout">End session</string>
+ <!-- label for screenshot item in power menu -->
+ <string name="global_action_screenshot">Screenshot</string>
+
<!-- Take bug report menu title [CHAR LIMIT=NONE] -->
<string name="bugreport_title">Take bug report</string>
<!-- Message in bugreport dialog describing what it does [CHAR LIMIT=NONE] -->
@@ -3030,6 +3033,10 @@
<string name="wifi_wakeup_onboarding_subtext">When you\'re near a high quality saved network</string>
<!--Notification action to disable Wi-Fi Wake during onboarding.-->
<string name="wifi_wakeup_onboarding_action_disable">Don\'t turn back on</string>
+ <!--Notification title for when Wi-Fi Wake enables Wi-Fi.-->
+ <string name="wifi_wakeup_enabled_title">Wi\u2011Fi turned on automatically</string>
+ <!--Notification content for when Wi-Fi Wake enables Wi-Fi. %1$s is the SSID of the nearby saved network that triggered the wakeup. -->
+ <string name="wifi_wakeup_enabled_content">You\u0027re near a saved network: <xliff:g id="network_ssid">%1$s</xliff:g></string>
<!-- A notification is shown when a wifi captive portal network is detected. This is the notification's title. -->
<string name="wifi_available_sign_in">Sign in to Wi-Fi network</string>
@@ -4795,4 +4802,11 @@
<!--Battery saver warning. STOPSHIP: Remove it eventually. -->
<string name="battery_saver_warning_title" translatable="false">Extreme battery saver</string>
+
+ <!-- Label for the uninstall button on the harmful app warning dialog. -->
+ <string name="harmful_app_warning_uninstall">Uninstall</string>
+ <!-- Label for the launch anyway button on the harmful app warning dialog. -->
+ <string name="harmful_app_warning_launch_anyway">Launch anyway</string>
+ <!-- Title for the harmful app warning dialog. -->
+ <string name="harmful_app_warning_title">Uninstall harmful app?</string>
</resources>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index ac3d402..f4ced58 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -462,6 +462,7 @@
<java-symbol type="bool" name="config_useDefaultFocusHighlight" />
<java-symbol type="array" name="config_deviceSpecificSystemServices" />
<java-symbol type="string" name="config_deviceSpecificDevicePolicyManagerService" />
+ <java-symbol type="integer" name="config_num_physical_slots" />
<java-symbol type="color" name="tab_indicator_text_v4" />
@@ -1721,6 +1722,7 @@
<java-symbol type="string" name="global_action_lockdown" />
<java-symbol type="string" name="global_action_voice_assist" />
<java-symbol type="string" name="global_action_assist" />
+ <java-symbol type="string" name="global_action_screenshot" />
<java-symbol type="string" name="invalidPuk" />
<java-symbol type="string" name="lockscreen_carrier_default" />
<java-symbol type="style" name="Animation.LockScreen" />
@@ -1918,6 +1920,8 @@
<java-symbol type="string" name="wifi_wakeup_onboarding_title" />
<java-symbol type="string" name="wifi_wakeup_onboarding_subtext" />
<java-symbol type="string" name="wifi_wakeup_onboarding_action_disable" />
+ <java-symbol type="string" name="wifi_wakeup_enabled_title" />
+ <java-symbol type="string" name="wifi_wakeup_enabled_content" />
<java-symbol type="string" name="accessibility_binding_label" />
<java-symbol type="string" name="adb_active_notification_message" />
<java-symbol type="string" name="adb_active_notification_title" />
@@ -2434,9 +2438,6 @@
<!-- Cascading submenus -->
<java-symbol type="dimen" name="cascading_menus_min_smallest_width" />
- <!-- From SignalStrength -->
- <java-symbol type="array" name="config_lteDbmThresholds" />
-
<java-symbol type="string" name="android_system_label" />
<java-symbol type="string" name="system_error_wipe_data" />
<java-symbol type="string" name="system_error_manufacturer" />
@@ -2889,8 +2890,9 @@
<java-symbol type="bool" name="config_permissionReviewRequired" />
-
+ <!-- Global actions icons -->
<java-symbol type="drawable" name="ic_restart" />
+ <java-symbol type="drawable" name="ic_screenshot" />
<java-symbol type="drawable" name="emergency_icon" />
@@ -3201,6 +3203,7 @@
<java-symbol type="string" name="global_action_logout" />
<java-symbol type="string" name="config_mainBuiltInDisplayCutout" />
+ <java-symbol type="bool" name="config_fillMainBuiltInDisplayCutout" />
<java-symbol type="drawable" name="ic_logout" />
<java-symbol type="array" name="config_autoBrightnessDisplayValuesNits" />
@@ -3208,4 +3211,8 @@
<java-symbol type="array" name="config_screenBrightnessNits" />
<java-symbol type="string" name="shortcut_disabled_reason_unknown" />
+
+ <java-symbol type="string" name="harmful_app_warning_uninstall" />
+ <java-symbol type="string" name="harmful_app_warning_launch_anyway" />
+ <java-symbol type="string" name="harmful_app_warning_title" />
</resources>
diff --git a/core/tests/coretests/res/layout/activity_text_view.xml b/core/tests/coretests/res/layout/activity_text_view.xml
index e795c10..dca1656 100644
--- a/core/tests/coretests/res/layout/activity_text_view.xml
+++ b/core/tests/coretests/res/layout/activity_text_view.xml
@@ -25,4 +25,9 @@
android:layout_width="match_parent"
android:layout_height="wrap_content" />
+ <TextView
+ android:id="@+id/nonselectable_textview"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content" />
+
</LinearLayout>
diff --git a/core/tests/coretests/src/android/app/NotificationTest.java b/core/tests/coretests/src/android/app/NotificationTest.java
index b51c677..9ab7544 100644
--- a/core/tests/coretests/src/android/app/NotificationTest.java
+++ b/core/tests/coretests/src/android/app/NotificationTest.java
@@ -18,11 +18,13 @@
import static com.android.internal.util.NotificationColorUtil.satisfiesTextContrast;
+import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotSame;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
+import android.annotation.Nullable;
import android.content.Context;
import android.content.Intent;
import android.graphics.BitmapFactory;
@@ -40,6 +42,8 @@
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.util.function.Consumer;
+
@RunWith(AndroidJUnit4.class)
@SmallTest
public class NotificationTest {
@@ -281,6 +285,40 @@
assertTrue(notification.extras.getBoolean(Notification.EXTRA_IS_GROUP_CONVERSATION));
}
+ @Test
+ public void action_builder_hasDefault() {
+ Notification.Action action = makeNotificationAction(null);
+ assertEquals(Notification.Action.SEMANTIC_ACTION_NONE, action.getSemanticAction());
+ }
+
+ @Test
+ public void action_builder_setSemanticAction() {
+ Notification.Action action = makeNotificationAction(
+ builder -> builder.setSemanticAction(Notification.Action.SEMANTIC_ACTION_REPLY));
+ assertEquals(Notification.Action.SEMANTIC_ACTION_REPLY, action.getSemanticAction());
+ }
+
+ @Test
+ public void action_parcel() {
+ Notification.Action action = writeAndReadParcelable(
+ makeNotificationAction(builder -> {
+ builder.setSemanticAction(Notification.Action.SEMANTIC_ACTION_ARCHIVE);
+ builder.setAllowGeneratedReplies(true);
+ }));
+
+ assertEquals(Notification.Action.SEMANTIC_ACTION_ARCHIVE, action.getSemanticAction());
+ assertTrue(action.getAllowGeneratedReplies());
+ }
+
+ @Test
+ public void action_clone() {
+ Notification.Action action = makeNotificationAction(
+ builder -> builder.setSemanticAction(Notification.Action.SEMANTIC_ACTION_DELETE));
+ assertEquals(
+ Notification.Action.SEMANTIC_ACTION_DELETE,
+ action.clone().getSemanticAction());
+ }
+
private Notification.Builder getMediaNotification() {
MediaSession session = new MediaSession(mContext, "test");
return new Notification.Builder(mContext, "color")
@@ -300,4 +338,18 @@
p.setDataPosition(0);
return p.readParcelable(/* classLoader */ null);
}
+
+ /**
+ * Creates a Notification.Action by mocking initial dependencies and then applying
+ * transformations if they're defined.
+ */
+ private Notification.Action makeNotificationAction(
+ @Nullable Consumer<Notification.Action.Builder> transformation) {
+ Notification.Action.Builder actionBuilder =
+ new Notification.Action.Builder(null, "Test Title", null);
+ if (transformation != null) {
+ transformation.accept(actionBuilder);
+ }
+ return actionBuilder.build();
+ }
}
diff --git a/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java b/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java
index c19a343..aefc47e 100644
--- a/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java
+++ b/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java
@@ -42,8 +42,7 @@
@RunWith(AndroidJUnit4.class)
@SmallTest
-// TODO: b/70616950
-//@Presubmit
+@Presubmit
public class ObjectPoolTests {
// 1. Check if two obtained objects from pool are not the same.
diff --git a/core/tests/coretests/src/android/content/pm/PackageParserTest.java b/core/tests/coretests/src/android/content/pm/PackageParserTest.java
index 53f4f3a..267267e 100644
--- a/core/tests/coretests/src/android/content/pm/PackageParserTest.java
+++ b/core/tests/coretests/src/android/content/pm/PackageParserTest.java
@@ -146,14 +146,14 @@
}
private void verifyComputeTargetSdkVersion(int targetSdkVersion, String targetSdkCodename,
- boolean isPlatformReleased, int expectedTargetSdk) {
+ boolean isPlatformReleased, int expectedTargetSdk, boolean forceCurrentDev) {
final String[] outError = new String[1];
final int result = PackageParser.computeTargetSdkVersion(
targetSdkVersion,
targetSdkCodename,
- PLATFORM_VERSION,
isPlatformReleased ? CODENAMES_RELEASED : CODENAMES_PRE_RELEASE,
- outError);
+ outError,
+ forceCurrentDev);
assertEquals(result, expectedTargetSdk);
@@ -169,34 +169,45 @@
// Do allow older release targetSdkVersion on pre-release platform.
// APP: Released API 10
// DEV: Pre-release API 20
- verifyComputeTargetSdkVersion(OLDER_VERSION, RELEASED, false, OLDER_VERSION);
+ verifyComputeTargetSdkVersion(OLDER_VERSION, RELEASED, false, OLDER_VERSION,
+ false /* forceCurrentDev */);
// Do allow same release targetSdkVersion on pre-release platform.
// APP: Released API 20
// DEV: Pre-release API 20
- verifyComputeTargetSdkVersion(PLATFORM_VERSION, RELEASED, false, PLATFORM_VERSION);
+ verifyComputeTargetSdkVersion(PLATFORM_VERSION, RELEASED, false, PLATFORM_VERSION,
+ false /* forceCurrentDev */);
// Do allow newer release targetSdkVersion on pre-release platform.
// APP: Released API 30
// DEV: Pre-release API 20
- verifyComputeTargetSdkVersion(NEWER_VERSION, RELEASED, false, NEWER_VERSION);
+ verifyComputeTargetSdkVersion(NEWER_VERSION, RELEASED, false, NEWER_VERSION,
+ false /* forceCurrentDev */);
// Don't allow older pre-release targetSdkVersion on pre-release platform.
// APP: Pre-release API 10
// DEV: Pre-release API 20
- verifyComputeTargetSdkVersion(OLDER_VERSION, OLDER_PRE_RELEASE, false, -1);
+ verifyComputeTargetSdkVersion(OLDER_VERSION, OLDER_PRE_RELEASE, false, -1,
+ false /* forceCurrentDev */);
// Do allow same pre-release targetSdkVersion on pre-release platform,
// but overwrite the specified version with CUR_DEVELOPMENT.
// APP: Pre-release API 20
// DEV: Pre-release API 20
verifyComputeTargetSdkVersion(PLATFORM_VERSION, PRE_RELEASE, false,
- Build.VERSION_CODES.CUR_DEVELOPMENT);
+ Build.VERSION_CODES.CUR_DEVELOPMENT, false /* forceCurrentDev */);
// Don't allow newer pre-release targetSdkVersion on pre-release platform.
// APP: Pre-release API 30
// DEV: Pre-release API 20
- verifyComputeTargetSdkVersion(NEWER_VERSION, NEWER_PRE_RELEASE, false, -1);
+ verifyComputeTargetSdkVersion(NEWER_VERSION, NEWER_PRE_RELEASE, false, -1,
+ false /* forceCurrentDev */);
+
+ // Force newer pre-release targetSdkVersion to current pre-release platform.
+ // APP: Pre-release API 30
+ // DEV: Pre-release API 20
+ verifyComputeTargetSdkVersion(NEWER_VERSION, NEWER_PRE_RELEASE, false,
+ Build.VERSION_CODES.CUR_DEVELOPMENT, true /* forceCurrentDev */);
}
@Test
@@ -204,32 +215,38 @@
// Do allow older release targetSdkVersion on released platform.
// APP: Released API 10
// DEV: Released API 20
- verifyComputeTargetSdkVersion(OLDER_VERSION, RELEASED, true, OLDER_VERSION);
+ verifyComputeTargetSdkVersion(OLDER_VERSION, RELEASED, true, OLDER_VERSION,
+ false /* forceCurrentDev */);
// Do allow same release targetSdkVersion on released platform.
// APP: Released API 20
// DEV: Released API 20
- verifyComputeTargetSdkVersion(PLATFORM_VERSION, RELEASED, true, PLATFORM_VERSION);
+ verifyComputeTargetSdkVersion(PLATFORM_VERSION, RELEASED, true, PLATFORM_VERSION,
+ false /* forceCurrentDev */);
// Do allow newer release targetSdkVersion on released platform.
// APP: Released API 30
// DEV: Released API 20
- verifyComputeTargetSdkVersion(NEWER_VERSION, RELEASED, true, NEWER_VERSION);
+ verifyComputeTargetSdkVersion(NEWER_VERSION, RELEASED, true, NEWER_VERSION,
+ false /* forceCurrentDev */);
// Don't allow older pre-release targetSdkVersion on released platform.
// APP: Pre-release API 10
// DEV: Released API 20
- verifyComputeTargetSdkVersion(OLDER_VERSION, OLDER_PRE_RELEASE, true, -1);
+ verifyComputeTargetSdkVersion(OLDER_VERSION, OLDER_PRE_RELEASE, true, -1,
+ false /* forceCurrentDev */);
// Don't allow same pre-release targetSdkVersion on released platform.
// APP: Pre-release API 20
// DEV: Released API 20
- verifyComputeTargetSdkVersion(PLATFORM_VERSION, PRE_RELEASE, true, -1);
+ verifyComputeTargetSdkVersion(PLATFORM_VERSION, PRE_RELEASE, true, -1,
+ false /* forceCurrentDev */);
// Don't allow newer pre-release targetSdkVersion on released platform.
// APP: Pre-release API 30
// DEV: Released API 20
- verifyComputeTargetSdkVersion(NEWER_VERSION, NEWER_PRE_RELEASE, true, -1);
+ verifyComputeTargetSdkVersion(NEWER_VERSION, NEWER_PRE_RELEASE, true, -1,
+ false /* forceCurrentDev */);
}
/**
diff --git a/core/tests/coretests/src/android/provider/SettingsBackupTest.java b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
index 68789f3..410bee0 100644
--- a/core/tests/coretests/src/android/provider/SettingsBackupTest.java
+++ b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
@@ -350,6 +350,7 @@
Settings.Global.SYS_STORAGE_FULL_THRESHOLD_BYTES,
Settings.Global.SYS_STORAGE_THRESHOLD_MAX_BYTES,
Settings.Global.SYS_STORAGE_THRESHOLD_PERCENTAGE,
+ Settings.Global.SYS_VDSO,
Settings.Global.TCP_DEFAULT_INIT_RWND,
Settings.Global.TETHER_DUN_APN,
Settings.Global.TETHER_DUN_REQUIRED,
@@ -421,7 +422,8 @@
Settings.Global.WTF_IS_FATAL,
Settings.Global.ZEN_MODE,
Settings.Global.ZEN_MODE_CONFIG_ETAG,
- Settings.Global.ZEN_MODE_RINGER_LEVEL);
+ Settings.Global.ZEN_MODE_RINGER_LEVEL,
+ Settings.Global.ZRAM_ENABLED);
private static final Set<String> BACKUP_BLACKLISTED_SECURE_SETTINGS =
newHashSet(
@@ -513,6 +515,7 @@
Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE,
Settings.Secure.SETTINGS_CLASSNAME,
Settings.Secure.SHOW_NOTE_ABOUT_NOTIFICATION_HIDING, // candidate?
+ Settings.Secure.SHOW_ROTATION_SUGGESTIONS,
Settings.Secure.SKIP_FIRST_USE_HINTS, // candidate?
Settings.Secure.SMS_DEFAULT_APPLICATION,
Settings.Secure.TRUST_AGENTS_INITIALIZED,
diff --git a/core/tests/coretests/src/android/text/MeasuredTextTest.java b/core/tests/coretests/src/android/text/MeasuredParagraphTest.java
similarity index 87%
rename from core/tests/coretests/src/android/text/MeasuredTextTest.java
rename to core/tests/coretests/src/android/text/MeasuredParagraphTest.java
index ddef0c6..5d33397 100644
--- a/core/tests/coretests/src/android/text/MeasuredTextTest.java
+++ b/core/tests/coretests/src/android/text/MeasuredParagraphTest.java
@@ -31,7 +31,7 @@
@SmallTest
@RunWith(AndroidJUnit4.class)
-public class MeasuredTextTest {
+public class MeasuredParagraphTest {
private static final TextDirectionHeuristic LTR = TextDirectionHeuristics.LTR;
private static final TextDirectionHeuristic RTL = TextDirectionHeuristics.RTL;
@@ -60,9 +60,9 @@
@Test
public void buildForBidi() {
- MeasuredText mt = null;
+ MeasuredParagraph mt = null;
- mt = MeasuredText.buildForBidi("XXX", 0, 3, LTR, null);
+ mt = MeasuredParagraph.buildForBidi("XXX", 0, 3, LTR, null);
assertNotNull(mt);
assertNotNull(mt.getChars());
assertEquals("XXX", charsToString(mt.getChars()));
@@ -75,7 +75,7 @@
assertEquals(0, mt.getNativePtr());
// Recycle it
- MeasuredText mt2 = MeasuredText.buildForBidi("_VVV_", 1, 4, RTL, mt);
+ MeasuredParagraph mt2 = MeasuredParagraph.buildForBidi("_VVV_", 1, 4, RTL, mt);
assertEquals(mt2, mt);
assertNotNull(mt2.getChars());
assertEquals("VVV", charsToString(mt.getChars()));
@@ -91,9 +91,9 @@
@Test
public void buildForMeasurement() {
- MeasuredText mt = null;
+ MeasuredParagraph mt = null;
- mt = MeasuredText.buildForMeasurement(PAINT, "XXX", 0, 3, LTR, null);
+ mt = MeasuredParagraph.buildForMeasurement(PAINT, "XXX", 0, 3, LTR, null);
assertNotNull(mt);
assertNotNull(mt.getChars());
assertEquals("XXX", charsToString(mt.getChars()));
@@ -109,7 +109,8 @@
assertEquals(0, mt.getNativePtr());
// Recycle it
- MeasuredText mt2 = MeasuredText.buildForMeasurement(PAINT, "_VVV_", 1, 4, RTL, mt);
+ MeasuredParagraph mt2 =
+ MeasuredParagraph.buildForMeasurement(PAINT, "_VVV_", 1, 4, RTL, mt);
assertEquals(mt2, mt);
assertNotNull(mt2.getChars());
assertEquals("VVV", charsToString(mt.getChars()));
@@ -129,9 +130,9 @@
@Test
public void buildForStaticLayout() {
- MeasuredText mt = null;
+ MeasuredParagraph mt = null;
- mt = MeasuredText.buildForStaticLayout(PAINT, "XXX", 0, 3, LTR, null);
+ mt = MeasuredParagraph.buildForStaticLayout(PAINT, "XXX", 0, 3, LTR, null);
assertNotNull(mt);
assertNotNull(mt.getChars());
assertEquals("XXX", charsToString(mt.getChars()));
@@ -145,7 +146,8 @@
assertNotEquals(0, mt.getNativePtr());
// Recycle it
- MeasuredText mt2 = MeasuredText.buildForStaticLayout(PAINT, "_VVV_", 1, 4, RTL, mt);
+ MeasuredParagraph mt2 =
+ MeasuredParagraph.buildForStaticLayout(PAINT, "_VVV_", 1, 4, RTL, mt);
assertEquals(mt2, mt);
assertNotNull(mt2.getChars());
assertEquals("VVV", charsToString(mt.getChars()));
@@ -163,6 +165,6 @@
@Test
public void testFor70146381() {
- MeasuredText.buildForMeasurement(PAINT, "X…", 0, 2, RTL, null);
+ MeasuredParagraph.buildForMeasurement(PAINT, "X…", 0, 2, RTL, null);
}
}
diff --git a/core/tests/coretests/src/android/view/textclassifier/TextClassificationTest.java b/core/tests/coretests/src/android/view/textclassifier/TextClassificationTest.java
new file mode 100644
index 0000000..9ee7fac
--- /dev/null
+++ b/core/tests/coretests/src/android/view/textclassifier/TextClassificationTest.java
@@ -0,0 +1,161 @@
+/*
+ * 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;
+
+import static org.junit.Assert.assertEquals;
+
+import android.content.Intent;
+import android.graphics.Bitmap;
+import android.graphics.Color;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.ColorDrawable;
+import android.os.LocaleList;
+import android.os.Parcel;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.view.View;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Locale;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class TextClassificationTest {
+
+ public BitmapDrawable generateTestDrawable(int width, int height, int colorValue) {
+ final int numPixels = width * height;
+ final int[] colors = new int[numPixels];
+ for (int i = 0; i < numPixels; ++i) {
+ colors[i] = colorValue;
+ }
+ final Bitmap bitmap = Bitmap.createBitmap(colors, width, height, Bitmap.Config.ARGB_8888);
+ final BitmapDrawable drawable = new BitmapDrawable(null, bitmap);
+ drawable.setTargetDensity(bitmap.getDensity());
+ return drawable;
+ }
+
+ @Test
+ public void testParcel() {
+ final String text = "text";
+ final BitmapDrawable primaryIcon = generateTestDrawable(16, 16, Color.RED);
+ final String primaryLabel = "primarylabel";
+ final Intent primaryIntent = new Intent("primaryintentaction");
+ final View.OnClickListener primaryOnClick = v -> { };
+ final BitmapDrawable secondaryIcon0 = generateTestDrawable(32, 288, Color.GREEN);
+ final String secondaryLabel0 = "secondarylabel0";
+ final Intent secondaryIntent0 = new Intent("secondaryintentaction0");
+ final BitmapDrawable secondaryIcon1 = generateTestDrawable(576, 288, Color.BLUE);
+ final String secondaryLabel1 = "secondaryLabel1";
+ final Intent secondaryIntent1 = null;
+ final BitmapDrawable secondaryIcon2 = null;
+ final String secondaryLabel2 = null;
+ final Intent secondaryIntent2 = new Intent("secondaryintentaction2");
+ final ColorDrawable secondaryIcon3 = new ColorDrawable(Color.CYAN);
+ final String secondaryLabel3 = null;
+ final Intent secondaryIntent3 = null;
+ final String signature = "signature";
+ final TextClassification reference = new TextClassification.Builder()
+ .setText(text)
+ .setPrimaryAction(primaryIntent, primaryLabel, primaryIcon)
+ .setOnClickListener(primaryOnClick)
+ .addSecondaryAction(null, null, null) // ignored
+ .addSecondaryAction(secondaryIntent0, secondaryLabel0, secondaryIcon0)
+ .addSecondaryAction(secondaryIntent1, secondaryLabel1, secondaryIcon1)
+ .addSecondaryAction(secondaryIntent2, secondaryLabel2, secondaryIcon2)
+ .addSecondaryAction(secondaryIntent3, secondaryLabel3, secondaryIcon3)
+ .setEntityType(TextClassifier.TYPE_ADDRESS, 0.3f)
+ .setEntityType(TextClassifier.TYPE_PHONE, 0.7f)
+ .setSignature(signature)
+ .build();
+
+ // Parcel and unparcel using ParcelableWrapper.
+ final TextClassification.ParcelableWrapper parcelableReference = new TextClassification
+ .ParcelableWrapper(reference);
+ final Parcel parcel = Parcel.obtain();
+ parcelableReference.writeToParcel(parcel, parcelableReference.describeContents());
+ parcel.setDataPosition(0);
+ final TextClassification result =
+ TextClassification.ParcelableWrapper.CREATOR.createFromParcel(
+ parcel).getTextClassification();
+
+ assertEquals(text, result.getText());
+ assertEquals(signature, result.getSignature());
+ assertEquals(4, result.getSecondaryActionsCount());
+
+ // Primary action (re-use existing icon).
+ final Bitmap resPrimaryIcon = ((BitmapDrawable) result.getIcon()).getBitmap();
+ assertEquals(primaryIcon.getBitmap().getPixel(0, 0), resPrimaryIcon.getPixel(0, 0));
+ assertEquals(16, resPrimaryIcon.getWidth());
+ assertEquals(16, resPrimaryIcon.getHeight());
+ assertEquals(primaryLabel, result.getLabel());
+ assertEquals(primaryIntent.getAction(), result.getIntent().getAction());
+ assertEquals(null, result.getOnClickListener()); // Non-parcelable.
+
+ // Secondary action 0 (scale with height limit).
+ final Bitmap resSecondaryIcon0 = ((BitmapDrawable) result.getSecondaryIcon(0)).getBitmap();
+ assertEquals(secondaryIcon0.getBitmap().getPixel(0, 0), resSecondaryIcon0.getPixel(0, 0));
+ assertEquals(16, resSecondaryIcon0.getWidth());
+ assertEquals(144, resSecondaryIcon0.getHeight());
+ assertEquals(secondaryLabel0, result.getSecondaryLabel(0));
+ assertEquals(secondaryIntent0.getAction(), result.getSecondaryIntent(0).getAction());
+
+ // Secondary action 1 (scale with width limit).
+ final Bitmap resSecondaryIcon1 = ((BitmapDrawable) result.getSecondaryIcon(1)).getBitmap();
+ assertEquals(secondaryIcon1.getBitmap().getPixel(0, 0), resSecondaryIcon1.getPixel(0, 0));
+ assertEquals(144, resSecondaryIcon1.getWidth());
+ assertEquals(72, resSecondaryIcon1.getHeight());
+ assertEquals(secondaryLabel1, result.getSecondaryLabel(1));
+ assertEquals(null, result.getSecondaryIntent(1));
+
+ // Secondary action 2 (no icon).
+ assertEquals(null, result.getSecondaryIcon(2));
+ assertEquals(null, result.getSecondaryLabel(2));
+ assertEquals(secondaryIntent2.getAction(), result.getSecondaryIntent(2).getAction());
+
+ // Secondary action 3 (convert non-bitmap drawable with negative size).
+ final Bitmap resSecondaryIcon3 = ((BitmapDrawable) result.getSecondaryIcon(3)).getBitmap();
+ assertEquals(secondaryIcon3.getColor(), resSecondaryIcon3.getPixel(0, 0));
+ assertEquals(1, resSecondaryIcon3.getWidth());
+ assertEquals(1, resSecondaryIcon3.getHeight());
+ assertEquals(null, result.getSecondaryLabel(3));
+ assertEquals(null, result.getSecondaryIntent(3));
+
+ // Entities.
+ assertEquals(2, result.getEntityCount());
+ assertEquals(TextClassifier.TYPE_PHONE, result.getEntity(0));
+ assertEquals(TextClassifier.TYPE_ADDRESS, result.getEntity(1));
+ assertEquals(0.7f, result.getConfidenceScore(TextClassifier.TYPE_PHONE), 1e-7f);
+ assertEquals(0.3f, result.getConfidenceScore(TextClassifier.TYPE_ADDRESS), 1e-7f);
+ }
+
+ @Test
+ public void testParcelOptions() {
+ TextClassification.Options reference = new TextClassification.Options();
+ reference.setDefaultLocales(new LocaleList(Locale.US, Locale.GERMANY));
+
+ // Parcel and unparcel.
+ final Parcel parcel = Parcel.obtain();
+ reference.writeToParcel(parcel, reference.describeContents());
+ parcel.setDataPosition(0);
+ TextClassification.Options result = TextClassification.Options.CREATOR.createFromParcel(
+ parcel);
+
+ assertEquals("en-US,de-DE", result.getDefaultLocales().toLanguageTags());
+ }
+}
diff --git a/core/tests/coretests/src/android/view/textclassifier/TextLinksTest.java b/core/tests/coretests/src/android/view/textclassifier/TextLinksTest.java
new file mode 100644
index 0000000..a82542c
--- /dev/null
+++ b/core/tests/coretests/src/android/view/textclassifier/TextLinksTest.java
@@ -0,0 +1,118 @@
+/*
+ * 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;
+
+import static org.junit.Assert.assertEquals;
+
+import android.os.LocaleList;
+import android.os.Parcel;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.util.ArrayMap;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class TextLinksTest {
+
+ private TextClassificationManager mTcm;
+ private TextClassifier mClassifier;
+
+ @Before
+ public void setup() {
+ mTcm = InstrumentationRegistry.getTargetContext()
+ .getSystemService(TextClassificationManager.class);
+ mTcm.setTextClassifier(null);
+ mClassifier = mTcm.getTextClassifier();
+ }
+
+ private Map<String, Float> getEntityScores(float address, float phone, float other) {
+ final Map<String, Float> result = new ArrayMap<>();
+ if (address > 0.f) {
+ result.put(TextClassifier.TYPE_ADDRESS, address);
+ }
+ if (phone > 0.f) {
+ result.put(TextClassifier.TYPE_PHONE, phone);
+ }
+ if (other > 0.f) {
+ result.put(TextClassifier.TYPE_OTHER, other);
+ }
+ return result;
+ }
+
+ @Test
+ 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)))
+ .build();
+
+ // Parcel and unparcel.
+ final Parcel parcel = Parcel.obtain();
+ reference.writeToParcel(parcel, reference.describeContents());
+ parcel.setDataPosition(0);
+ final TextLinks result = TextLinks.CREATOR.createFromParcel(parcel);
+ final List<TextLinks.TextLink> resultList = new ArrayList<>(result.getLinks());
+
+ assertEquals(2, resultList.size());
+ assertEquals(0, resultList.get(0).getStart());
+ assertEquals(4, resultList.get(0).getEnd());
+ assertEquals(1, resultList.get(0).getEntityCount());
+ assertEquals(TextClassifier.TYPE_OTHER, resultList.get(0).getEntity(0));
+ assertEquals(1.f, resultList.get(0).getConfidenceScore(TextClassifier.TYPE_OTHER), 1e-7f);
+ assertEquals(5, resultList.get(1).getStart());
+ assertEquals(12, resultList.get(1).getEnd());
+ assertEquals(3, resultList.get(1).getEntityCount());
+ assertEquals(TextClassifier.TYPE_ADDRESS, resultList.get(1).getEntity(0));
+ assertEquals(TextClassifier.TYPE_OTHER, resultList.get(1).getEntity(1));
+ assertEquals(TextClassifier.TYPE_PHONE, resultList.get(1).getEntity(2));
+ assertEquals(.8f, resultList.get(1).getConfidenceScore(TextClassifier.TYPE_ADDRESS), 1e-7f);
+ assertEquals(.5f, resultList.get(1).getConfidenceScore(TextClassifier.TYPE_OTHER), 1e-7f);
+ assertEquals(.1f, resultList.get(1).getConfidenceScore(TextClassifier.TYPE_PHONE), 1e-7f);
+ }
+
+ @Test
+ public void testParcelOptions() {
+ TextClassifier.EntityConfig entityConfig = new TextClassifier.EntityConfig(
+ TextClassifier.ENTITY_PRESET_NONE);
+ entityConfig.includeEntities("a", "b", "c");
+ entityConfig.excludeEntities("b");
+ TextLinks.Options reference = new TextLinks.Options();
+ reference.setDefaultLocales(new LocaleList(Locale.US, Locale.GERMANY));
+ reference.setEntityConfig(entityConfig);
+
+ // Parcel and unparcel.
+ final Parcel parcel = Parcel.obtain();
+ reference.writeToParcel(parcel, reference.describeContents());
+ parcel.setDataPosition(0);
+ TextLinks.Options result = TextLinks.Options.CREATOR.createFromParcel(parcel);
+
+ assertEquals("en-US,de-DE", result.getDefaultLocales().toLanguageTags());
+ assertEquals(Arrays.asList("a", "c"), result.getEntityConfig().getEntities(mClassifier));
+ }
+}
diff --git a/core/tests/coretests/src/android/view/textclassifier/TextSelectionTest.java b/core/tests/coretests/src/android/view/textclassifier/TextSelectionTest.java
new file mode 100644
index 0000000..e920236
--- /dev/null
+++ b/core/tests/coretests/src/android/view/textclassifier/TextSelectionTest.java
@@ -0,0 +1,86 @@
+/*
+ * 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;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import android.os.LocaleList;
+import android.os.Parcel;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Locale;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class TextSelectionTest {
+
+ @Test
+ public void testParcel() {
+ final int startIndex = 13;
+ final int endIndex = 37;
+ final String signature = "signature";
+ final TextSelection reference = new TextSelection.Builder(startIndex, endIndex)
+ .setEntityType(TextClassifier.TYPE_ADDRESS, 0.3f)
+ .setEntityType(TextClassifier.TYPE_PHONE, 0.7f)
+ .setEntityType(TextClassifier.TYPE_URL, 0.1f)
+ .setSignature(signature)
+ .build();
+
+ // Parcel and unparcel using ParcelableWrapper.
+ final TextSelection.ParcelableWrapper parcelableReference = new TextSelection
+ .ParcelableWrapper(reference);
+ final Parcel parcel = Parcel.obtain();
+ parcelableReference.writeToParcel(parcel, parcelableReference.describeContents());
+ parcel.setDataPosition(0);
+ final TextSelection result =
+ TextSelection.ParcelableWrapper.CREATOR.createFromParcel(
+ parcel).getTextSelection();
+
+ assertEquals(startIndex, result.getSelectionStartIndex());
+ assertEquals(endIndex, result.getSelectionEndIndex());
+ assertEquals(signature, result.getSignature());
+
+ assertEquals(3, result.getEntityCount());
+ assertEquals(TextClassifier.TYPE_PHONE, result.getEntity(0));
+ assertEquals(TextClassifier.TYPE_ADDRESS, result.getEntity(1));
+ assertEquals(TextClassifier.TYPE_URL, result.getEntity(2));
+ assertEquals(0.7f, result.getConfidenceScore(TextClassifier.TYPE_PHONE), 1e-7f);
+ assertEquals(0.3f, result.getConfidenceScore(TextClassifier.TYPE_ADDRESS), 1e-7f);
+ assertEquals(0.1f, result.getConfidenceScore(TextClassifier.TYPE_URL), 1e-7f);
+ }
+
+ @Test
+ public void testParcelOptions() {
+ TextSelection.Options reference = new TextSelection.Options();
+ reference.setDefaultLocales(new LocaleList(Locale.US, Locale.GERMANY));
+ reference.setDarkLaunchAllowed(true);
+
+ // Parcel and unparcel.
+ final Parcel parcel = Parcel.obtain();
+ reference.writeToParcel(parcel, reference.describeContents());
+ parcel.setDataPosition(0);
+ TextSelection.Options result = TextSelection.Options.CREATOR.createFromParcel(parcel);
+
+ assertEquals("en-US,de-DE", result.getDefaultLocales().toLanguageTags());
+ assertTrue(result.isDarkLaunchAllowed());
+ }
+}
diff --git a/core/tests/coretests/src/android/widget/TextViewActivityTest.java b/core/tests/coretests/src/android/widget/TextViewActivityTest.java
index 1a654f4..bbca12f 100644
--- a/core/tests/coretests/src/android/widget/TextViewActivityTest.java
+++ b/core/tests/coretests/src/android/widget/TextViewActivityTest.java
@@ -311,11 +311,20 @@
@Test
public void testToolbarAppearsAfterLinkClicked() throws Throwable {
+ runToolbarAppearsAfterLinkClickedTest(R.id.textview);
+ }
+
+ @Test
+ public void testToolbarAppearsAfterLinkClickedNonselectable() throws Throwable {
+ runToolbarAppearsAfterLinkClickedTest(R.id.nonselectable_textview);
+ }
+
+ private void runToolbarAppearsAfterLinkClickedTest(int id) throws Throwable {
+ TextView textView = mActivity.findViewById(id);
useSystemDefaultTextClassifier();
TextClassificationManager textClassificationManager =
mActivity.getSystemService(TextClassificationManager.class);
TextClassifier textClassifier = textClassificationManager.getTextClassifier();
- final TextView textView = mActivity.findViewById(R.id.textview);
SpannableString content = new SpannableString("Call me at +19148277737");
TextLinks links = textClassifier.generateLinks(content);
links.apply(content, null);
@@ -331,7 +340,7 @@
TextLinks.TextLink textLink = links.getLinks().iterator().next();
int position = (textLink.getStart() + textLink.getEnd()) / 2;
- onView(withId(R.id.textview)).perform(clickOnTextAtIndex(position));
+ onView(withId(id)).perform(clickOnTextAtIndex(position));
sleepForFloatingToolbarPopup();
assertFloatingToolbarIsDisplayed();
}
diff --git a/data/etc/platform.xml b/data/etc/platform.xml
index d2c855b..f169f22 100644
--- a/data/etc/platform.xml
+++ b/data/etc/platform.xml
@@ -168,6 +168,8 @@
<assign-permission name="android.permission.ACCESS_LOWPAN_STATE" uid="lowpan" />
<assign-permission name="android.permission.MANAGE_LOWPAN_INTERFACES" uid="lowpan" />
+ <assign-permission name="android.permission.STATSCOMPANION" uid="statsd" />
+
<!-- This is a list of all the libraries available for application
code to link against. -->
diff --git a/graphics/java/android/graphics/drawable/RippleBackground.java b/graphics/java/android/graphics/drawable/RippleBackground.java
index dea194e..4571553 100644
--- a/graphics/java/android/graphics/drawable/RippleBackground.java
+++ b/graphics/java/android/graphics/drawable/RippleBackground.java
@@ -16,17 +16,12 @@
package android.graphics.drawable;
-import android.animation.Animator;
-import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.animation.TimeInterpolator;
import android.graphics.Canvas;
-import android.graphics.CanvasProperty;
import android.graphics.Paint;
import android.graphics.Rect;
import android.util.FloatProperty;
-import android.view.DisplayListCanvas;
-import android.view.RenderNodeAnimator;
import android.view.animation.LinearInterpolator;
/**
@@ -78,8 +73,8 @@
private void onStateChanged(boolean animateChanged) {
float newOpacity = 0.0f;
- if (mHovered) newOpacity += 1.0f;
- if (mFocused) newOpacity += 1.0f;
+ if (mHovered) newOpacity += .25f;
+ if (mFocused) newOpacity += .75f;
if (mAnimator != null) {
mAnimator.cancel();
mAnimator = null;
diff --git a/graphics/java/android/graphics/drawable/RippleDrawable.java b/graphics/java/android/graphics/drawable/RippleDrawable.java
index 734cff5..b883656 100644
--- a/graphics/java/android/graphics/drawable/RippleDrawable.java
+++ b/graphics/java/android/graphics/drawable/RippleDrawable.java
@@ -264,8 +264,8 @@
}
setRippleActive(enabled && pressed);
-
setBackgroundActive(hovered, focused);
+
return changed;
}
@@ -879,22 +879,18 @@
// Grab the color for the current state and cut the alpha channel in
// half so that the ripple and background together yield full alpha.
final int color = mState.mColor.getColorForState(getState(), Color.BLACK);
- final int halfAlpha = (Color.alpha(color) / 2) << 24;
final Paint p = mRipplePaint;
if (mMaskColorFilter != null) {
// The ripple timing depends on the paint's alpha value, so we need
// to push just the alpha channel into the paint and let the filter
// handle the full-alpha color.
- final int fullAlphaColor = color | (0xFF << 24);
- mMaskColorFilter.setColor(fullAlphaColor);
-
- p.setColor(halfAlpha);
+ mMaskColorFilter.setColor(color | 0xFF000000);
+ p.setColor(color & 0xFF000000);
p.setColorFilter(mMaskColorFilter);
p.setShader(mMaskShader);
} else {
- final int halfAlphaColor = (color & 0xFFFFFF) | halfAlpha;
- p.setColor(halfAlphaColor);
+ p.setColor(color);
p.setColorFilter(null);
p.setShader(null);
}
diff --git a/libs/hwui/SkiaCanvas.cpp b/libs/hwui/SkiaCanvas.cpp
index 13e1ebe..2e08670 100644
--- a/libs/hwui/SkiaCanvas.cpp
+++ b/libs/hwui/SkiaCanvas.cpp
@@ -765,7 +765,8 @@
for (size_t i = start; i < end; i++) {
glyphs[i - start] = layout.getGlyphId(i);
- float x = hOffset + layout.getX(i);
+ float halfWidth = layout.getCharAdvance(i) * 0.5f;
+ float x = hOffset + layout.getX(i) + halfWidth;
float y = vOffset + layout.getY(i);
SkPoint pos;
@@ -776,8 +777,8 @@
}
xform[i - start].fSCos = tan.x();
xform[i - start].fSSin = tan.y();
- xform[i - start].fTx = pos.x() - tan.y() * y;
- xform[i - start].fTy = pos.y() + tan.x() * y;
+ xform[i - start].fTx = pos.x() - tan.y() * y - halfWidth * tan.x();
+ xform[i - start].fTy = pos.y() + tan.x() * y - halfWidth * tan.y();
}
this->asSkCanvas()->drawTextRSXform(glyphs, sizeof(uint16_t) * N, xform, nullptr, paint);
diff --git a/libs/hwui/pipeline/skia/SkiaVulkanReadback.h b/libs/hwui/pipeline/skia/SkiaVulkanReadback.h
new file mode 100644
index 0000000..65b89d6
--- /dev/null
+++ b/libs/hwui/pipeline/skia/SkiaVulkanReadback.h
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include "Readback.h"
+
+namespace android {
+namespace uirenderer {
+namespace skiapipeline {
+
+class SkiaVulkanReadback : public Readback {
+public:
+ SkiaVulkanReadback(renderthread::RenderThread& thread) : Readback(thread) {}
+
+ virtual CopyResult copySurfaceInto(Surface& surface, const Rect& srcRect,
+ SkBitmap* bitmap) override {
+ //TODO: implement Vulkan readback.
+ return CopyResult::UnknownError;
+ }
+
+ virtual CopyResult copyGraphicBufferInto(GraphicBuffer* graphicBuffer,
+ SkBitmap* bitmap) override {
+ //TODO: implement Vulkan readback.
+ return CopyResult::UnknownError;
+ }
+};
+
+} /* namespace skiapipeline */
+} /* namespace uirenderer */
+} /* namespace android */
diff --git a/libs/hwui/renderthread/RenderThread.cpp b/libs/hwui/renderthread/RenderThread.cpp
index 79dc09f..8e0546b 100644
--- a/libs/hwui/renderthread/RenderThread.cpp
+++ b/libs/hwui/renderthread/RenderThread.cpp
@@ -24,6 +24,7 @@
#include "hwui/Bitmap.h"
#include "pipeline/skia/SkiaOpenGLPipeline.h"
#include "pipeline/skia/SkiaOpenGLReadback.h"
+#include "pipeline/skia/SkiaVulkanReadback.h"
#include "pipeline/skia/SkiaVulkanPipeline.h"
#include "renderstate/RenderState.h"
#include "renderthread/OpenGLPipeline.h"
@@ -158,12 +159,11 @@
mReadback = new OpenGLReadbackImpl(*this);
break;
case RenderPipelineType::SkiaGL:
- case RenderPipelineType::SkiaVulkan:
- // It works to use the OpenGL pipeline for Vulkan but this is not
- // ideal as it causes us to create an OpenGL context in addition
- // to the Vulkan one.
mReadback = new skiapipeline::SkiaOpenGLReadback(*this);
break;
+ case RenderPipelineType::SkiaVulkan:
+ mReadback = new skiapipeline::SkiaVulkanReadback(*this);
+ break;
default:
LOG_ALWAYS_FATAL("canvas context type %d not supported", (int32_t)renderType);
break;
diff --git a/libs/services/Android.bp b/libs/services/Android.bp
new file mode 100644
index 0000000..e5e865f
--- /dev/null
+++ b/libs/services/Android.bp
@@ -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.
+
+// Provides C++ wrappers for system services.
+
+cc_library_shared {
+ name: "libservices",
+ srcs: [
+ ":IDropBoxManagerService.aidl",
+ "src/os/DropBoxManager.cpp",
+ "src/os/StatsLogEventWrapper.cpp",
+ ],
+
+ shared_libs: [
+ "libbinder",
+ "liblog",
+ "libcutils",
+ "libutils",
+ ],
+ header_libs: [
+ "libbase_headers",
+ ],
+ aidl: {
+ include_dirs: ["frameworks/base/core/java/"],
+ },
+
+ export_include_dirs: ["include"],
+ export_header_lib_headers: ["libbase_headers"],
+
+ cflags: [
+ "-Wall",
+ "-Werror",
+ "-Wunused",
+ "-Wunreachable-code",
+ ],
+}
diff --git a/libs/services/Android.mk b/libs/services/Android.mk
deleted file mode 100644
index d72059a..0000000
--- a/libs/services/Android.mk
+++ /dev/null
@@ -1,44 +0,0 @@
-# Copyright (C) 2010 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT 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)
-
-# Provides C++ wrappers for system services.
-
-include $(CLEAR_VARS)
-
-LOCAL_MODULE := libservices
-LOCAL_SRC_FILES := \
- ../../core/java/com/android/internal/os/IDropBoxManagerService.aidl \
- src/os/DropBoxManager.cpp \
- src/os/StatsLogEventWrapper.cpp
-
-LOCAL_AIDL_INCLUDES := \
- $(LOCAL_PATH)/../../core/java
-LOCAL_C_INCLUDES := \
- system/core/include
-LOCAL_SHARED_LIBRARIES := \
- libbinder \
- liblog \
- libcutils \
- libutils
-
-LOCAL_EXPORT_C_INCLUDE_DIRS := $(LOCAL_PATH)/include
-LOCAL_C_INCLUDES += $(LOCAL_PATH)/include
-
-LOCAL_CFLAGS += -Wall -Werror -Wunused -Wunreachable-code
-
-include $(BUILD_SHARED_LIBRARY)
-
-
diff --git a/native/android/net.c b/native/android/net.c
index de4b90c..60296a7 100644
--- a/native/android/net.c
+++ b/native/android/net.c
@@ -27,7 +27,7 @@
static const uint32_t k32BitMask = 0xffffffff;
// This value MUST be kept in sync with the corresponding value in
// the android.net.Network#getNetworkHandle() implementation.
- static const uint32_t kHandleMagic = 0xfacade;
+ static const uint32_t kHandleMagic = 0xcafed00d;
// Check for minimum acceptable version of the API in the low bits.
if (handle != NETWORK_UNSPECIFIED &&
diff --git a/packages/PrintSpooler/src/com/android/printspooler/widget/PrintContentView.java b/packages/PrintSpooler/src/com/android/printspooler/widget/PrintContentView.java
index 8b00ed0..faa10cc 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/widget/PrintContentView.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/widget/PrintContentView.java
@@ -23,6 +23,7 @@
import android.view.View;
import android.view.ViewGroup;
import android.view.inputmethod.InputMethodManager;
+
import com.android.printspooler.R;
/**
@@ -410,7 +411,7 @@
mPrintButton.offsetTopAndBottom(dy);
- mDraggableContent.notifySubtreeAccessibilityStateChangedIfNeeded();
+ mDraggableContent.notifyAccessibilitySubtreeChanged();
onDragProgress(progress);
}
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
index 48de1c9..3698132 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
@@ -1119,6 +1119,9 @@
dumpSetting(s, p,
Settings.Global.NOTIFICATION_SNOOZE_OPTIONS,
GlobalSettingsProto.NOTIFICATION_SNOOZE_OPTIONS);
+ dumpSetting(s, p,
+ Settings.Global.ZRAM_ENABLED,
+ GlobalSettingsProto.ZRAM_ENABLED);
}
/** Dump a single {@link SettingsState.Setting} to a proto buf */
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index b3d6357..0f43db0 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -134,6 +134,7 @@
<!-- Permission needed to access privileged VR APIs -->
<uses-permission android:name="android.permission.RESTRICTED_VR_ACCESS" />
<uses-permission android:name="android.permission.MANAGE_BIND_INSTANT_SERVICE" />
+ <uses-permission android:name="android.permission.SET_HARMFUL_APP_WARNINGS" />
<application android:label="@string/app_label"
android:defaultToDeviceProtectedStorage="true"
diff --git a/packages/Shell/src/com/android/shell/BugreportProgressService.java b/packages/Shell/src/com/android/shell/BugreportProgressService.java
index 73d03c6..600f0dc 100644
--- a/packages/Shell/src/com/android/shell/BugreportProgressService.java
+++ b/packages/Shell/src/com/android/shell/BugreportProgressService.java
@@ -616,7 +616,7 @@
final IWindowManager wm = IWindowManager.Stub
.asInterface(ServiceManager.getService(Context.WINDOW_SERVICE));
try {
- wm.dismissKeyguard(null);
+ wm.dismissKeyguard(null, null);
} catch (Exception e) {
// ignore it
}
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_status_view.xml b/packages/SystemUI/res-keyguard/layout/keyguard_status_view.xml
index c97cfc4..347cf1c 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_status_view.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_status_view.xml
@@ -34,7 +34,6 @@
android:orientation="vertical">
<RelativeLayout
android:id="@+id/keyguard_clock_container"
- android:animateLayoutChanges="true"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal|top">
diff --git a/packages/SystemUI/res/drawable/ic_sysbar_rotate_button.xml b/packages/SystemUI/res/drawable/ic_sysbar_rotate_button.xml
new file mode 100644
index 0000000..255e377
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_sysbar_rotate_button.xml
@@ -0,0 +1,117 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ 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.
+-->
+<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:aapt="http://schemas.android.com/aapt">
+ <aapt:attr name="android:drawable">
+ <vector android:height="24dp"
+ android:width="24dp"
+ android:viewportHeight="102"
+ android:viewportWidth="102"
+ android:tint="?attr/singleToneColor">
+ <group android:name="_R_G">
+ <group android:name="_R_G_L_0_G" android:translateX="53.086" android:translateY="48.907000000000004" android:pivotX="-2.083" android:pivotY="2.083" android:rotation="90">
+ <group android:name="_R_G_L_0_G_D_0_P_0_G_0_T_0" android:rotation="100.1" android:scaleX="0.7979999999999999" android:scaleY="0.7979999999999999">
+ <path android:name="_R_G_L_0_G_D_0_P_0" android:fillColor="#ffffff" android:fillAlpha="1" android:fillType="nonZero" android:pathData=" M17.15 -37.84 C30.19,-31.91 39.52,-19.62 41.9,-4.86 C42.15,-3.39 43.45,-2.31 44.95,-2.31 C46.88,-2.31 48.34,-4.06 48.05,-5.94 C44.37,-27.64 27.64,-45.91 0.84,-48.09 C-1.08,-48.25 -2.17,-45.91 -0.83,-44.53 C-0.83,-44.53 9.87,-33.83 9.87,-33.83 C10.67,-33.04 11.92,-33.04 12.76,-33.79 C12.76,-33.79 17.15,-37.84 17.15,-37.84c "/>
+ </group>
+ <group android:name="_R_G_L_0_G_D_1_P_0_G_0_T_0" android:rotation="87.2" android:scaleX="0.77" android:scaleY="0.77">
+ <path android:name="_R_G_L_0_G_D_1_P_0" android:fillColor="#ffffff" android:fillAlpha="1" android:fillType="nonZero" android:pathData=" M-21.32 42.01 C-34.36,36.07 -43.68,23.78 -46.07,9.02 C-46.33,7.55 -47.62,6.47 -49.12,6.47 C-51.04,6.47 -52.51,8.23 -52.21,10.11 C-48.53,31.81 -31.81,50.08 -5.01,52.25 C-3.09,52.42 -2,50.08 -3.34,48.7 C-3.34,48.7 -14.04,38 -14.04,38 C-14.84,37.21 -16.11,37.19 -16.93,37.96 C-16.93,37.96 -21.32,42.01 -21.32,42.01c "/>
+ </group>
+ <path android:name="_R_G_L_0_G_D_2_P_0" android:fillColor="#ffffff" android:fillAlpha="1" android:fillType="nonZero" android:pathData=" M40.77 9.4 C40.77,9.4 -9.4,-40.77 -9.4,-40.77 C-11.91,-43.28 -15.67,-43.28 -18.18,-40.77 C-18.18,-40.77 -44.94,-14.01 -44.94,-14.01 C-47.45,-11.5 -47.45,-7.74 -44.94,-5.23 C-44.94,-5.23 5.23,44.94 5.23,44.94 C7.74,47.45 11.51,47.45 14.01,44.94 C14.01,44.94 40.77,18.18 40.77,18.18 C43.28,15.67 43.28,11.91 40.77,9.4c M3.85 34.82 C3.85,34.82 -34.4,-3.44 -34.4,-3.44 C-34.4,-3.44 -7.64,-30.19 -7.64,-30.19 C-7.64,-30.19 30.61,8.06 30.61,8.06 C30.61,8.06 3.85,34.82 3.85,34.82c "/>
+ </group>
+ </group>
+ <group android:name="time_group"/>
+ </vector>
+ </aapt:attr>
+
+ <target android:name="_R_G_L_0_G_D_0_P_0_G_0_T_0">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator android:propertyName="rotation" android:duration="333" android:startOffset="0" android:valueFrom="100.1" android:valueTo="0" android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0"/>
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+
+ <target android:name="_R_G_L_0_G_D_0_P_0_G_0_T_0">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator android:propertyName="scaleX" android:duration="333" android:startOffset="0" android:valueFrom="0.798" android:valueTo="1" android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.667,1 1.0,1.0"/>
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator android:propertyName="scaleY" android:duration="333" android:startOffset="0" android:valueFrom="0.798" android:valueTo="1" android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.667,1 1.0,1.0"/>
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+
+ <target android:name="_R_G_L_0_G_D_1_P_0_G_0_T_0">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator android:propertyName="rotation" android:duration="333" android:startOffset="0" android:valueFrom="87.2" android:valueTo="0" android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0"/>
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+
+ <target android:name="_R_G_L_0_G_D_1_P_0_G_0_T_0">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator android:propertyName="scaleX" android:duration="333" android:startOffset="0" android:valueFrom="0.77" android:valueTo="1" android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.667,1 1.0,1.0"/>
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator android:propertyName="scaleY" android:duration="333" android:startOffset="0" android:valueFrom="0.77" android:valueTo="1" android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.667,1 1.0,1.0"/>
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+
+ <target android:name="_R_G_L_0_G">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator android:propertyName="rotation" android:duration="333" android:startOffset="0" android:valueFrom="90" android:valueTo="0" android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0"/>
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+
+ <target android:name="time_group">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator android:propertyName="translateX" android:duration="517" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"/>
+ </set>
+ </aapt:attr>
+ </target>
+</animated-vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/rotate_suggestion.xml b/packages/SystemUI/res/layout/rotate_suggestion.xml
new file mode 100644
index 0000000..7762950
--- /dev/null
+++ b/packages/SystemUI/res/layout/rotate_suggestion.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:systemui="http://schemas.android.com/apk/res-auto"
+ android:layout_width="@dimen/navigation_side_padding"
+ android:layout_height="match_parent"
+ android:layout_weight="0"
+ >
+ <com.android.systemui.statusbar.policy.KeyButtonView
+ android:id="@+id/rotate_suggestion"
+ android:layout_width="@dimen/navigation_extra_key_width"
+ android:layout_height="match_parent"
+ android:layout_marginEnd="2dp"
+ android:visibility="invisible"
+ android:scaleType="centerInside"
+ />
+ <!-- TODO android:contentDescription -->
+</FrameLayout>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index a19917d..dde4dcf 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -61,11 +61,26 @@
<!-- When the battery is low, this is displayed to the user in a dialog. The title of the low battery alert. [CHAR LIMIT=NONE]-->
<string name="battery_low_title">Battery is low</string>
+ <!-- When the battery is low and hybrid notifications are enabled, this is displayed to the user in a dialog.
+ The title of the low battery alert. [CHAR LIMIT=NONE]-->
+ <string name="battery_low_title_hybrid">Battery is low. Turn on Battery Saver</string>
+
<!-- A message that appears when the battery level is getting low in a dialog. This is
- appened to the subtitle of the low battery alert. "percentage" is the percentage of battery
+ appended to the subtitle of the low battery alert. "percentage" is the percentage of battery
remaining [CHAR LIMIT=none]-->
<string name="battery_low_percent_format"><xliff:g id="percentage">%s</xliff:g> remaining</string>
+ <!-- A message that appears when the battery remaining estimate is low in a dialog. This is
+ appended to the subtitle of the low battery alert. "percentage" is the percentage of battery
+ remaining. "time" is the amount of time remaining before the phone runs out of battery [CHAR LIMIT=none]-->
+ <string name="battery_low_percent_format_hybrid"><xliff:g id="percentage">%s</xliff:g> remaining, about <xliff:g id="time">%s</xliff:g> left based on your usage</string>
+
+ <!-- A message that appears when the battery remaining estimate is low in a dialog and insufficient
+ data was present to say it is customized to the user. This is appended to the subtitle of the
+ low battery alert. "percentage" is the percentage of battery remaining. "time" is the amount
+ of time remaining before the phone runs out of battery [CHAR LIMIT=none]-->
+ <string name="battery_low_percent_format_hybrid_short"><xliff:g id="percentage">%s</xliff:g> remaining, about <xliff:g id="time">%s</xliff:g> left</string>
+
<!-- Same as battery_low_percent_format, with a notice about battery saver if on. [CHAR LIMIT=none]-->
<string name="battery_low_percent_format_saver_started"><xliff:g id="percentage">%s</xliff:g> remaining. Battery Saver is on.</string>
@@ -173,22 +188,25 @@
[CHAR LIMIT=25] -->
<string name="compat_mode_off">Stretch to fill screen</string>
+ <!-- Power menu item for taking a screenshot [CHAR LIMIT=20]-->
+ <string name="global_action_screenshot">Screenshot</string>
+
<!-- Notification ticker displayed when a screenshot is being saved to the Gallery. [CHAR LIMIT=30] -->
<string name="screenshot_saving_ticker">Saving screenshot\u2026</string>
<!-- Notification title displayed when a screenshot is being saved to the Gallery. [CHAR LIMIT=50] -->
<string name="screenshot_saving_title">Saving screenshot\u2026</string>
<!-- Notification text displayed when a screenshot is being saved to the Gallery. [CHAR LIMIT=100] -->
- <string name="screenshot_saving_text">Screenshot is being saved.</string>
+ <string name="screenshot_saving_text">Screenshot is being saved</string>
<!-- Notification title displayed when a screenshot is saved to the Gallery. [CHAR LIMIT=50] -->
- <string name="screenshot_saved_title">Screenshot captured.</string>
+ <string name="screenshot_saved_title">Screenshot saved</string>
<!-- Notification text displayed when a screenshot is saved to the Gallery. [CHAR LIMIT=100] -->
- <string name="screenshot_saved_text">Tap to view your screenshot.</string>
+ <string name="screenshot_saved_text">Tap to view your screenshot</string>
<!-- Notification title displayed when we fail to take a screenshot. [CHAR LIMIT=50] -->
- <string name="screenshot_failed_title">Couldn\'t capture screenshot.</string>
+ <string name="screenshot_failed_title">Couldn\'t capture screenshot</string>
<!-- Notification text displayed when we fail to save a screenshot for unknown reasons. [CHAR LIMIT=100] -->
- <string name="screenshot_failed_to_save_unknown_text">Problem encountered while saving screenshot.</string>
+ <string name="screenshot_failed_to_save_unknown_text">Problem encountered while saving screenshot</string>
<!-- Notification text displayed when we fail to save a screenshot. [CHAR LIMIT=100] -->
- <string name="screenshot_failed_to_save_text">Can\'t save screenshot due to limited storage space.</string>
+ <string name="screenshot_failed_to_save_text">Can\'t save screenshot due to limited storage space</string>
<!-- Notification text displayed when we fail to take a screenshot. [CHAR LIMIT=100] -->
<string name="screenshot_failed_to_capture_text">Taking screenshots isn\'t allowed by the app or
your organization</string>
@@ -2038,4 +2056,5 @@
<string name="touch_filtered_warning">Because an app is obscuring a permission request, Settings
can’t verify your response.</string>
+
</resources>
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputView.java
index a980413..d63ad08 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputView.java
@@ -280,7 +280,7 @@
@Override
public void showPromptReason(int reason) {
if (reason != PROMPT_REASON_NONE) {
- int promtReasonStringRes = getPromtReasonStringRes(reason);
+ int promtReasonStringRes = getPromptReasonStringRes(reason);
if (promtReasonStringRes != 0) {
mSecurityMessageDisplay.setMessage(promtReasonStringRes);
}
@@ -288,12 +288,12 @@
}
@Override
- public void showMessage(String message, int color) {
+ public void showMessage(CharSequence message, int color) {
mSecurityMessageDisplay.setNextMessageColor(color);
mSecurityMessageDisplay.setMessage(message);
}
- protected abstract int getPromtReasonStringRes(int reason);
+ protected abstract int getPromptReasonStringRes(int reason);
// Cause a VIRTUAL_KEY vibration
public void doHapticKeyClick() {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardHostView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardHostView.java
index 27a3f7d..f1a5ca9 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardHostView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardHostView.java
@@ -34,6 +34,7 @@
import com.android.internal.widget.LockPatternUtils;
import com.android.keyguard.KeyguardSecurityContainer.SecurityCallback;
import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
+import com.android.settingslib.Utils;
import java.io.File;
@@ -171,10 +172,14 @@
mSecurityContainer.showPromptReason(reason);
}
- public void showMessage(String message, int color) {
+ public void showMessage(CharSequence message, int color) {
mSecurityContainer.showMessage(message, color);
}
+ public void showErrorMessage(CharSequence message) {
+ showMessage(message, Utils.getColorError(mContext));
+ }
+
/**
* Dismisses the keyguard by going to the next screen or making it gone.
* @param targetUserId a user that needs to be the foreground user at the dismissal completion.
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java
index b6184a8..ff5f5e7 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java
@@ -117,7 +117,7 @@
}
@Override
- protected int getPromtReasonStringRes(int reason) {
+ protected int getPromptReasonStringRes(int reason) {
switch (reason) {
case PROMPT_REASON_RESTART:
return R.string.kg_prompt_reason_restart_password;
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternView.java
index d636316..cb066a1 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternView.java
@@ -398,7 +398,7 @@
}
@Override
- public void showMessage(String message, int color) {
+ public void showMessage(CharSequence message, int color) {
mSecurityMessageDisplay.setNextMessageColor(color);
mSecurityMessageDisplay.setMessage(message);
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java
index c04ae68..6539ccf 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java
@@ -103,7 +103,7 @@
}
@Override
- protected int getPromtReasonStringRes(int reason) {
+ protected int getPromptReasonStringRes(int reason) {
switch (reason) {
case PROMPT_REASON_RESTART:
return R.string.kg_prompt_reason_restart_pin;
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
index 9f39321..8dc4609 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
@@ -543,8 +543,7 @@
}
}
-
- public void showMessage(String message, int color) {
+ public void showMessage(CharSequence message, int color) {
if (mCurrentSecuritySelection != SecurityMode.None) {
getSecurityView(mCurrentSecuritySelection).showMessage(message, color);
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityView.java
index 8290842..360dba3 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityView.java
@@ -106,7 +106,7 @@
* @param message the message to show
* @param color the color to use
*/
- void showMessage(String message, int color);
+ void showMessage(CharSequence message, int color);
/**
* Instruct the view to show usability hints, if any.
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipper.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipper.java
index 6012c45..a2ff8f7 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipper.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipper.java
@@ -139,7 +139,7 @@
}
@Override
- public void showMessage(String message, int color) {
+ public void showMessage(CharSequence message, int color) {
KeyguardSecurityView ksv = getSecurityView();
if (ksv != null) {
ksv.showMessage(message, color);
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinView.java
index 6e0b56e..e7432ba 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinView.java
@@ -168,7 +168,7 @@
}
@Override
- protected int getPromtReasonStringRes(int reason) {
+ protected int getPromptReasonStringRes(int reason) {
// No message on SIM Pin
return 0;
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukView.java
index 876d170..afee8ec 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukView.java
@@ -211,7 +211,7 @@
}
@Override
- protected int getPromtReasonStringRes(int reason) {
+ protected int getPromptReasonStringRes(int reason) {
// No message on SIM Puk
return 0;
}
diff --git a/packages/SystemUI/src/com/android/keyguard/ViewMediatorCallback.java b/packages/SystemUI/src/com/android/keyguard/ViewMediatorCallback.java
index eff84c6..5c68123 100644
--- a/packages/SystemUI/src/com/android/keyguard/ViewMediatorCallback.java
+++ b/packages/SystemUI/src/com/android/keyguard/ViewMediatorCallback.java
@@ -99,4 +99,10 @@
* Invoked when the secondary display showing a keyguard window changes.
*/
void onSecondaryDisplayShowingChanged(int displayId);
+
+ /**
+ * Consumes a message that was enqueued to be displayed on the next time the bouncer shows up.
+ * @return Message that should be displayed above the challenge.
+ */
+ CharSequence consumeCustomMessage();
}
diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java
index e7e70af..7403ddc 100644
--- a/packages/SystemUI/src/com/android/systemui/Dependency.java
+++ b/packages/SystemUI/src/com/android/systemui/Dependency.java
@@ -40,6 +40,8 @@
import com.android.systemui.plugins.PluginManager;
import com.android.systemui.plugins.PluginManagerImpl;
import com.android.systemui.plugins.VolumeDialogController;
+import com.android.systemui.power.EnhancedEstimates;
+import com.android.systemui.power.EnhancedEstimatesImpl;
import com.android.systemui.power.PowerNotificationWarnings;
import com.android.systemui.power.PowerUI;
import com.android.systemui.statusbar.phone.ConfigurationControllerImpl;
@@ -310,6 +312,8 @@
mProviders.put(OverviewProxyService.class, () -> new OverviewProxyService(mContext));
+ mProviders.put(EnhancedEstimates.class, () -> new EnhancedEstimatesImpl());
+
// Put all dependencies above here so the factory can override them if it wants.
SystemUIFactory.getInstance().injectDependencies(mProviders, mContext);
}
diff --git a/packages/SystemUI/src/com/android/systemui/EmulatedDisplayCutout.java b/packages/SystemUI/src/com/android/systemui/EmulatedDisplayCutout.java
index a102260..f41425a 100644
--- a/packages/SystemUI/src/com/android/systemui/EmulatedDisplayCutout.java
+++ b/packages/SystemUI/src/com/android/systemui/EmulatedDisplayCutout.java
@@ -38,6 +38,9 @@
import android.view.WindowInsets;
import android.view.WindowManager;
+import com.android.systemui.statusbar.policy.ConfigurationController;
+import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
+
import java.util.Collections;
import java.util.List;
@@ -45,18 +48,28 @@
* Emulates a display cutout by drawing its shape in an overlay as supplied by
* {@link DisplayCutout}.
*/
-public class EmulatedDisplayCutout extends SystemUI {
+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);
- mContext.getContentResolver().registerContentObserver(
- Settings.Global.getUriFor(Settings.Global.EMULATE_DISPLAY_CUTOUT),
- false, mObserver, UserHandle.USER_ALL);
- mObserver.onChange(false);
+ 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) {
@@ -94,17 +107,6 @@
return lp;
}
- private ContentObserver mObserver = new ContentObserver(new Handler(Looper.getMainLooper())) {
- @Override
- public void onChange(boolean selfChange) {
- boolean emulateCutout = Settings.Global.getInt(
- mContext.getContentResolver(), Settings.Global.EMULATE_DISPLAY_CUTOUT,
- Settings.Global.EMULATE_DISPLAY_CUTOUT_OFF)
- != Settings.Global.EMULATE_DISPLAY_CUTOUT_OFF;
- setAttached(emulateCutout);
- }
- };
-
private static class CutoutView extends View {
private final Paint mPaint = new Paint();
private final Path mBounds = new Path();
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
index e008148..0f34513 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
@@ -24,10 +24,12 @@
import android.app.WallpaperManager;
import android.app.admin.DevicePolicyManager;
import android.content.BroadcastReceiver;
+import android.content.ComponentName;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.ServiceConnection;
import android.content.pm.UserInfo;
import android.database.ContentObserver;
import android.graphics.Point;
@@ -36,7 +38,9 @@
import android.net.ConnectivityManager;
import android.os.Build;
import android.os.Handler;
+import android.os.IBinder;
import android.os.Message;
+import android.os.Messenger;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemProperties;
@@ -77,6 +81,7 @@
import com.android.internal.telephony.TelephonyIntents;
import com.android.internal.telephony.TelephonyProperties;
import com.android.internal.util.EmergencyAffordanceManager;
+import com.android.internal.util.ScreenshotHelper;
import com.android.internal.widget.LockPatternUtils;
import com.android.systemui.Dependency;
import com.android.systemui.HardwareUiLayout;
@@ -117,6 +122,7 @@
private static final String GLOBAL_ACTION_KEY_ASSIST = "assist";
private static final String GLOBAL_ACTION_KEY_RESTART = "restart";
private static final String GLOBAL_ACTION_KEY_LOGOUT = "logout";
+ private static final String GLOBAL_ACTION_KEY_SCREENSHOT = "screenshot";
private final Context mContext;
private final GlobalActionsManager mWindowManagerFuncs;
@@ -143,6 +149,7 @@
private boolean mHasLogoutButton;
private final boolean mShowSilentToggle;
private final EmergencyAffordanceManager mEmergencyAffordanceManager;
+ private final ScreenshotHelper mScreenshotHelper;
/**
* @param context everything needs a context :(
@@ -183,6 +190,7 @@
R.bool.config_useFixedVolume);
mEmergencyAffordanceManager = new EmergencyAffordanceManager(context);
+ mScreenshotHelper = new ScreenshotHelper(context);
}
/**
@@ -340,6 +348,8 @@
mItems.add(getAssistAction());
} else if (GLOBAL_ACTION_KEY_RESTART.equals(actionKey)) {
mItems.add(new RestartAction());
+ } else if (GLOBAL_ACTION_KEY_SCREENSHOT.equals(actionKey)) {
+ mItems.add(new ScreenshotAction());
} else if (GLOBAL_ACTION_KEY_LOGOUT.equals(actionKey)) {
if (mDevicePolicyManager.isLogoutEnabled()
&& getCurrentUser().id != UserHandle.USER_SYSTEM) {
@@ -458,6 +468,38 @@
}
+ private class ScreenshotAction extends SinglePressAction {
+ public ScreenshotAction() {
+ super(R.drawable.ic_screenshot, R.string.global_action_screenshot);
+ }
+
+ @Override
+ public void onPress() {
+ // Add a little delay before executing, to give the
+ // dialog a chance to go away before it takes a
+ // screenshot.
+ // TODO: instead, omit global action dialog layer
+ mHandler.postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ mScreenshotHelper.takeScreenshot(1, true, true, mHandler);
+ MetricsLogger.action(mContext,
+ MetricsEvent.ACTION_SCREENSHOT_POWER_MENU);
+ }
+ }, 500);
+ }
+
+ @Override
+ public boolean showDuringKeyguard() {
+ return true;
+ }
+
+ @Override
+ public boolean showBeforeProvisioning() {
+ return false;
+ }
+ }
+
private class BugReportAction extends SinglePressAction implements LongPressAction {
public BugReportAction() {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
index 2a5ae0d..22b41a4 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
@@ -96,9 +96,9 @@
}
@Override // Binder interface
- public void dismiss(IKeyguardDismissCallback callback) {
+ public void dismiss(IKeyguardDismissCallback callback, CharSequence message) {
checkPermission();
- mKeyguardViewMediator.dismiss(callback);
+ mKeyguardViewMediator.dismiss(callback, message);
}
@Override // Binder interface
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java
index bd46c5f..e49e80d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java
@@ -103,11 +103,12 @@
@Override
public Slice onBindSlice(Uri sliceUri) {
- ListBuilder builder = new ListBuilder(mSliceUri)
- .addRow(new RowBuilder(mDateUri).setTitle(mLastText));
+ ListBuilder builder = new ListBuilder(getContext(), mSliceUri);
+ builder.addRow(new RowBuilder(builder, mDateUri).setTitle(mLastText));
if (!TextUtils.isEmpty(mNextAlarm)) {
Icon icon = Icon.createWithResource(getContext(), R.drawable.ic_access_alarms_big);
- builder.addRow(new RowBuilder(mAlarmUri).setTitle(mNextAlarm).addEndItem(icon));
+ builder.addRow(new RowBuilder(builder, mAlarmUri)
+ .setTitle(mNextAlarm).addEndItem(icon));
}
return builder.build();
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 91ae448..653e500 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -25,7 +25,6 @@
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_LOCKOUT;
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_TIMEOUT;
-
import android.app.ActivityManager;
import android.app.AlarmManager;
import android.app.NotificationManager;
@@ -344,6 +343,7 @@
private boolean mWakeAndUnlocking;
private IKeyguardDrawnCallback mDrawnCallback;
private boolean mLockWhenSimRemoved;
+ private CharSequence mCustomMessage;
KeyguardUpdateMonitorCallback mUpdateCallback = new KeyguardUpdateMonitorCallback() {
@@ -368,7 +368,7 @@
return;
} else if (info.isGuest() || info.isDemo()) {
// If we just switched to a guest, try to dismiss keyguard.
- dismiss(null /* callback */);
+ dismiss(null /* callback */, null /* message */);
}
}
}
@@ -654,6 +654,13 @@
}
@Override
+ public CharSequence consumeCustomMessage() {
+ final CharSequence message = mCustomMessage;
+ mCustomMessage = null;
+ return message;
+ }
+
+ @Override
public void onSecondaryDisplayShowingChanged(int displayId) {
synchronized (KeyguardViewMediator.this) {
setShowingLocked(mShowing, displayId, false);
@@ -1321,20 +1328,22 @@
/**
* Dismiss the keyguard through the security layers.
* @param callback Callback to be informed about the result
+ * @param message Message that should be displayed on the bouncer.
*/
- private void handleDismiss(IKeyguardDismissCallback callback) {
+ private void handleDismiss(IKeyguardDismissCallback callback, CharSequence message) {
if (mShowing) {
if (callback != null) {
mDismissCallbackRegistry.addCallback(callback);
}
+ mCustomMessage = message;
mStatusBarKeyguardViewManager.dismissAndCollapse();
} else if (callback != null) {
new DismissCallbackWrapper(callback).notifyDismissError();
}
}
- public void dismiss(IKeyguardDismissCallback callback) {
- mHandler.obtainMessage(DISMISS, callback).sendToTarget();
+ public void dismiss(IKeyguardDismissCallback callback, CharSequence message) {
+ mHandler.obtainMessage(DISMISS, new DismissMessage(callback, message)).sendToTarget();
}
/**
@@ -1551,7 +1560,8 @@
}
break;
case DISMISS:
- handleDismiss((IKeyguardDismissCallback) msg.obj);
+ final DismissMessage message = (DismissMessage) msg.obj;
+ handleDismiss(message.getCallback(), message.getMessage());
break;
case START_KEYGUARD_EXIT_ANIM:
Trace.beginSection("KeyguardViewMediator#handleMessage START_KEYGUARD_EXIT_ANIM");
@@ -2161,4 +2171,22 @@
}
}
}
+
+ private static class DismissMessage {
+ private final CharSequence mMessage;
+ private final IKeyguardDismissCallback mCallback;
+
+ DismissMessage(IKeyguardDismissCallback callback, CharSequence message) {
+ mCallback = callback;
+ mMessage = message;
+ }
+
+ public IKeyguardDismissCallback getCallback() {
+ return mCallback;
+ }
+
+ public CharSequence getMessage() {
+ return mMessage;
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/power/EnhancedEstimates.java b/packages/SystemUI/src/com/android/systemui/power/EnhancedEstimates.java
new file mode 100644
index 0000000..8f41a60
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/power/EnhancedEstimates.java
@@ -0,0 +1,8 @@
+package com.android.systemui.power;
+
+public interface EnhancedEstimates {
+
+ boolean isHybridNotificationEnabled();
+
+ Estimate getEstimate();
+}
diff --git a/packages/SystemUI/src/com/android/systemui/power/EnhancedEstimatesImpl.java b/packages/SystemUI/src/com/android/systemui/power/EnhancedEstimatesImpl.java
new file mode 100644
index 0000000..d447542
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/power/EnhancedEstimatesImpl.java
@@ -0,0 +1,16 @@
+package com.android.systemui.power;
+
+import android.util.Log;
+
+public class EnhancedEstimatesImpl implements EnhancedEstimates {
+
+ @Override
+ public boolean isHybridNotificationEnabled() {
+ return false;
+ }
+
+ @Override
+ public Estimate getEstimate() {
+ return null;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/power/Estimate.java b/packages/SystemUI/src/com/android/systemui/power/Estimate.java
new file mode 100644
index 0000000..12a8f0a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/power/Estimate.java
@@ -0,0 +1,11 @@
+package com.android.systemui.power;
+
+public class Estimate {
+ public final long estimateMillis;
+ public final boolean isBasedOnUsage;
+
+ public Estimate(long estimateMillis, boolean isBasedOnUsage) {
+ this.estimateMillis = estimateMillis;
+ this.isBasedOnUsage = isBasedOnUsage;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java b/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java
index c29b362..736286f 100644
--- a/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java
+++ b/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java
@@ -17,40 +17,40 @@
package com.android.systemui.power;
import android.app.Notification;
-import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
-import android.content.ContentResolver;
import android.content.Context;
import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener;
import android.content.DialogInterface.OnDismissListener;
import android.content.Intent;
import android.content.IntentFilter;
+import android.icu.text.MeasureFormat;
+import android.icu.text.MeasureFormat.FormatWidth;
+import android.icu.util.Measure;
+import android.icu.util.MeasureUnit;
import android.media.AudioAttributes;
-import android.net.Uri;
import android.os.AsyncTask;
import android.os.Handler;
import android.os.Looper;
import android.os.PowerManager;
-import android.os.SystemClock;
import android.os.UserHandle;
-import android.provider.Settings;
import android.support.annotation.VisibleForTesting;
+import android.text.format.DateUtils;
import android.util.Slog;
import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
-import com.android.internal.notification.SystemNotificationChannels;
import com.android.settingslib.Utils;
import com.android.systemui.R;
import com.android.systemui.SystemUI;
-import com.android.systemui.statusbar.phone.StatusBar;
import com.android.systemui.statusbar.phone.SystemUIDialog;
import com.android.systemui.util.NotificationChannels;
import java.io.PrintWriter;
import java.text.NumberFormat;
+import java.util.Locale;
+import java.util.concurrent.TimeUnit;
public class PowerNotificationWarnings implements PowerUI.WarningsUI {
private static final String TAG = PowerUI.TAG + ".Notification";
@@ -96,8 +96,9 @@
private long mScreenOffTime;
private int mShowing;
- private long mBucketDroppedNegativeTimeMs;
+ private long mWarningTriggerTimeMs;
+ private Estimate mEstimate;
private boolean mWarning;
private boolean mPlaySound;
private boolean mInvalidCharger;
@@ -130,14 +131,22 @@
public void update(int batteryLevel, int bucket, long screenOffTime) {
mBatteryLevel = batteryLevel;
if (bucket >= 0) {
- mBucketDroppedNegativeTimeMs = 0;
+ mWarningTriggerTimeMs = 0;
} else if (bucket < mBucket) {
- mBucketDroppedNegativeTimeMs = System.currentTimeMillis();
+ mWarningTriggerTimeMs = System.currentTimeMillis();
}
mBucket = bucket;
mScreenOffTime = screenOffTime;
}
+ @Override
+ public void updateEstimate(Estimate estimate) {
+ mEstimate = estimate;
+ if (estimate.estimateMillis <= PowerUI.THREE_HOURS_IN_MILLIS) {
+ mWarningTriggerTimeMs = System.currentTimeMillis();
+ }
+ }
+
private void updateNotification() {
if (DEBUG) Slog.d(TAG, "updateNotification mWarning=" + mWarning + " mPlaySound="
+ mPlaySound + " mInvalidCharger=" + mInvalidCharger);
@@ -171,25 +180,43 @@
mNoMan.notifyAsUser(TAG_BATTERY, SystemMessage.NOTE_BAD_CHARGER, n, UserHandle.ALL);
}
- private void showWarningNotification() {
- final int textRes = R.string.battery_low_percent_format;
+ protected void showWarningNotification() {
final String percentage = NumberFormat.getPercentInstance().format((double) mBatteryLevel / 100.0);
+ // get standard notification copy
+ String title = mContext.getString(R.string.battery_low_title);
+ String contentText = mContext.getString(R.string.battery_low_percent_format, percentage);
+
+ // override notification copy if hybrid notification enabled
+ if (mEstimate != null) {
+ title = mContext.getString(R.string.battery_low_title_hybrid);
+ contentText = mContext.getString(
+ mEstimate.isBasedOnUsage
+ ? R.string.battery_low_percent_format_hybrid
+ : R.string.battery_low_percent_format_hybrid_short,
+ percentage,
+ getTimeRemainingFormatted());
+ }
+
final Notification.Builder nb =
new Notification.Builder(mContext, NotificationChannels.BATTERY)
.setSmallIcon(R.drawable.ic_power_low)
// Bump the notification when the bucket dropped.
- .setWhen(mBucketDroppedNegativeTimeMs)
+ .setWhen(mWarningTriggerTimeMs)
.setShowWhen(false)
- .setContentTitle(mContext.getString(R.string.battery_low_title))
- .setContentText(mContext.getString(textRes, percentage))
+ .setContentTitle(title)
+ .setContentText(contentText)
.setOnlyAlertOnce(true)
.setDeleteIntent(pendingBroadcast(ACTION_DISMISSED_WARNING))
- .setVisibility(Notification.VISIBILITY_PUBLIC)
- .setColor(Utils.getColorAttr(mContext, android.R.attr.colorError));
+ .setVisibility(Notification.VISIBILITY_PUBLIC);
if (hasBatterySettings()) {
nb.setContentIntent(pendingBroadcast(ACTION_SHOW_BATTERY_SETTINGS));
}
+ // Make the notification red if the percentage goes below a certain amount or the time
+ // remaining estimate is disabled
+ if (mEstimate == null || mBucket < 0) {
+ nb.setColor(Utils.getColorAttr(mContext, android.R.attr.colorError));
+ }
nb.addAction(0,
mContext.getString(R.string.battery_saver_start_action),
pendingBroadcast(ACTION_START_SAVER));
@@ -201,6 +228,23 @@
mNoMan.notifyAsUser(TAG_BATTERY, SystemMessage.NOTE_POWER_LOW, n, UserHandle.ALL);
}
+ @VisibleForTesting
+ String getTimeRemainingFormatted() {
+ final Locale currentLocale = mContext.getResources().getConfiguration().getLocales().get(0);
+ MeasureFormat frmt = MeasureFormat.getInstance(currentLocale, FormatWidth.NARROW);
+
+ final long remainder = mEstimate.estimateMillis % DateUtils.HOUR_IN_MILLIS;
+ final long hours = TimeUnit.MILLISECONDS.toHours(
+ mEstimate.estimateMillis - remainder);
+ // round down to the nearest 15 min for now to not appear overly precise
+ final long minutes = TimeUnit.MILLISECONDS.toMinutes(
+ remainder - (remainder % TimeUnit.MINUTES.toMillis(15)));
+ final Measure hoursMeasure = new Measure(hours, MeasureUnit.HOUR);
+ final Measure minutesMeasure = new Measure(minutes, MeasureUnit.MINUTE);
+
+ return frmt.formatMeasures(hoursMeasure, minutesMeasure);
+ }
+
private PendingIntent pendingBroadcast(String action) {
return PendingIntent.getBroadcastAsUser(mContext,
0, new Intent(action), 0, UserHandle.CURRENT);
diff --git a/packages/SystemUI/src/com/android/systemui/power/PowerUI.java b/packages/SystemUI/src/com/android/systemui/power/PowerUI.java
index c1a3623..c5aab60 100644
--- a/packages/SystemUI/src/com/android/systemui/power/PowerUI.java
+++ b/packages/SystemUI/src/com/android/systemui/power/PowerUI.java
@@ -52,6 +52,7 @@
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.Arrays;
+import java.util.concurrent.TimeUnit;
public class PowerUI extends SystemUI {
static final String TAG = "PowerUI";
@@ -59,6 +60,7 @@
private static final long TEMPERATURE_INTERVAL = 30 * DateUtils.SECOND_IN_MILLIS;
private static final long TEMPERATURE_LOGGING_INTERVAL = DateUtils.HOUR_IN_MILLIS;
private static final int MAX_RECENT_TEMPS = 125; // TEMPERATURE_LOGGING_INTERVAL plus a buffer
+ static final long THREE_HOURS_IN_MILLIS = DateUtils.HOUR_IN_MILLIS * 3;
private final Handler mHandler = new Handler();
private final Receiver mReceiver = new Receiver();
@@ -68,9 +70,11 @@
private WarningsUI mWarnings;
private final Configuration mLastConfiguration = new Configuration();
private int mBatteryLevel = 100;
+ private long mTimeRemaining = Long.MAX_VALUE;
private int mBatteryStatus = BatteryManager.BATTERY_STATUS_UNKNOWN;
private int mPlugType = 0;
private int mInvalidCharger = 0;
+ private EnhancedEstimates mEnhancedEstimates;
private int mLowBatteryAlertCloseLevel;
private final int[] mLowBatteryReminderLevels = new int[2];
@@ -83,8 +87,8 @@
private long mNextLogTime;
private IThermalService mThermalService;
- // We create a method reference here so that we are guaranteed that we can remove a callback
// by using the same instance (method references are not guaranteed to be the same object
+ // We create a method reference here so that we are guaranteed that we can remove a callback
// each time they are created).
private final Runnable mUpdateTempCallback = this::updateTemperatureWarning;
@@ -94,6 +98,7 @@
mContext.getSystemService(Context.HARDWARE_PROPERTIES_SERVICE);
mScreenOffTime = mPowerManager.isScreenOn() ? -1 : SystemClock.elapsedRealtime();
mWarnings = Dependency.get(WarningsUI.class);
+ mEnhancedEstimates = Dependency.get(EnhancedEstimates.class);
mLastConfiguration.setTo(mContext.getResources().getConfiguration());
ContentObserver obs = new ContentObserver(mHandler) {
@@ -131,10 +136,15 @@
com.android.internal.R.integer.config_criticalBatteryWarningLevel);
final ContentResolver resolver = mContext.getContentResolver();
- int defWarnLevel = mContext.getResources().getInteger(
+ final int defWarnLevel = mContext.getResources().getInteger(
com.android.internal.R.integer.config_lowBatteryWarningLevel);
- int warnLevel = Settings.Global.getInt(resolver,
+ final int lowPowerModeTriggerLevel = Settings.Global.getInt(resolver,
Settings.Global.LOW_POWER_MODE_TRIGGER_LEVEL, defWarnLevel);
+
+ // NOTE: Keep the logic in sync with BatteryService.
+ // TODO: Propagate this value from BatteryService to system UI, really.
+ int warnLevel = Math.min(defWarnLevel, lowPowerModeTriggerLevel);
+
if (warnLevel == 0) {
warnLevel = defWarnLevel;
}
@@ -231,21 +241,9 @@
return;
}
- boolean isPowerSaver = mPowerManager.isPowerSaveMode();
- if (!plugged
- && !isPowerSaver
- && (bucket < oldBucket || oldPlugged)
- && mBatteryStatus != BatteryManager.BATTERY_STATUS_UNKNOWN
- && bucket < 0) {
+ // Show the correct version of low battery warning if needed
+ maybeShowBatteryWarning(plugged, oldPlugged, oldBucket, bucket);
- // only play SFX when the dialog comes up or the bucket changes
- final boolean playSound = bucket != oldBucket || oldPlugged;
- mWarnings.showLowBatteryWarning(playSound);
- } else if (isPowerSaver || plugged || (bucket > oldBucket && bucket > 0)) {
- mWarnings.dismissLowBatteryWarning();
- } else {
- mWarnings.updateLowBatteryWarning();
- }
} else if (Intent.ACTION_SCREEN_OFF.equals(action)) {
mScreenOffTime = SystemClock.elapsedRealtime();
} else if (Intent.ACTION_SCREEN_ON.equals(action)) {
@@ -256,7 +254,65 @@
Slog.w(TAG, "unknown intent: " + intent);
}
}
- };
+ }
+
+ protected void maybeShowBatteryWarning(boolean plugged, boolean oldPlugged, int oldBucket,
+ int bucket) {
+ boolean isPowerSaver = mPowerManager.isPowerSaveMode();
+ // only play SFX when the dialog comes up or the bucket changes
+ final boolean playSound = bucket != oldBucket || oldPlugged;
+ long oldTimeRemaining = mTimeRemaining;
+ if (mEnhancedEstimates.isHybridNotificationEnabled()) {
+ final Estimate estimate = mEnhancedEstimates.getEstimate();
+ // Turbo is not always booted once SysUI is running so we have ot make sure we actually
+ // get data back
+ if (estimate != null) {
+ mTimeRemaining = estimate.estimateMillis;
+ mWarnings.updateEstimate(estimate);
+ }
+ }
+
+ if (shouldShowLowBatteryWarning(plugged, oldPlugged, oldBucket, bucket, oldTimeRemaining,
+ mTimeRemaining,
+ isPowerSaver, mBatteryStatus)) {
+ mWarnings.showLowBatteryWarning(playSound);
+ } else if (shouldDismissLowBatteryWarning(plugged, oldBucket, bucket, mTimeRemaining,
+ isPowerSaver)) {
+ mWarnings.dismissLowBatteryWarning();
+ } else {
+ mWarnings.updateLowBatteryWarning();
+ }
+ }
+
+ @VisibleForTesting
+ boolean shouldShowLowBatteryWarning(boolean plugged, boolean oldPlugged, int oldBucket,
+ int bucket, long oldTimeRemaining, long timeRemaining,
+ boolean isPowerSaver, int mBatteryStatus) {
+ return !plugged
+ && !isPowerSaver
+ && (((bucket < oldBucket || oldPlugged) && bucket < 0)
+ || (mEnhancedEstimates.isHybridNotificationEnabled()
+ && timeRemaining < THREE_HOURS_IN_MILLIS
+ && isHourLess(oldTimeRemaining, timeRemaining)))
+ && mBatteryStatus != BatteryManager.BATTERY_STATUS_UNKNOWN;
+ }
+
+ private boolean isHourLess(long oldTimeRemaining, long timeRemaining) {
+ final long dif = oldTimeRemaining - timeRemaining;
+ return dif >= TimeUnit.HOURS.toMillis(1);
+ }
+
+ @VisibleForTesting
+ boolean shouldDismissLowBatteryWarning(boolean plugged, int oldBucket, int bucket,
+ long timeRemaining, boolean isPowerSaver) {
+ final boolean hybridWouldDismiss = mEnhancedEstimates.isHybridNotificationEnabled()
+ && timeRemaining > THREE_HOURS_IN_MILLIS;
+ final boolean standardWouldDismiss = (bucket > oldBucket && bucket > 0);
+ return isPowerSaver
+ || plugged
+ || (standardWouldDismiss && (!mEnhancedEstimates.isHybridNotificationEnabled()
+ || hybridWouldDismiss));
+ }
private void initTemperatureWarning() {
ContentResolver resolver = mContext.getContentResolver();
@@ -428,6 +484,7 @@
public interface WarningsUI {
void update(int batteryLevel, int bucket, long screenOffTime);
+ void updateEstimate(Estimate estimate);
void dismissLowBatteryWarning();
void showLowBatteryWarning(boolean playSound);
void dismissInvalidChargerWarning();
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java
index f3bae20..34b8bfe 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java
@@ -67,6 +67,8 @@
case WindowManager.TAKE_SCREENSHOT_SELECTED_REGION:
mScreenshot.takeScreenshotPartial(finisher, msg.arg1 > 0, msg.arg2 > 0);
break;
+ default:
+ Log.d(TAG, "Invalid screenshot option: " + msg.what);
}
}
};
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ButtonDispatcher.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ButtonDispatcher.java
index a83e659..78ee040 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ButtonDispatcher.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ButtonDispatcher.java
@@ -89,6 +89,10 @@
return mAlpha != null ? mAlpha : 1;
}
+ public KeyButtonDrawable getImageDrawable() {
+ return mImageDrawable;
+ }
+
public void setImageDrawable(KeyButtonDrawable drawable) {
mImageDrawable = drawable;
final int N = mViews.size();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
index b71ebfd..699e8cf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
@@ -131,6 +131,10 @@
mRoot.setVisibility(View.VISIBLE);
mKeyguardView.onResume();
showPromptReason(mBouncerPromptReason);
+ final CharSequence customMessage = mCallback.consumeCustomMessage();
+ if (customMessage != null) {
+ mKeyguardView.showErrorMessage(customMessage);
+ }
// We might still be collapsed and the view didn't have time to layout yet or still
// be small, let's wait on the predraw to do the animation in that case.
if (mKeyguardView.getHeight() != 0 && mKeyguardView.getHeight() != mStatusBarHeight) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ManagedProfileControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ManagedProfileControllerImpl.java
index 7f4deb0..0f8d59b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ManagedProfileControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ManagedProfileControllerImpl.java
@@ -61,7 +61,7 @@
public void setWorkModeEnabled(boolean enableWorkMode) {
synchronized (mProfiles) {
for (UserInfo ui : mProfiles) {
- if (!mUserManager.trySetQuietModeEnabled(!enableWorkMode, UserHandle.of(ui.id))) {
+ if (!mUserManager.requestQuietModeEnabled(!enableWorkMode, UserHandle.of(ui.id))) {
StatusBarManager statusBarManager = (StatusBarManager) mContext
.getSystemService(android.app.Service.STATUS_BAR_SERVICE);
statusBarManager.collapsePanels();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
index 695168e..70ec45e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
@@ -24,6 +24,10 @@
import static com.android.systemui.statusbar.phone.StatusBar.dumpBarTransitions;
import android.accessibilityservice.AccessibilityServiceInfo;
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.ActivityManagerNative;
@@ -39,6 +43,7 @@
import android.database.ContentObserver;
import android.graphics.PixelFormat;
import android.graphics.Rect;
+import android.graphics.drawable.AnimatedVectorDrawable;
import android.inputmethodservice.InputMethodService;
import android.os.Binder;
import android.os.Bundle;
@@ -70,17 +75,22 @@
import com.android.internal.util.LatencyTracker;
import com.android.systemui.Dependency;
import com.android.systemui.OverviewProxyService;
+import com.android.systemui.Interpolators;
import com.android.systemui.R;
import com.android.systemui.SysUiServiceProvider;
import com.android.systemui.assist.AssistManager;
import com.android.systemui.fragments.FragmentHostManager;
import com.android.systemui.fragments.FragmentHostManager.FragmentListener;
import com.android.systemui.recents.Recents;
+import com.android.systemui.recents.misc.SysUiTaskStackChangeListener;
+import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.stackdivider.Divider;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.CommandQueue.Callbacks;
import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper;
+import com.android.systemui.statusbar.policy.KeyButtonDrawable;
import com.android.systemui.statusbar.policy.KeyButtonView;
+import com.android.systemui.statusbar.policy.RotationLockController;
import com.android.systemui.statusbar.stack.StackStateAnimator;
import java.io.FileDescriptor;
@@ -101,6 +111,8 @@
/** Allow some time inbetween the long press for back and recents. */
private static final int LOCK_TO_APP_GESTURE_TOLERENCE = 200;
+ private static final int ROTATE_SUGGESTION_TIMEOUT_MS = 4000;
+
protected NavigationBarView mNavigationBarView = null;
protected AssistManager mAssistManager;
@@ -130,6 +142,15 @@
public boolean mHomeBlockedThisTouch;
+ private int mLastRotationSuggestion;
+ private RotationLockController mRotationLockController;
+ private TaskStackListenerImpl mTaskStackListener;
+
+ private final Runnable mRemoveRotationProposal = () -> setRotateSuggestionButtonState(false);
+ private Animator mRotateShowAnimator;
+ private Animator mRotateHideAnimator;
+
+
// ----- Fragment Lifecycle Callbacks -----
@Override
@@ -163,6 +184,12 @@
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
+
+ mRotationLockController = Dependency.get(RotationLockController.class);
+
+ // Register the task stack listener
+ mTaskStackListener = new TaskStackListenerImpl();
+ ActivityManagerWrapper.getInstance().registerTaskStackListener(mTaskStackListener);
}
@Override
@@ -178,6 +205,9 @@
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
+
+ // Unregister the task stack listener
+ ActivityManagerWrapper.getInstance().unregisterTaskStackListener(mTaskStackListener);
}
@Override
@@ -304,6 +334,92 @@
}
}
+ @Override
+ public void onRotationProposal(final int rotation) {
+ // This method will only be called if rotation is valid but will include proposals for the
+ // current system rotation
+ Handler h = getView().getHandler();
+ if (rotation == mWindowManager.getDefaultDisplay().getRotation()) {
+ // Use this as a signal to remove any current suggestions
+ h.removeCallbacks(mRemoveRotationProposal);
+ setRotateSuggestionButtonState(false);
+ } else {
+ mLastRotationSuggestion = rotation; // Remember rotation for click
+ setRotateSuggestionButtonState(true);
+ h.removeCallbacks(mRemoveRotationProposal); // Stop any pending removal
+ h.postDelayed(mRemoveRotationProposal,
+ ROTATE_SUGGESTION_TIMEOUT_MS); // Schedule timeout
+ }
+ }
+
+ public void setRotateSuggestionButtonState(final boolean visible) {
+ setRotateSuggestionButtonState(visible, false);
+ }
+
+ public void setRotateSuggestionButtonState(final boolean visible, final boolean skipAnim) {
+ ButtonDispatcher rotBtn = mNavigationBarView.getRotateSuggestionButton();
+ boolean currentlyVisible = rotBtn.getVisibility() == View.VISIBLE;
+
+ // Rerun a show animation to indicate change but don't rerun a hide animation
+ if (!visible && !currentlyVisible) return;
+
+ View currentView = mNavigationBarView.getRotateSuggestionButton().getCurrentView();
+ if (currentView == null) return;
+
+ KeyButtonDrawable kbd = mNavigationBarView.getRotateSuggestionButton().getImageDrawable();
+ if (kbd == null) return;
+
+ AnimatedVectorDrawable animIcon = (AnimatedVectorDrawable) kbd.getDrawable(0);
+ if (visible) { // Appear and change
+ rotBtn.setVisibility(View.VISIBLE);
+
+ if (skipAnim) {
+ currentView.setAlpha(1f);
+ return;
+ }
+
+ // Start a new animation if running
+ if (mRotateShowAnimator != null) mRotateShowAnimator.pause();
+ if (mRotateHideAnimator != null) mRotateHideAnimator.pause();
+
+ ObjectAnimator appearFade = ObjectAnimator.ofFloat(currentView, "alpha",
+ 0f, 1f);
+ appearFade.setDuration(100);
+ appearFade.setInterpolator(Interpolators.LINEAR);
+ mRotateShowAnimator = appearFade;
+ appearFade.start();
+
+ // Run the rotate icon's animation
+ animIcon.reset();
+ animIcon.start();
+ } else { // Hide
+
+ if (skipAnim) {
+ rotBtn.setVisibility(View.INVISIBLE);
+ return;
+ }
+
+ // Don't start any new hide animations if one is running
+ if (mRotateHideAnimator != null && mRotateHideAnimator.isRunning()) return;
+ // Pause any active show animations but don't reset the AVD to avoid jumps
+ if (mRotateShowAnimator != null) mRotateShowAnimator.pause();
+
+ ObjectAnimator fadeOut = ObjectAnimator.ofFloat(currentView, "alpha",
+ 0f);
+ fadeOut.setDuration(100);
+ fadeOut.setInterpolator(Interpolators.LINEAR);
+ fadeOut.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ rotBtn.setVisibility(View.INVISIBLE);
+ }
+ });
+
+ mRotateHideAnimator = fadeOut;
+ fadeOut.start();
+ }
+ }
+
// Injected from StatusBar at creation.
public void setCurrentSysuiVisibility(int systemUiVisibility) {
mSystemUiVisibility = systemUiVisibility;
@@ -406,6 +522,9 @@
accessibilityButton.setOnClickListener(this::onAccessibilityClick);
accessibilityButton.setOnLongClickListener(this::onAccessibilityLongClick);
updateAccessibilityServicesState(mAccessibilityManager);
+
+ ButtonDispatcher rotateSuggestionButton = mNavigationBarView.getRotateSuggestionButton();
+ rotateSuggestionButton.setOnClickListener(this::onRotateSuggestionClick);
}
private boolean onHomeTouch(View v, MotionEvent event) {
@@ -598,6 +717,10 @@
mNavigationBarView.setAccessibilityButtonState(showAccessibilityButton, targetSelection);
}
+ private void onRotateSuggestionClick(View v) {
+ mRotationLockController.setRotationLockedAtAngle(true, mLastRotationSuggestion);
+ }
+
// ----- Methods that StatusBar talks to (should be minimized) -----
public void setLightBarController(LightBarController lightBarController) {
@@ -646,6 +769,13 @@
private final Stub mRotationWatcher = new Stub() {
@Override
public void onRotationChanged(int rotation) throws RemoteException {
+ // If the screen rotation changes while locked, update lock rotation to flow with
+ // new screen rotation and hide any showing suggestions.
+ if (mRotationLockController.isRotationLocked()) {
+ mRotationLockController.setRotationLockedAtAngle(true, rotation);
+ setRotateSuggestionButtonState(false, true);
+ }
+
// We need this to be scheduled as early as possible to beat the redrawing of
// window in response to the orientation change.
Handler h = getView().getHandler();
@@ -671,6 +801,31 @@
}
};
+ class TaskStackListenerImpl extends SysUiTaskStackChangeListener {
+ // Invalidate any rotation suggestion on task change or activity orientation change
+ // Note: all callbacks happen on main thread
+
+ @Override
+ public void onTaskStackChanged() {
+ setRotateSuggestionButtonState(false);
+ }
+
+ @Override
+ public void onTaskRemoved(int taskId) {
+ setRotateSuggestionButtonState(false);
+ }
+
+ @Override
+ public void onTaskMovedToFront(int taskId) {
+ setRotateSuggestionButtonState(false);
+ }
+
+ @Override
+ public void onActivityRequestedOrientationChanged(int taskId, int requestedOrientation) {
+ setRotateSuggestionButtonState(false);
+ }
+ }
+
public static View create(Context context, FragmentListener listener) {
WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT,
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 4e79314b..b8b309b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarInflaterView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarInflaterView.java
@@ -63,6 +63,7 @@
public static final String RECENT = "recent";
public static final String NAVSPACE = "space";
public static final String CLIPBOARD = "clipboard";
+ public static final String ROTATE = "rotate";
public static final String KEY = "key";
public static final String LEFT = "left";
public static final String RIGHT = "right";
@@ -311,7 +312,7 @@
View v = null;
String button = extractButton(buttonSpec);
if (LEFT.equals(button)) {
- String s = Dependency.get(TunerService.class).getValue(NAV_BAR_LEFT, NAVSPACE);
+ String s = Dependency.get(TunerService.class).getValue(NAV_BAR_LEFT, ROTATE);
button = extractButton(s);
} else if (RIGHT.equals(button)) {
String s = Dependency.get(TunerService.class).getValue(NAV_BAR_RIGHT, MENU_IME);
@@ -334,6 +335,8 @@
v = inflater.inflate(R.layout.nav_key_space, parent, false);
} else if (CLIPBOARD.equals(button)) {
v = inflater.inflate(R.layout.clipboard, parent, false);
+ } else if (ROTATE.equals(button)) {
+ v = inflater.inflate(R.layout.rotate_suggestion, parent, false);
} else if (button.startsWith(KEY)) {
String uri = extractImage(button);
int code = extractKeycode(button);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
index 392581d..006a85b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
@@ -31,6 +31,7 @@
import android.os.Handler;
import android.os.Message;
import android.os.RemoteException;
+import android.support.annotation.ColorInt;
import android.util.AttributeSet;
import android.util.Log;
import android.util.SparseArray;
@@ -56,6 +57,7 @@
import com.android.systemui.plugins.statusbar.phone.NavGesture;
import com.android.systemui.plugins.statusbar.phone.NavGesture.GestureHelper;
import com.android.systemui.stackdivider.Divider;
+import com.android.systemui.statusbar.policy.TintedKeyButtonDrawable;
import com.android.systemui.statusbar.policy.DeadZone;
import com.android.systemui.statusbar.policy.KeyButtonDrawable;
@@ -94,6 +96,7 @@
private KeyButtonDrawable mImeIcon;
private KeyButtonDrawable mMenuIcon;
private KeyButtonDrawable mAccessibilityIcon;
+ private KeyButtonDrawable mRotateSuggestionIcon;
private GestureHelper mGestureHelper;
private DeadZone mDeadZone;
@@ -229,6 +232,9 @@
mButtonDispatchers.put(R.id.ime_switcher, new ButtonDispatcher(R.id.ime_switcher));
mButtonDispatchers.put(R.id.accessibility_button,
new ButtonDispatcher(R.id.accessibility_button));
+ mButtonDispatchers.put(R.id.rotate_suggestion,
+ new ButtonDispatcher(R.id.rotate_suggestion));
+
mOverviewProxyService = Dependency.get(OverviewProxyService.class);
}
@@ -305,6 +311,10 @@
return mButtonDispatchers.get(R.id.accessibility_button);
}
+ public ButtonDispatcher getRotateSuggestionButton() {
+ return mButtonDispatchers.get(R.id.rotate_suggestion);
+ }
+
public SparseArray<ButtonDispatcher> getButtonDispatchers() {
return mButtonDispatchers;
}
@@ -349,6 +359,11 @@
mImeIcon = getDrawable(darkContext, lightContext,
R.drawable.ic_ime_switcher_default, R.drawable.ic_ime_switcher_default);
+ int lightColor = Utils.getColorAttr(lightContext, R.attr.singleToneColor);
+ int darkColor = Utils.getColorAttr(darkContext, R.attr.singleToneColor);
+ mRotateSuggestionIcon = getDrawable(ctx, R.drawable.ic_sysbar_rotate_button,
+ lightColor, darkColor);
+
if (ALTERNATE_CAR_MODE_UI) {
updateCarModeIcons(ctx);
}
@@ -366,6 +381,11 @@
darkContext.getDrawable(darkIcon));
}
+ private KeyButtonDrawable getDrawable(Context ctx, @DrawableRes int icon,
+ @ColorInt int lightColor, @ColorInt int darkColor) {
+ return TintedKeyButtonDrawable.create(ctx.getDrawable(icon), lightColor, darkColor);
+ }
+
@Override
public void setLayoutDirection(int layoutDirection) {
// Reload all the icons
@@ -439,6 +459,8 @@
setAccessibilityButtonState(mShowAccessibilityButton, mLongClickableAccessibilityButton);
getAccessibilityButton().setImageDrawable(mAccessibilityIcon);
+ getRotateSuggestionButton().setImageDrawable(mRotateSuggestionIcon);
+
setDisabledFlags(mDisabledFlags, true);
mBarTransitions.reapplyDarkIntensity();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index 8504d8e..c667309 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -175,13 +175,18 @@
public void dismissWithAction(OnDismissAction r, Runnable cancelAction,
boolean afterKeyguardGone) {
+ dismissWithAction(r, cancelAction, afterKeyguardGone, null /* message */);
+ }
+
+ public void dismissWithAction(OnDismissAction r, Runnable cancelAction,
+ boolean afterKeyguardGone, String message) {
if (mShowing) {
cancelPendingWakeupAction();
// If we're dozing, this needs to be delayed until after we wake up - unless we're
// wake-and-unlocking, because there dozing will last until the end of the transition.
if (mDozing && !isWakeAndUnlocking()) {
mPendingWakeupAction = new DismissWithActionRequest(
- r, cancelAction, afterKeyguardGone);
+ r, cancelAction, afterKeyguardGone, message);
return;
}
@@ -632,7 +637,7 @@
if (request != null) {
if (mShowing) {
dismissWithAction(request.dismissAction, request.cancelAction,
- request.afterKeyguardGone);
+ request.afterKeyguardGone, request.message);
} else if (request.dismissAction != null) {
request.dismissAction.onDismiss();
}
@@ -651,12 +656,14 @@
final OnDismissAction dismissAction;
final Runnable cancelAction;
final boolean afterKeyguardGone;
+ final String message;
DismissWithActionRequest(OnDismissAction dismissAction, Runnable cancelAction,
- boolean afterKeyguardGone) {
+ boolean afterKeyguardGone, String message) {
this.dismissAction = dismissAction;
this.cancelAction = cancelAction;
this.afterKeyguardGone = afterKeyguardGone;
+ this.message = message;
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonDrawable.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonDrawable.java
index 21a96e2..cce9d1c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonDrawable.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonDrawable.java
@@ -39,7 +39,7 @@
}
}
- private KeyButtonDrawable(Drawable[] drawables) {
+ protected KeyButtonDrawable(Drawable[] drawables) {
super(drawables);
for (int i = 0; i < drawables.length; i++) {
setLayerGravity(i, Gravity.CENTER);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/TintedKeyButtonDrawable.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/TintedKeyButtonDrawable.java
new file mode 100644
index 0000000..acf9c00
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/TintedKeyButtonDrawable.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.annotation.ColorInt;
+import android.graphics.drawable.Drawable;
+
+import com.android.internal.graphics.ColorUtils;
+
+/**
+ * Drawable for {@link KeyButtonView}s which contains a single asset and colors for light and dark
+ * navigation bar mode.
+ */
+public class TintedKeyButtonDrawable extends KeyButtonDrawable {
+
+ private final int mLightColor;
+ private final int mDarkColor;
+
+ public static TintedKeyButtonDrawable create(Drawable drawable, @ColorInt int lightColor,
+ @ColorInt int darkColor) {
+ return new TintedKeyButtonDrawable(new Drawable[] { drawable }, lightColor, darkColor);
+ }
+
+ private TintedKeyButtonDrawable(Drawable[] drawables, int lightColor, int darkColor){
+ super(drawables);
+ mLightColor = lightColor;
+ mDarkColor = darkColor;
+ }
+
+ @Override
+ public void setDarkIntensity(float intensity) {
+ // Duplicate intensity scaling from KeyButtonDrawable
+ int intermediateColor = ColorUtils.compositeColors(
+ setAlphaFloat(mDarkColor, intensity),
+ setAlphaFloat(mLightColor,1f - intensity));
+ getDrawable(0).setTint(intermediateColor);
+ invalidateSelf();
+ }
+
+ private int setAlphaFloat(int color, float alpha) {
+ return ColorUtils.setAlphaComponent(color, (int) (alpha * 255f));
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardSliceProviderTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardSliceProviderTest.java
index be28569..cd409d8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardSliceProviderTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardSliceProviderTest.java
@@ -33,8 +33,13 @@
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.util.Arrays;
+
import androidx.app.slice.SliceItem;
+import androidx.app.slice.SliceProvider;
+import androidx.app.slice.SliceSpecs;
import androidx.app.slice.core.SliceQuery;
+import androidx.app.slice.widget.SliceLiveData;
@SmallTest
@RunWith(AndroidTestingRunner.class)
@@ -47,6 +52,7 @@
public void setup() {
mProvider = new TestableKeyguardSliceProvider();
mProvider.attachInfo(getContext(), null);
+ SliceProvider.setSpecs(Arrays.asList(SliceSpecs.LIST));
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/power/PowerNotificationWarningsTest.java b/packages/SystemUI/tests/src/com/android/systemui/power/PowerNotificationWarningsTest.java
index 7f07e0c..3e37cfe 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/power/PowerNotificationWarningsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/power/PowerNotificationWarningsTest.java
@@ -38,6 +38,7 @@
import com.android.systemui.SysuiTestCase;
import com.android.systemui.util.NotificationChannels;
+import java.util.concurrent.TimeUnit;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -46,6 +47,9 @@
@SmallTest
@RunWith(AndroidJUnit4.class)
public class PowerNotificationWarningsTest extends SysuiTestCase {
+
+ public static final String FORMATTED_45M = "0h 45m";
+ public static final String FORMATTED_HOUR = "1h 0m";
private final NotificationManager mMockNotificationManager = mock(NotificationManager.class);
private PowerNotificationWarnings mPowerNotificationWarnings;
@@ -147,4 +151,22 @@
verify(mMockNotificationManager, times(1)).cancelAsUser(anyString(),
eq(SystemMessage.NOTE_THERMAL_SHUTDOWN), any());
}
+
+ @Test
+ public void testGetTimeRemainingFormatted_roundsDownTo15() {
+ mPowerNotificationWarnings.updateEstimate(
+ new Estimate(TimeUnit.MINUTES.toMillis(57), true));
+ String time = mPowerNotificationWarnings.getTimeRemainingFormatted();
+
+ assertTrue("time:" + time + ", expected: " + FORMATTED_45M, time.equals(FORMATTED_45M));
+ }
+
+ @Test
+ public void testGetTimeRemainingFormatted_keepsMinutesWhenZero() {
+ mPowerNotificationWarnings.updateEstimate(
+ new Estimate(TimeUnit.MINUTES.toMillis(65), true));
+ String time = mPowerNotificationWarnings.getTimeRemainingFormatted();
+
+ assertTrue("time:" + time + ", expected: " + FORMATTED_HOUR, time.equals(FORMATTED_HOUR));
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java b/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java
index e4734a4..fdb7f8d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java
@@ -19,12 +19,15 @@
import static android.os.HardwarePropertiesManager.TEMPERATURE_SHUTDOWN;
import static android.provider.Settings.Global.SHOW_TEMPERATURE_WARNING;
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertTrue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.content.Context;
+import android.os.BatteryManager;
import android.os.HardwarePropertiesManager;
import android.provider.Settings;
import android.testing.AndroidTestingRunner;
@@ -37,6 +40,7 @@
import com.android.systemui.power.PowerUI.WarningsUI;
import com.android.systemui.statusbar.phone.StatusBar;
+import java.util.concurrent.TimeUnit;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -46,14 +50,23 @@
@SmallTest
public class PowerUITest extends SysuiTestCase {
+ private static final boolean UNPLUGGED = false;
+ private static final boolean POWER_SAVER_OFF = false;
+ private static final int ABOVE_WARNING_BUCKET = 1;
+ public static final int BELOW_WARNING_BUCKET = -1;
+ public static final long BELOW_HYBRID_THRESHOLD = TimeUnit.HOURS.toMillis(2);
+ public static final long ABOVE_HYBRID_THRESHOLD = TimeUnit.HOURS.toMillis(4);
private HardwarePropertiesManager mHardProps;
private WarningsUI mMockWarnings;
private PowerUI mPowerUI;
+ private EnhancedEstimates mEnhacedEstimates;
@Before
public void setup() {
mMockWarnings = mDependency.injectMockDependency(WarningsUI.class);
+ mEnhacedEstimates = mDependency.injectMockDependency(EnhancedEstimates.class);
mHardProps = mock(HardwarePropertiesManager.class);
+
mContext.putComponent(StatusBar.class, mock(StatusBar.class));
mContext.addMockSystemService(Context.HARDWARE_PROPERTIES_SERVICE, mHardProps);
@@ -128,6 +141,180 @@
verify(mMockWarnings).showHighTemperatureWarning();
}
+ @Test
+ public void testShouldShowLowBatteryWarning_showHybridOnly_returnsShow() {
+ when(mEnhacedEstimates.isHybridNotificationEnabled()).thenReturn(true);
+ mPowerUI.start();
+
+ // unplugged device that would not show the non-hybrid notification but would show the
+ // hybrid
+ boolean shouldShow =
+ mPowerUI.shouldShowLowBatteryWarning(UNPLUGGED, UNPLUGGED, ABOVE_WARNING_BUCKET,
+ ABOVE_WARNING_BUCKET, Long.MAX_VALUE, BELOW_HYBRID_THRESHOLD,
+ POWER_SAVER_OFF, BatteryManager.BATTERY_HEALTH_GOOD);
+ assertTrue(shouldShow);
+ }
+
+ @Test
+ public void testShouldShowLowBatteryWarning_showHybrid_showStandard_returnsShow() {
+ when(mEnhacedEstimates.isHybridNotificationEnabled()).thenReturn(true);
+ mPowerUI.start();
+
+ // unplugged device that would show the non-hybrid notification and the hybrid
+ boolean shouldShow =
+ mPowerUI.shouldShowLowBatteryWarning(UNPLUGGED, UNPLUGGED, ABOVE_WARNING_BUCKET,
+ BELOW_WARNING_BUCKET, Long.MAX_VALUE, BELOW_HYBRID_THRESHOLD,
+ POWER_SAVER_OFF, BatteryManager.BATTERY_HEALTH_GOOD);
+ assertTrue(shouldShow);
+ }
+
+ @Test
+ public void testShouldShowLowBatteryWarning_showStandardOnly_returnsShow() {
+ when(mEnhacedEstimates.isHybridNotificationEnabled()).thenReturn(true);
+ mPowerUI.start();
+
+ // unplugged device that would show the non-hybrid but not the hybrid
+ boolean shouldShow =
+ mPowerUI.shouldShowLowBatteryWarning(UNPLUGGED, UNPLUGGED, ABOVE_WARNING_BUCKET,
+ BELOW_WARNING_BUCKET, Long.MAX_VALUE, ABOVE_HYBRID_THRESHOLD,
+ POWER_SAVER_OFF, BatteryManager.BATTERY_HEALTH_GOOD);
+ assertTrue(shouldShow);
+ }
+
+ @Test
+ public void testShouldShowLowBatteryWarning_deviceHighBattery_returnsNoShow() {
+ when(mEnhacedEstimates.isHybridNotificationEnabled()).thenReturn(true);
+ mPowerUI.start();
+
+ // unplugged device that would show the neither due to battery level being good
+ boolean shouldShow =
+ mPowerUI.shouldShowLowBatteryWarning(UNPLUGGED, UNPLUGGED, ABOVE_WARNING_BUCKET,
+ ABOVE_WARNING_BUCKET, ABOVE_HYBRID_THRESHOLD, ABOVE_HYBRID_THRESHOLD,
+ POWER_SAVER_OFF, BatteryManager.BATTERY_HEALTH_GOOD);
+ assertFalse(shouldShow);
+ }
+
+ @Test
+ public void testShouldShowLowBatteryWarning_devicePlugged_returnsNoShow() {
+ when(mEnhacedEstimates.isHybridNotificationEnabled()).thenReturn(true);
+ mPowerUI.start();
+
+ // plugged device that would show the neither due to being plugged
+ boolean shouldShow =
+ mPowerUI.shouldShowLowBatteryWarning(!UNPLUGGED, UNPLUGGED, ABOVE_WARNING_BUCKET,
+ BELOW_WARNING_BUCKET, ABOVE_HYBRID_THRESHOLD, BELOW_HYBRID_THRESHOLD,
+ POWER_SAVER_OFF, BatteryManager.BATTERY_HEALTH_GOOD);
+ assertFalse(shouldShow);
+ }
+
+ @Test
+ public void testShouldShowLowBatteryWarning_deviceBatteryStatusUnkown_returnsNoShow() {
+ when(mEnhacedEstimates.isHybridNotificationEnabled()).thenReturn(true);
+ mPowerUI.start();
+
+ // Unknown battery status device that would show the neither due
+ boolean shouldShow =
+ mPowerUI.shouldShowLowBatteryWarning(UNPLUGGED, UNPLUGGED, ABOVE_WARNING_BUCKET,
+ BELOW_WARNING_BUCKET, ABOVE_HYBRID_THRESHOLD, BELOW_HYBRID_THRESHOLD,
+ !POWER_SAVER_OFF, BatteryManager.BATTERY_STATUS_UNKNOWN);
+ assertFalse(shouldShow);
+ }
+
+ @Test
+ public void testShouldShowLowBatteryWarning_batterySaverEnabled_returnsNoShow() {
+ when(mEnhacedEstimates.isHybridNotificationEnabled()).thenReturn(true);
+ mPowerUI.start();
+
+ // BatterySaverEnabled device that would show the neither due to battery saver
+ boolean shouldShow =
+ mPowerUI.shouldShowLowBatteryWarning(UNPLUGGED, UNPLUGGED, ABOVE_WARNING_BUCKET,
+ BELOW_WARNING_BUCKET, ABOVE_HYBRID_THRESHOLD, BELOW_HYBRID_THRESHOLD,
+ !POWER_SAVER_OFF, BatteryManager.BATTERY_HEALTH_GOOD);
+ assertFalse(shouldShow);
+ }
+
+ @Test
+ public void testShouldDismissLowBatteryWarning_dismissWhenPowerSaverEnabled() {
+ mPowerUI.start();
+ when(mEnhacedEstimates.isHybridNotificationEnabled()).thenReturn(true);
+ // device that gets power saver turned on should dismiss
+ boolean shouldDismiss =
+ mPowerUI.shouldDismissLowBatteryWarning(UNPLUGGED, BELOW_WARNING_BUCKET,
+ BELOW_WARNING_BUCKET, ABOVE_HYBRID_THRESHOLD, !POWER_SAVER_OFF);
+ assertTrue(shouldDismiss);
+ }
+
+ @Test
+ public void testShouldDismissLowBatteryWarning_dismissWhenPlugged() {
+ mPowerUI.start();
+ when(mEnhacedEstimates.isHybridNotificationEnabled()).thenReturn(true);
+
+ // device that gets plugged in should dismiss
+ boolean shouldDismiss =
+ mPowerUI.shouldDismissLowBatteryWarning(!UNPLUGGED, BELOW_WARNING_BUCKET,
+ BELOW_WARNING_BUCKET, ABOVE_HYBRID_THRESHOLD, POWER_SAVER_OFF);
+ assertTrue(shouldDismiss);
+ }
+
+ @Test
+ public void testShouldDismissLowBatteryWarning_dismissHybridSignal_showStandardSignal_shouldShow() {
+ mPowerUI.start();
+ when(mEnhacedEstimates.isHybridNotificationEnabled()).thenReturn(true);
+ // would dismiss hybrid but not non-hybrid should not dismiss
+ boolean shouldDismiss =
+ mPowerUI.shouldDismissLowBatteryWarning(UNPLUGGED, BELOW_WARNING_BUCKET,
+ BELOW_WARNING_BUCKET, ABOVE_HYBRID_THRESHOLD, POWER_SAVER_OFF);
+ assertFalse(shouldDismiss);
+ }
+
+ @Test
+ public void testShouldDismissLowBatteryWarning_showHybridSignal_dismissStandardSignal_shouldShow() {
+ mPowerUI.start();
+ when(mEnhacedEstimates.isHybridNotificationEnabled()).thenReturn(true);
+
+ // would dismiss non-hybrid but not hybrid should not dismiss
+ boolean shouldDismiss =
+ mPowerUI.shouldDismissLowBatteryWarning(UNPLUGGED, BELOW_WARNING_BUCKET,
+ ABOVE_WARNING_BUCKET, BELOW_HYBRID_THRESHOLD, POWER_SAVER_OFF);
+ assertFalse(shouldDismiss);
+ }
+
+ @Test
+ public void testShouldDismissLowBatteryWarning_showBothSignal_shouldShow() {
+ mPowerUI.start();
+ when(mEnhacedEstimates.isHybridNotificationEnabled()).thenReturn(true);
+
+ // should not dismiss when both would not dismiss
+ boolean shouldDismiss =
+ mPowerUI.shouldDismissLowBatteryWarning(UNPLUGGED, BELOW_WARNING_BUCKET,
+ BELOW_WARNING_BUCKET, BELOW_HYBRID_THRESHOLD, POWER_SAVER_OFF);
+ assertFalse(shouldDismiss);
+ }
+
+ @Test
+ public void testShouldDismissLowBatteryWarning_dismissBothSignal_shouldDismiss() {
+ mPowerUI.start();
+ when(mEnhacedEstimates.isHybridNotificationEnabled()).thenReturn(true);
+
+ //should dismiss if both would dismiss
+ boolean shouldDismiss =
+ mPowerUI.shouldDismissLowBatteryWarning(UNPLUGGED, BELOW_WARNING_BUCKET,
+ ABOVE_WARNING_BUCKET, ABOVE_HYBRID_THRESHOLD, POWER_SAVER_OFF);
+ assertTrue(shouldDismiss);
+ }
+
+ @Test
+ public void testShouldDismissLowBatteryWarning_dismissStandardSignal_hybridDisabled_shouldDismiss() {
+ mPowerUI.start();
+ when(mEnhacedEstimates.isHybridNotificationEnabled()).thenReturn(false);
+
+ // would dismiss non-hybrid with hybrid disabled should dismiss
+ boolean shouldDismiss =
+ mPowerUI.shouldDismissLowBatteryWarning(UNPLUGGED, BELOW_WARNING_BUCKET,
+ ABOVE_WARNING_BUCKET, ABOVE_HYBRID_THRESHOLD, POWER_SAVER_OFF);
+ assertTrue(shouldDismiss);
+ }
+
private void setCurrentTemp(float temp) {
when(mHardProps.getDeviceTemperatures(DEVICE_TEMPERATURE_SKIN, TEMPERATURE_CURRENT))
.thenReturn(new float[] { temp });
diff --git a/packages/overlays/DisplayCutoutEmulationOverlay/Android.mk b/packages/overlays/DisplayCutoutEmulationOverlay/Android.mk
new file mode 100644
index 0000000..f4205ad6
--- /dev/null
+++ b/packages/overlays/DisplayCutoutEmulationOverlay/Android.mk
@@ -0,0 +1,13 @@
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_RRO_THEME := DisplayCutoutEmulation
+LOCAL_CERTIFICATE := platform
+
+LOCAL_SRC_FILES := $(call all-subdir-java-files)
+
+LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
+
+LOCAL_PACKAGE_NAME := DisplayCutoutEmulationOverlay
+
+include $(BUILD_RRO_PACKAGE)
diff --git a/packages/overlays/DisplayCutoutEmulationOverlay/AndroidManifest.xml b/packages/overlays/DisplayCutoutEmulationOverlay/AndroidManifest.xml
new file mode 100644
index 0000000..dd43690
--- /dev/null
+++ b/packages/overlays/DisplayCutoutEmulationOverlay/AndroidManifest.xml
@@ -0,0 +1,8 @@
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.internal.display.cutout.emulation"
+ android:versionCode="1"
+ android:versionName="1.0">
+ <overlay android:targetPackage="android" android:priority="1"/>
+
+ <application android:label="@string/display_cutout_emulation_overlay" android:hasCode="false"/>
+</manifest>
diff --git a/packages/overlays/DisplayCutoutEmulationOverlay/res/values/config.xml b/packages/overlays/DisplayCutoutEmulationOverlay/res/values/config.xml
new file mode 100644
index 0000000..88c19c7
--- /dev/null
+++ b/packages/overlays/DisplayCutoutEmulationOverlay/res/values/config.xml
@@ -0,0 +1,44 @@
+<!--
+ ~ Copyright (C) 2018 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+
+ <!-- The bounding path of the cutout region of the main built-in display.
+ Must either be empty if there is no cutout region, or a string that is parsable by
+ {@link android.util.PathParser}. -->
+ <string translatable="false" name="config_mainBuiltInDisplayCutout">
+ M 687.0,0
+ l -66,50
+ l 0,50
+ l 66,50
+ l 66,0
+ l 66,-50
+ l 0,-50
+ l -66,-50
+ z
+ </string>
+
+ <!-- Whether the display cutout region of the main built-in display should be forced to
+ black in software (to avoid aliasing or emulate a cutout that is not physically existent).
+ -->
+ <bool name="config_fillMainBuiltInDisplayCutout">true</bool>
+
+ <!-- Height of the status bar -->
+ <dimen name="status_bar_height">150px</dimen>
+
+</resources>
+
+
diff --git a/core/java/android/security/recoverablekeystore/KeyStoreRecoveryData.aidl b/packages/overlays/DisplayCutoutEmulationOverlay/res/values/strings.xml
similarity index 60%
copy from core/java/android/security/recoverablekeystore/KeyStoreRecoveryData.aidl
copy to packages/overlays/DisplayCutoutEmulationOverlay/res/values/strings.xml
index bd76051..5d5c425 100644
--- a/core/java/android/security/recoverablekeystore/KeyStoreRecoveryData.aidl
+++ b/packages/overlays/DisplayCutoutEmulationOverlay/res/values/strings.xml
@@ -1,11 +1,13 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/**
+ * 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
+ * http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
@@ -13,8 +15,10 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+-->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-package android.security.recoverablekeystore;
+ <string name="display_cutout_emulation_overlay">Display Cutout Emulation</string>
-/* @hide */
-parcelable KeyStoreRecoveryData;
+</resources>
+
diff --git a/proto/src/metrics_constants.proto b/proto/src/metrics_constants.proto
index 4f04d36..04cee67 100644
--- a/proto/src/metrics_constants.proto
+++ b/proto/src/metrics_constants.proto
@@ -5129,6 +5129,21 @@
// OS: P
FUELGAUGE_SMART_BATTERY = 1281;
+ // ACTION: User tapped Screenshot in the power menu.
+ // CATEGORY: GLOBAL_SYSTEM_UI
+ // OS: P
+ ACTION_SCREENSHOT_POWER_MENU = 1282;
+
+ // OPEN: Settings > Apps & Notifications -> Special app access -> Storage Access
+ // CATEGORY: SETTINGS
+ // OS: P
+ STORAGE_ACCESS = 1283;
+
+ // OPEN: Settings > Apps & Notifications -> Special app access -> Storage Access -> Package
+ // CATEGORY: SETTINGS
+ // OS: P
+ APPLICATIONS_STORAGE_DETAIL = 1284;
+
// ---- 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/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index ba8ce59..fc6058c 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -1280,7 +1280,7 @@
}
int servicePackageUid = serviceInfo.applicationInfo.uid;
- if (mAppOpsManager.noteOpNoThrow(AppOpsManager.OP_BIND_ACCESSIBILITY_SERVICE,
+ if (mAppOpsManager.noteOpNoThrow(AppOpsManager.OPSTR_BIND_ACCESSIBILITY_SERVICE,
servicePackageUid, serviceInfo.packageName) != AppOpsManager.MODE_ALLOWED) {
Slog.w(LOG_TAG, "Skipping accessibility service " + new ComponentName(
serviceInfo.packageName, serviceInfo.name).flattenToShortString()
@@ -1362,14 +1362,13 @@
private int computeRelevantEventTypes(UserState userState, Client client) {
int relevantEventTypes = 0;
- int numBoundServices = userState.mBoundServices.size();
- for (int i = 0; i < numBoundServices; i++) {
- AccessibilityServiceConnection service =
- userState.mBoundServices.get(i);
+ // Use iterator for thread-safety
+ for (AccessibilityServiceConnection service : userState.mBoundServices) {
relevantEventTypes |= isClientInPackageWhitelist(service.getServiceInfo(), client)
? service.getRelevantEventTypes()
: 0;
}
+
relevantEventTypes |= isClientInPackageWhitelist(
mUiAutomationManager.getServiceInfo(), client)
? mUiAutomationManager.getRelevantEventTypes()
diff --git a/services/accessibility/java/com/android/server/accessibility/MagnificationGestureHandler.java b/services/accessibility/java/com/android/server/accessibility/MagnificationGestureHandler.java
index 74d2ddd..0a03b7f 100644
--- a/services/accessibility/java/com/android/server/accessibility/MagnificationGestureHandler.java
+++ b/services/accessibility/java/com/android/server/accessibility/MagnificationGestureHandler.java
@@ -17,6 +17,7 @@
package com.android.server.accessibility;
import static android.view.InputDevice.SOURCE_TOUCHSCREEN;
+import static android.view.MotionEvent.ACTION_CANCEL;
import static android.view.MotionEvent.ACTION_DOWN;
import static android.view.MotionEvent.ACTION_MOVE;
import static android.view.MotionEvent.ACTION_POINTER_DOWN;
@@ -364,7 +365,7 @@
persistScaleAndTransitionTo(mViewportDraggingState);
- } else if (action == ACTION_UP) {
+ } else if (action == ACTION_UP || action == ACTION_CANCEL) {
persistScaleAndTransitionTo(mDetectingState);
@@ -496,7 +497,9 @@
}
}
break;
- case ACTION_UP: {
+
+ case ACTION_UP:
+ case ACTION_CANCEL: {
if (!mZoomedInBeforeDrag) zoomOff();
clear();
transitionTo(mDetectingState);
@@ -533,12 +536,15 @@
@Override
public void onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
- if (event.getActionMasked() == ACTION_UP) {
- transitionTo(mDetectingState);
- }
+ switch (event.getActionMasked()) {
+ case ACTION_UP:
+ case ACTION_CANCEL: {
+ transitionTo(mDetectingState);
+ } break;
- if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
- mLastDelegatedDownEventTime = event.getDownTime();
+ case ACTION_DOWN: {
+ mLastDelegatedDownEventTime = event.getDownTime();
+ } break;
}
if (getNext() != null) {
// We cache some events to see if the user wants to trigger magnification.
diff --git a/services/backup/java/com/android/server/backup/RefactoredBackupManagerService.java b/services/backup/java/com/android/server/backup/RefactoredBackupManagerService.java
index 03591a8..350d7af 100644
--- a/services/backup/java/com/android/server/backup/RefactoredBackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/RefactoredBackupManagerService.java
@@ -207,6 +207,10 @@
public static final String BACKUP_FINISHED_ACTION = "android.intent.action.BACKUP_FINISHED";
public static final String BACKUP_FINISHED_PACKAGE_EXTRA = "packageName";
+ // Time delay for initialization operations that can be delayed so as not to consume too much CPU
+ // on bring-up and increase time-to-UI.
+ private static final long INITIALIZATION_DELAY_MILLIS = 3000;
+
// Timeout interval for deciding that a bind or clear-data has taken too long
private static final long TIMEOUT_INTERVAL = 10 * 1000;
@@ -282,6 +286,9 @@
private final BackupPasswordManager mBackupPasswordManager;
+ // Time when we post the transport registration operation
+ private final long mRegisterTransportsRequestedTime;
+
@GuardedBy("mPendingRestores")
private boolean mIsRestoreInProgress;
@GuardedBy("mPendingRestores")
@@ -735,6 +742,9 @@
// Set up our transport options and initialize the default transport
SystemConfig systemConfig = SystemConfig.getInstance();
Set<ComponentName> transportWhitelist = systemConfig.getBackupTransportWhitelist();
+ if (transportWhitelist == null) {
+ transportWhitelist = Collections.emptySet();
+ }
String transport =
Settings.Secure.getString(
@@ -749,8 +759,7 @@
new TransportManager(
context,
transportWhitelist,
- transport,
- backupThread.getLooper());
+ transport);
// If encrypted file systems is enabled or disabled, this call will return the
// correct directory.
@@ -852,12 +861,14 @@
}
mTransportManager = transportManager;
- mTransportManager.setTransportBoundListener(mTransportBoundListener);
- mTransportManager.registerAllTransports();
+ mTransportManager.setOnTransportRegisteredListener(this::onTransportRegistered);
+ mRegisterTransportsRequestedTime = SystemClock.elapsedRealtime();
+ mBackupHandler.postDelayed(
+ mTransportManager::registerTransports, INITIALIZATION_DELAY_MILLIS);
- // Now that we know about valid backup participants, parse any
- // leftover journal files into the pending backup set
- mBackupHandler.post(this::parseLeftoverJournals);
+ // Now that we know about valid backup participants, parse any leftover journal files into
+ // the pending backup set
+ mBackupHandler.postDelayed(this::parseLeftoverJournals, INITIALIZATION_DELAY_MILLIS);
// Power management
mWakelock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "*backup*");
@@ -1151,39 +1162,28 @@
}
}
- private TransportManager.TransportBoundListener mTransportBoundListener =
- new TransportManager.TransportBoundListener() {
- @Override
- public boolean onTransportBound(IBackupTransport transport) {
- // If the init sentinel file exists, we need to be sure to perform the init
- // as soon as practical. We also create the state directory at registration
- // time to ensure it's present from the outset.
- String name = null;
- try {
- name = transport.name();
- String transportDirName = transport.transportDirName();
- File stateDir = new File(mBaseStateDir, transportDirName);
- stateDir.mkdirs();
+ private void onTransportRegistered(String transportName, String transportDirName) {
+ if (DEBUG) {
+ long timeMs = SystemClock.elapsedRealtime() - mRegisterTransportsRequestedTime;
+ Slog.d(TAG, "Transport " + transportName + " registered " + timeMs
+ + "ms after first request (delay = " + INITIALIZATION_DELAY_MILLIS + "ms)");
+ }
- File initSentinel = new File(stateDir, INIT_SENTINEL_FILE_NAME);
- if (initSentinel.exists()) {
- synchronized (mQueueLock) {
- mPendingInits.add(name);
+ File stateDir = new File(mBaseStateDir, transportDirName);
+ stateDir.mkdirs();
- // TODO: pick a better starting time than now + 1 minute
- long delay = 1000 * 60; // one minute, in milliseconds
- mAlarmManager.set(AlarmManager.RTC_WAKEUP,
- System.currentTimeMillis() + delay, mRunInitIntent);
- }
- }
- return true;
- } catch (Exception e) {
- // the transport threw when asked its file naming prefs; declare it invalid
- Slog.w(TAG, "Failed to regiser transport: " + name);
- return false;
- }
- }
- };
+ File initSentinel = new File(stateDir, INIT_SENTINEL_FILE_NAME);
+ if (initSentinel.exists()) {
+ synchronized (mQueueLock) {
+ mPendingInits.add(transportName);
+
+ // TODO: pick a better starting time than now + 1 minute
+ long delay = 1000 * 60; // one minute, in milliseconds
+ mAlarmManager.set(AlarmManager.RTC_WAKEUP,
+ System.currentTimeMillis() + delay, mRunInitIntent);
+ }
+ }
+ }
// ----- Track installation/removal of packages -----
private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
@@ -2891,14 +2891,14 @@
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP,
"listAllTransports");
- return mTransportManager.getBoundTransportNames();
+ return mTransportManager.getRegisteredTransportNames();
}
@Override
public ComponentName[] listAllTransportComponents() {
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP,
"listAllTransportComponents");
- return mTransportManager.getAllTransportComponents();
+ return mTransportManager.getRegisteredTransportComponents();
}
@Override
@@ -3003,6 +3003,8 @@
/** Selects transport {@code transportName} and returns previous selected transport. */
@Override
+ @Deprecated
+ @Nullable
public String selectBackupTransport(String transportName) {
mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.BACKUP, "selectBackupTransport");
diff --git a/services/backup/java/com/android/server/backup/TransportManager.java b/services/backup/java/com/android/server/backup/TransportManager.java
index 34b8935..09456b68 100644
--- a/services/backup/java/com/android/server/backup/TransportManager.java
+++ b/services/backup/java/com/android/server/backup/TransportManager.java
@@ -16,93 +16,56 @@
package com.android.server.backup;
-import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;
-
import android.annotation.Nullable;
+import android.annotation.WorkerThread;
import android.app.backup.BackupManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
-import android.content.ServiceConnection;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
-import android.content.pm.ServiceInfo;
-import android.os.Handler;
-import android.os.IBinder;
-import android.os.Looper;
-import android.os.Message;
import android.os.RemoteException;
import android.os.UserHandle;
-import android.provider.Settings;
import android.util.ArrayMap;
-import android.util.ArraySet;
import android.util.EventLog;
-import android.util.Log;
import android.util.Slog;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.backup.IBackupTransport;
+import com.android.internal.util.Preconditions;
import com.android.server.EventLogTags;
+import com.android.server.backup.transport.OnTransportRegisteredListener;
import com.android.server.backup.transport.TransportClient;
import com.android.server.backup.transport.TransportClientManager;
import com.android.server.backup.transport.TransportConnectionListener;
import com.android.server.backup.transport.TransportNotAvailableException;
import com.android.server.backup.transport.TransportNotRegisteredException;
-import java.util.ArrayList;
-import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Consumer;
import java.util.function.Predicate;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
-/**
- * Handles in-memory bookkeeping of all BackupTransport objects.
- */
+/** Handles in-memory bookkeeping of all BackupTransport objects. */
public class TransportManager {
-
private static final String TAG = "BackupTransportManager";
@VisibleForTesting
public static final String SERVICE_ACTION_TRANSPORT_HOST = "android.backup.TRANSPORT_HOST";
- private static final long REBINDING_TIMEOUT_UNPROVISIONED_MS = 30 * 1000; // 30 sec
- private static final long REBINDING_TIMEOUT_PROVISIONED_MS = 5 * 60 * 1000; // 5 mins
- private static final int REBINDING_TIMEOUT_MSG = 1;
-
private final Intent mTransportServiceIntent = new Intent(SERVICE_ACTION_TRANSPORT_HOST);
private final Context mContext;
private final PackageManager mPackageManager;
private final Set<ComponentName> mTransportWhitelist;
- private final Handler mHandler;
private final TransportClientManager mTransportClientManager;
-
- /**
- * This listener is called after we bind to any transport. If it returns true, this is a valid
- * transport.
- */
- private TransportBoundListener mTransportBoundListener;
-
private final Object mTransportLock = new Object();
-
- /**
- * We have detected these transports on the device. Unless in exceptional cases, we are also
- * bound to all of these.
- */
- @GuardedBy("mTransportLock")
- private final Map<ComponentName, TransportConnection> mValidTransports = new ArrayMap<>();
-
- /** We are currently bound to these transports. */
- @GuardedBy("mTransportLock")
- private final Map<String, ComponentName> mBoundTransports = new ArrayMap<>();
-
- /** @see #getEligibleTransportComponents() */
- @GuardedBy("mTransportLock")
- private final Set<ComponentName> mEligibleTransports = new ArraySet<>();
+ private OnTransportRegisteredListener mOnTransportRegisteredListener = (c, n) -> {};
/** @see #getRegisteredTransportNames() */
@GuardedBy("mTransportLock")
@@ -110,120 +73,98 @@
new ArrayMap<>();
@GuardedBy("mTransportLock")
+ @Nullable
private volatile String mCurrentTransportName;
- TransportManager(
- Context context,
- Set<ComponentName> whitelist,
- String defaultTransport,
- TransportBoundListener listener,
- Looper looper) {
- this(context, whitelist, defaultTransport, looper);
- mTransportBoundListener = listener;
+ TransportManager(Context context, Set<ComponentName> whitelist, String selectedTransport) {
+ this(context, whitelist, selectedTransport, new TransportClientManager(context));
}
+ @VisibleForTesting
TransportManager(
Context context,
Set<ComponentName> whitelist,
- String defaultTransport,
- Looper looper) {
+ String selectedTransport,
+ TransportClientManager transportClientManager) {
mContext = context;
mPackageManager = context.getPackageManager();
- if (whitelist != null) {
- mTransportWhitelist = whitelist;
- } else {
- mTransportWhitelist = new ArraySet<>();
- }
- mCurrentTransportName = defaultTransport;
- mHandler = new RebindOnTimeoutHandler(looper);
- mTransportClientManager = new TransportClientManager(context);
+ mTransportWhitelist = Preconditions.checkNotNull(whitelist);
+ mCurrentTransportName = selectedTransport;
+ mTransportClientManager = transportClientManager;
}
- public void setTransportBoundListener(TransportBoundListener transportBoundListener) {
- mTransportBoundListener = transportBoundListener;
+ /* Sets a listener to be called whenever a transport is registered. */
+ public void setOnTransportRegisteredListener(OnTransportRegisteredListener listener) {
+ mOnTransportRegisteredListener = listener;
}
+ @WorkerThread
void onPackageAdded(String packageName) {
- // New package added. Bind to all transports it contains.
- synchronized (mTransportLock) {
- log_verbose("Package added. Binding to all transports. " + packageName);
- bindToAllInternal(packageName, null /* all components */);
- }
+ registerTransportsFromPackage(packageName, transportComponent -> true);
}
void onPackageRemoved(String packageName) {
- // Package removed. Remove all its transports from our list. These transports have already
- // been removed from mBoundTransports because onServiceDisconnected would already been
- // called on TransportConnection objects.
synchronized (mTransportLock) {
- Iterator<Map.Entry<ComponentName, TransportConnection>> iter =
- mValidTransports.entrySet().iterator();
- while (iter.hasNext()) {
- Map.Entry<ComponentName, TransportConnection> validTransport = iter.next();
- ComponentName componentName = validTransport.getKey();
- if (componentName.getPackageName().equals(packageName)) {
- TransportConnection transportConnection = validTransport.getValue();
- iter.remove();
- if (transportConnection != null) {
- mContext.unbindService(transportConnection);
- log_verbose("Package removed, removing transport: "
- + componentName.flattenToShortString());
- }
- }
- }
- removeTransportsIfLocked(
- componentName -> packageName.equals(componentName.getPackageName()));
+ mRegisteredTransportsDescriptionMap.keySet().removeIf(fromPackageFilter(packageName));
}
}
- void onPackageChanged(String packageName, String[] components) {
+ @WorkerThread
+ void onPackageChanged(String packageName, String... components) {
synchronized (mTransportLock) {
- // Remove all changed components from mValidTransports. We'll bind to them again
- // and re-add them if still valid.
- Set<ComponentName> transportsToBeRemoved = new ArraySet<>();
- for (String component : components) {
- ComponentName componentName = new ComponentName(packageName, component);
- transportsToBeRemoved.add(componentName);
- TransportConnection removed = mValidTransports.remove(componentName);
- if (removed != null) {
- mContext.unbindService(removed);
- log_verbose("Package changed. Removing transport: " +
- componentName.flattenToShortString());
- }
- }
- removeTransportsIfLocked(transportsToBeRemoved::contains);
- bindToAllInternal(packageName, components);
+ Set<ComponentName> transportComponents =
+ Stream.of(components)
+ .map(component -> new ComponentName(packageName, component))
+ .collect(Collectors.toSet());
+
+ mRegisteredTransportsDescriptionMap.keySet().removeIf(transportComponents::contains);
+ registerTransportsFromPackage(packageName, transportComponents::contains);
}
}
- @GuardedBy("mTransportLock")
- private void removeTransportsIfLocked(Predicate<ComponentName> filter) {
- mEligibleTransports.removeIf(filter);
- mRegisteredTransportsDescriptionMap.keySet().removeIf(filter);
- }
-
- public IBackupTransport getTransportBinder(String transportName) {
+ /**
+ * Returns the {@link ComponentName}s of the registered transports.
+ *
+ * <p>A *registered* transport is a transport that satisfies intent with action
+ * android.backup.TRANSPORT_HOST, returns true for {@link #isTransportTrusted(ComponentName)}
+ * and that we have successfully connected to once.
+ */
+ ComponentName[] getRegisteredTransportComponents() {
synchronized (mTransportLock) {
- ComponentName component = mBoundTransports.get(transportName);
- if (component == null) {
- Slog.w(TAG, "Transport " + transportName + " not bound.");
- return null;
- }
- TransportConnection conn = mValidTransports.get(component);
- if (conn == null) {
- Slog.w(TAG, "Transport " + transportName + " not valid.");
- return null;
- }
- return conn.getBinder();
+ return mRegisteredTransportsDescriptionMap
+ .keySet()
+ .toArray(new ComponentName[mRegisteredTransportsDescriptionMap.size()]);
}
}
- public IBackupTransport getCurrentTransportBinder() {
- return getTransportBinder(mCurrentTransportName);
+ /**
+ * Returns the names of the registered transports.
+ *
+ * @see #getRegisteredTransportComponents()
+ */
+ String[] getRegisteredTransportNames() {
+ synchronized (mTransportLock) {
+ return mRegisteredTransportsDescriptionMap
+ .values()
+ .stream()
+ .map(transportDescription -> transportDescription.name)
+ .toArray(String[]::new);
+ }
+ }
+
+ /** Returns a set with the whitelisted transports. */
+ Set<ComponentName> getTransportWhitelist() {
+ return mTransportWhitelist;
+ }
+
+ @Nullable
+ String getCurrentTransportName() {
+ return mCurrentTransportName;
}
/**
* Returns the transport name associated with {@code transportComponent}.
+ *
* @throws TransportNotRegisteredException if the transport is not registered.
*/
public String getTransportName(ComponentName transportComponent)
@@ -234,7 +175,32 @@
}
/**
- * Retrieve the configuration intent of {@code transportName}.
+ * Retrieves the transport dir name of {@code transportComponent}.
+ *
+ * @throws TransportNotRegisteredException if the transport is not registered.
+ */
+ public String getTransportDirName(ComponentName transportComponent)
+ throws TransportNotRegisteredException {
+ synchronized (mTransportLock) {
+ return getRegisteredTransportDescriptionOrThrowLocked(transportComponent)
+ .transportDirName;
+ }
+ }
+
+ /**
+ * Retrieves the transport dir name of {@code transportName}.
+ *
+ * @throws TransportNotRegisteredException if the transport is not registered.
+ */
+ public String getTransportDirName(String transportName) throws TransportNotRegisteredException {
+ synchronized (mTransportLock) {
+ return getRegisteredTransportDescriptionOrThrowLocked(transportName).transportDirName;
+ }
+ }
+
+ /**
+ * Retrieves the configuration intent of {@code transportName}.
+ *
* @throws TransportNotRegisteredException if the transport is not registered.
*/
@Nullable
@@ -247,7 +213,21 @@
}
/**
- * Retrieve the data management intent of {@code transportName}.
+ * Retrieves the current destination string of {@code transportName}.
+ *
+ * @throws TransportNotRegisteredException if the transport is not registered.
+ */
+ public String getTransportCurrentDestinationString(String transportName)
+ throws TransportNotRegisteredException {
+ synchronized (mTransportLock) {
+ return getRegisteredTransportDescriptionOrThrowLocked(transportName)
+ .currentDestinationString;
+ }
+ }
+
+ /**
+ * Retrieves the data management intent of {@code transportName}.
+ *
* @throws TransportNotRegisteredException if the transport is not registered.
*/
@Nullable
@@ -260,19 +240,8 @@
}
/**
- * Retrieve the current destination string of {@code transportName}.
- * @throws TransportNotRegisteredException if the transport is not registered.
- */
- public String getTransportCurrentDestinationString(String transportName)
- throws TransportNotRegisteredException {
- synchronized (mTransportLock) {
- return getRegisteredTransportDescriptionOrThrowLocked(transportName)
- .currentDestinationString;
- }
- }
-
- /**
- * Retrieve the data management label of {@code transportName}.
+ * Retrieves the data management label of {@code transportName}.
+ *
* @throws TransportNotRegisteredException if the transport is not registered.
*/
@Nullable
@@ -284,54 +253,74 @@
}
}
- /**
- * Retrieve the transport dir name of {@code transportName}.
- * @throws TransportNotRegisteredException if the transport is not registered.
- */
- public String getTransportDirName(String transportName)
- throws TransportNotRegisteredException {
+ /* Returns true if the transport identified by {@code transportName} is registered. */
+ public boolean isTransportRegistered(String transportName) {
synchronized (mTransportLock) {
- return getRegisteredTransportDescriptionOrThrowLocked(transportName)
- .transportDirName;
- }
- }
-
- /**
- * Retrieve the transport dir name of {@code transportComponent}.
- * @throws TransportNotRegisteredException if the transport is not registered.
- */
- public String getTransportDirName(ComponentName transportComponent)
- throws TransportNotRegisteredException {
- synchronized (mTransportLock) {
- return getRegisteredTransportDescriptionOrThrowLocked(transportComponent)
- .transportDirName;
+ return getRegisteredTransportEntryLocked(transportName) != null;
}
}
/**
* Execute {@code transportConsumer} for each registered transport passing the transport name.
* This is called with an internal lock held, ensuring that the transport will remain registered
- * while {@code transportConsumer} is being executed. Don't do heavy operations in
- * {@code transportConsumer}.
+ * while {@code transportConsumer} is being executed. Don't do heavy operations in {@code
+ * transportConsumer}.
*/
public void forEachRegisteredTransport(Consumer<String> transportConsumer) {
synchronized (mTransportLock) {
- for (TransportDescription transportDescription
- : mRegisteredTransportsDescriptionMap.values()) {
+ for (TransportDescription transportDescription :
+ mRegisteredTransportsDescriptionMap.values()) {
transportConsumer.accept(transportDescription.name);
}
}
}
- public String getTransportName(IBackupTransport binder) {
+ /**
+ * Updates given values for the transport already registered and identified with {@param
+ * transportComponent}. If the transport is not registered it will log and return.
+ */
+ public void updateTransportAttributes(
+ ComponentName transportComponent,
+ String name,
+ @Nullable Intent configurationIntent,
+ String currentDestinationString,
+ @Nullable Intent dataManagementIntent,
+ @Nullable String dataManagementLabel) {
synchronized (mTransportLock) {
- for (TransportConnection conn : mValidTransports.values()) {
- if (conn.getBinder() == binder) {
- return conn.getName();
- }
+ TransportDescription description =
+ mRegisteredTransportsDescriptionMap.get(transportComponent);
+ if (description == null) {
+ Slog.e(TAG, "Transport " + name + " not registered tried to change description");
+ return;
}
+ description.name = name;
+ description.configurationIntent = configurationIntent;
+ description.currentDestinationString = currentDestinationString;
+ description.dataManagementIntent = dataManagementIntent;
+ description.dataManagementLabel = dataManagementLabel;
+ Slog.d(TAG, "Transport " + name + " updated its attributes");
}
- return null;
+ }
+
+ @GuardedBy("mTransportLock")
+ private TransportDescription getRegisteredTransportDescriptionOrThrowLocked(
+ ComponentName transportComponent) throws TransportNotRegisteredException {
+ TransportDescription description =
+ mRegisteredTransportsDescriptionMap.get(transportComponent);
+ if (description == null) {
+ throw new TransportNotRegisteredException(transportComponent);
+ }
+ return description;
+ }
+
+ @GuardedBy("mTransportLock")
+ private TransportDescription getRegisteredTransportDescriptionOrThrowLocked(
+ String transportName) throws TransportNotRegisteredException {
+ TransportDescription description = getRegisteredTransportDescriptionLocked(transportName);
+ if (description == null) {
+ throw new TransportNotRegisteredException(transportName);
+ }
+ return description;
}
@GuardedBy("mTransportLock")
@@ -351,21 +340,11 @@
}
@GuardedBy("mTransportLock")
- private TransportDescription getRegisteredTransportDescriptionOrThrowLocked(
- String transportName) throws TransportNotRegisteredException {
- TransportDescription description = getRegisteredTransportDescriptionLocked(transportName);
- if (description == null) {
- throw new TransportNotRegisteredException(transportName);
- }
- return description;
- }
-
- @GuardedBy("mTransportLock")
@Nullable
private Map.Entry<ComponentName, TransportDescription> getRegisteredTransportEntryLocked(
String transportName) {
- for (Map.Entry<ComponentName, TransportDescription> entry
- : mRegisteredTransportsDescriptionMap.entrySet()) {
+ for (Map.Entry<ComponentName, TransportDescription> entry :
+ mRegisteredTransportsDescriptionMap.entrySet()) {
TransportDescription description = entry.getValue();
if (transportName.equals(description.name)) {
return entry;
@@ -374,17 +353,16 @@
return null;
}
- @GuardedBy("mTransportLock")
- private TransportDescription getRegisteredTransportDescriptionOrThrowLocked(
- ComponentName transportComponent) throws TransportNotRegisteredException {
- TransportDescription description =
- mRegisteredTransportsDescriptionMap.get(transportComponent);
- if (description == null) {
- throw new TransportNotRegisteredException(transportComponent);
- }
- return description;
- }
-
+ /**
+ * Returns a {@link TransportClient} for {@code transportName} or {@code null} if not
+ * registered.
+ *
+ * @param transportName The name of the transport.
+ * @param caller A {@link String} identifying the caller for logging/debugging purposes. Check
+ * {@link TransportClient#connectAsync(TransportConnectionListener, String)} for more
+ * details.
+ * @return A {@link TransportClient} or null if not registered.
+ */
@Nullable
public TransportClient getTransportClient(String transportName, String caller) {
try {
@@ -395,6 +373,16 @@
}
}
+ /**
+ * Returns a {@link TransportClient} for {@code transportName} or throws if not registered.
+ *
+ * @param transportName The name of the transport.
+ * @param caller A {@link String} identifying the caller for logging/debugging purposes. Check
+ * {@link TransportClient#connectAsync(TransportConnectionListener, String)} for more
+ * details.
+ * @return A {@link TransportClient}.
+ * @throws TransportNotRegisteredException if the transport is not registered.
+ */
public TransportClient getTransportClientOrThrow(String transportName, String caller)
throws TransportNotRegisteredException {
synchronized (mTransportLock) {
@@ -406,19 +394,14 @@
}
}
- public boolean isTransportRegistered(String transportName) {
- synchronized (mTransportLock) {
- return getRegisteredTransportEntryLocked(transportName) != null;
- }
- }
-
/**
- * Returns a {@link TransportClient} for the current transport or null if not found.
+ * Returns a {@link TransportClient} for the current transport or {@code null} if not
+ * registered.
*
* @param caller A {@link String} identifying the caller for logging/debugging purposes. Check
* {@link TransportClient#connectAsync(TransportConnectionListener, String)} for more
* details.
- * @return A {@link TransportClient} or null if not found.
+ * @return A {@link TransportClient} or null if not registered.
*/
@Nullable
public TransportClient getCurrentTransportClient(String caller) {
@@ -455,130 +438,88 @@
mTransportClientManager.disposeOfTransportClient(transportClient, caller);
}
- String[] getBoundTransportNames() {
- synchronized (mTransportLock) {
- return mBoundTransports.keySet().toArray(new String[mBoundTransports.size()]);
- }
- }
-
- ComponentName[] getAllTransportComponents() {
- synchronized (mTransportLock) {
- return mValidTransports.keySet().toArray(new ComponentName[mValidTransports.size()]);
- }
- }
-
/**
- * An *eligible* transport is a service component that satisfies intent with action
- * android.backup.TRANSPORT_HOST and returns true for
- * {@link #isTransportTrusted(ComponentName)}. It may be registered or not registered.
- * This method returns the {@link ComponentName}s of those transports.
- */
- ComponentName[] getEligibleTransportComponents() {
- synchronized (mTransportLock) {
- return mEligibleTransports.toArray(new ComponentName[mEligibleTransports.size()]);
- }
- }
-
- Set<ComponentName> getTransportWhitelist() {
- return mTransportWhitelist;
- }
-
- /**
- * A *registered* transport is an eligible transport that has been successfully connected and
- * that returned true for method
- * {@link TransportBoundListener#onTransportBound(IBackupTransport)} of TransportBoundListener
- * provided in the constructor. This method returns the names of the registered transports.
- */
- String[] getRegisteredTransportNames() {
- synchronized (mTransportLock) {
- return mRegisteredTransportsDescriptionMap.values().stream()
- .map(transportDescription -> transportDescription.name)
- .toArray(String[]::new);
- }
- }
-
- /**
- * Updates given values for the transport already registered and identified with
- * {@param transportComponent}. If the transport is not registered it will log and return.
- */
- public void updateTransportAttributes(
- ComponentName transportComponent,
- String name,
- @Nullable Intent configurationIntent,
- String currentDestinationString,
- @Nullable Intent dataManagementIntent,
- @Nullable String dataManagementLabel) {
- synchronized (mTransportLock) {
- TransportDescription description =
- mRegisteredTransportsDescriptionMap.get(transportComponent);
- if (description == null) {
- Slog.e(TAG, "Transport " + name + " not registered tried to change description");
- return;
- }
- description.name = name;
- description.configurationIntent = configurationIntent;
- description.currentDestinationString = currentDestinationString;
- description.dataManagementIntent = dataManagementIntent;
- description.dataManagementLabel = dataManagementLabel;
- Slog.d(TAG, "Transport " + name + " updated its attributes");
- }
- }
-
- @Nullable
- String getCurrentTransportName() {
- return mCurrentTransportName;
- }
-
- // This is for mocking, Mockito can't mock if package-protected and in the same package but
- // different class loaders. Checked with the debugger and class loaders are different
- // See https://github.com/mockito/mockito/issues/796
- @VisibleForTesting(visibility = PACKAGE)
- public void registerAllTransports() {
- bindToAllInternal(null /* all packages */, null /* all components */);
- }
-
- /**
- * Bind to all transports belonging to the given package and the given component list.
- * null acts a wildcard.
+ * Sets {@code transportName} as selected transport and returns previously selected transport
+ * name. If there was no previous transport it returns null.
*
- * If packageName is null, bind to all transports in all packages.
- * If components is null, bind to all transports in the given package.
+ * <p>You should NOT call this method in new code. This won't make any checks against {@code
+ * transportName}, putting any operation at risk of a {@link TransportNotRegisteredException} or
+ * another error at the time it's being executed.
+ *
+ * <p>{@link Deprecated} as public, this method can be used as private.
*/
- private void bindToAllInternal(String packageName, String[] components) {
- PackageInfo pkgInfo = null;
- if (packageName != null) {
+ @Deprecated
+ @Nullable
+ String selectTransport(String transportName) {
+ synchronized (mTransportLock) {
+ String prevTransport = mCurrentTransportName;
+ mCurrentTransportName = transportName;
+ return prevTransport;
+ }
+ }
+
+ /**
+ * Tries to register the transport if not registered. If successful also selects the transport.
+ *
+ * @param transportComponent Host of the transport.
+ * @return One of {@link BackupManager#SUCCESS}, {@link BackupManager#ERROR_TRANSPORT_INVALID}
+ * or {@link BackupManager#ERROR_TRANSPORT_UNAVAILABLE}.
+ */
+ @WorkerThread
+ public int registerAndSelectTransport(ComponentName transportComponent) {
+ synchronized (mTransportLock) {
+ if (!mRegisteredTransportsDescriptionMap.containsKey(transportComponent)) {
+ int result = registerTransport(transportComponent);
+ if (result != BackupManager.SUCCESS) {
+ return result;
+ }
+ }
+
try {
- pkgInfo = mPackageManager.getPackageInfo(packageName, 0);
- } catch (PackageManager.NameNotFoundException e) {
- Slog.w(TAG, "Package not found: " + packageName);
- return;
+ selectTransport(getTransportName(transportComponent));
+ return BackupManager.SUCCESS;
+ } catch (TransportNotRegisteredException e) {
+ // Shouldn't happen because we are holding the lock
+ Slog.wtf(TAG, "Transport unexpectedly not registered");
+ return BackupManager.ERROR_TRANSPORT_UNAVAILABLE;
}
}
+ }
- Intent intent = new Intent(mTransportServiceIntent);
- if (packageName != null) {
- intent.setPackage(packageName);
+ @WorkerThread
+ public void registerTransports() {
+ registerTransportsForIntent(mTransportServiceIntent, transportComponent -> true);
+ }
+
+ @WorkerThread
+ private void registerTransportsFromPackage(
+ String packageName, Predicate<ComponentName> transportComponentFilter) {
+ try {
+ mPackageManager.getPackageInfo(packageName, 0);
+ } catch (PackageManager.NameNotFoundException e) {
+ Slog.e(TAG, "Trying to register transports from package not found " + packageName);
+ return;
}
- List<ResolveInfo> hosts = mPackageManager.queryIntentServicesAsUser(
- intent, 0, UserHandle.USER_SYSTEM);
- if (hosts != null) {
+ registerTransportsForIntent(
+ new Intent(mTransportServiceIntent).setPackage(packageName),
+ transportComponentFilter.and(fromPackageFilter(packageName)));
+ }
+
+ @WorkerThread
+ private void registerTransportsForIntent(
+ Intent intent, Predicate<ComponentName> transportComponentFilter) {
+ List<ResolveInfo> hosts =
+ mPackageManager.queryIntentServicesAsUser(intent, 0, UserHandle.USER_SYSTEM);
+ if (hosts == null) {
+ return;
+ }
+ synchronized (mTransportLock) {
for (ResolveInfo host : hosts) {
- final ComponentName infoComponentName = getComponentName(host.serviceInfo);
- boolean shouldBind = false;
- if (components != null && packageName != null) {
- for (String component : components) {
- ComponentName cn = new ComponentName(pkgInfo.packageName, component);
- if (infoComponentName.equals(cn)) {
- shouldBind = true;
- break;
- }
- }
- } else {
- shouldBind = true;
- }
- if (shouldBind && isTransportTrusted(infoComponentName)) {
- tryBindTransport(infoComponentName);
+ ComponentName transportComponent = host.serviceInfo.getComponentName();
+ if (transportComponentFilter.test(transportComponent)
+ && isTransportTrusted(transportComponent)) {
+ registerTransport(transportComponent);
}
}
}
@@ -605,64 +546,6 @@
return true;
}
- private void tryBindTransport(ComponentName transportComponentName) {
- Slog.d(TAG, "Binding to transport: " + transportComponentName.flattenToShortString());
- // TODO: b/22388012 (Multi user backup and restore)
- TransportConnection connection = new TransportConnection(transportComponentName);
- synchronized (mTransportLock) {
- mEligibleTransports.add(transportComponentName);
- }
- if (bindToTransport(transportComponentName, connection)) {
- synchronized (mTransportLock) {
- mValidTransports.put(transportComponentName, connection);
- }
- } else {
- Slog.w(TAG, "Couldn't bind to transport " + transportComponentName);
- }
- }
-
- private boolean bindToTransport(ComponentName componentName, ServiceConnection connection) {
- Intent intent = new Intent(mTransportServiceIntent)
- .setComponent(componentName);
- return mContext.bindServiceAsUser(intent, connection, Context.BIND_AUTO_CREATE,
- createSystemUserHandle());
- }
-
- String selectTransport(String transportName) {
- synchronized (mTransportLock) {
- String prevTransport = mCurrentTransportName;
- mCurrentTransportName = transportName;
- return prevTransport;
- }
- }
-
- /**
- * Tries to register the transport if not registered. If successful also selects the transport.
- *
- * @param transportComponent Host of the transport.
- * @return One of {@link BackupManager#SUCCESS}, {@link BackupManager#ERROR_TRANSPORT_INVALID}
- * or {@link BackupManager#ERROR_TRANSPORT_UNAVAILABLE}.
- */
- public int registerAndSelectTransport(ComponentName transportComponent) {
- synchronized (mTransportLock) {
- if (!mRegisteredTransportsDescriptionMap.containsKey(transportComponent)) {
- int result = registerTransport(transportComponent);
- if (result != BackupManager.SUCCESS) {
- return result;
- }
- }
-
- try {
- selectTransport(getTransportName(transportComponent));
- return BackupManager.SUCCESS;
- } catch (TransportNotRegisteredException e) {
- // Shouldn't happen because we are holding the lock
- Slog.wtf(TAG, "Transport unexpectedly not registered");
- return BackupManager.ERROR_TRANSPORT_UNAVAILABLE;
- }
- }
- }
-
/**
* Tries to register transport represented by {@code transportComponent}.
*
@@ -670,7 +553,12 @@
* @return One of {@link BackupManager#SUCCESS}, {@link BackupManager#ERROR_TRANSPORT_INVALID}
* or {@link BackupManager#ERROR_TRANSPORT_UNAVAILABLE}.
*/
+ @WorkerThread
private int registerTransport(ComponentName transportComponent) {
+ if (!isTransportTrusted(transportComponent)) {
+ return BackupManager.ERROR_TRANSPORT_INVALID;
+ }
+
String transportString = transportComponent.flattenToShortString();
String callerLogString = "TransportManager.registerTransport()";
@@ -689,26 +577,21 @@
EventLog.writeEvent(EventLogTags.BACKUP_TRANSPORT_LIFECYCLE, transportString, 1);
int result;
- if (isTransportValid(transport)) {
- try {
- registerTransport(transportComponent, transport);
- // If registerTransport() hasn't thrown...
- result = BackupManager.SUCCESS;
- } catch (RemoteException e) {
- Slog.e(TAG, "Transport " + transportString + " died while registering");
- result = BackupManager.ERROR_TRANSPORT_UNAVAILABLE;
- }
- } else {
- Slog.w(TAG, "Can't register invalid transport " + transportString);
- result = BackupManager.ERROR_TRANSPORT_INVALID;
+ try {
+ String transportName = transport.name();
+ String transportDirName = transport.transportDirName();
+ registerTransport(transportComponent, transport);
+ // If registerTransport() hasn't thrown...
+ Slog.d(TAG, "Transport " + transportString + " registered");
+ mOnTransportRegisteredListener.onTransportRegistered(transportName, transportDirName);
+ result = BackupManager.SUCCESS;
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Transport " + transportString + " died while registering");
+ EventLog.writeEvent(EventLogTags.BACKUP_TRANSPORT_LIFECYCLE, transportString, 0);
+ result = BackupManager.ERROR_TRANSPORT_UNAVAILABLE;
}
mTransportClientManager.disposeOfTransportClient(transportClient, callerLogString);
- if (result == BackupManager.SUCCESS) {
- Slog.d(TAG, "Transport " + transportString + " registered");
- } else {
- EventLog.writeEvent(EventLogTags.BACKUP_TRANSPORT_LIFECYCLE, transportString, 0);
- }
return result;
}
@@ -717,204 +600,20 @@
throws RemoteException {
synchronized (mTransportLock) {
String name = transport.name();
- TransportDescription description = new TransportDescription(
- name,
- transport.transportDirName(),
- transport.configurationIntent(),
- transport.currentDestinationString(),
- transport.dataManagementIntent(),
- transport.dataManagementLabel());
+ TransportDescription description =
+ new TransportDescription(
+ name,
+ transport.transportDirName(),
+ transport.configurationIntent(),
+ transport.currentDestinationString(),
+ transport.dataManagementIntent(),
+ transport.dataManagementLabel());
mRegisteredTransportsDescriptionMap.put(transportComponent, description);
}
}
- private boolean isTransportValid(IBackupTransport transport) {
- if (mTransportBoundListener == null) {
- Slog.w(TAG, "setTransportBoundListener() not called, assuming transport invalid");
- return false;
- }
- return mTransportBoundListener.onTransportBound(transport);
- }
-
- private class TransportConnection implements ServiceConnection {
-
- // Hold mTransportLock to access these fields so as to provide a consistent view of them.
- private volatile IBackupTransport mBinder;
- private volatile String mTransportName;
-
- private final ComponentName mTransportComponent;
-
- private TransportConnection(ComponentName transportComponent) {
- mTransportComponent = transportComponent;
- }
-
- @Override
- public void onServiceConnected(ComponentName component, IBinder binder) {
- synchronized (mTransportLock) {
- mBinder = IBackupTransport.Stub.asInterface(binder);
- boolean success = false;
-
- EventLog.writeEvent(EventLogTags.BACKUP_TRANSPORT_LIFECYCLE,
- component.flattenToShortString(), 1);
-
- try {
- mTransportName = mBinder.name();
- // BackupManager requests some fields from the transport. If they are
- // invalid, throw away this transport.
- if (isTransportValid(mBinder)) {
- // We're now using the always-bound connection to do the registration but
- // when we remove the always-bound code this will be in the first binding
- // TODO: Move registration to first binding
- registerTransport(component, mBinder);
- // If registerTransport() hasn't thrown...
- success = true;
- }
- } catch (RemoteException e) {
- success = false;
- Slog.e(TAG, "Couldn't get transport name.", e);
- } finally {
- // we need to intern() the String of the component, so that we can use it with
- // Handler's removeMessages(), which uses == operator to compare the tokens
- String componentShortString = component.flattenToShortString().intern();
- if (success) {
- Slog.d(TAG, "Bound to transport: " + componentShortString);
- mBoundTransports.put(mTransportName, component);
- // cancel rebinding on timeout for this component as we've already connected
- mHandler.removeMessages(REBINDING_TIMEOUT_MSG, componentShortString);
- } else {
- Slog.w(TAG, "Bound to transport " + componentShortString +
- " but it is invalid");
- EventLog.writeEvent(EventLogTags.BACKUP_TRANSPORT_LIFECYCLE,
- componentShortString, 0);
- mContext.unbindService(this);
- mValidTransports.remove(component);
- mEligibleTransports.remove(component);
- mBinder = null;
- }
- }
- }
- }
-
- @Override
- public void onServiceDisconnected(ComponentName component) {
- synchronized (mTransportLock) {
- mBinder = null;
- mBoundTransports.remove(mTransportName);
- }
- String componentShortString = component.flattenToShortString();
- EventLog.writeEvent(EventLogTags.BACKUP_TRANSPORT_LIFECYCLE, componentShortString, 0);
- Slog.w(TAG, "Disconnected from transport " + componentShortString);
- scheduleRebindTimeout(component);
- }
-
- /**
- * We'll attempt to explicitly rebind to a transport if it hasn't happened automatically
- * for a few minutes after the binding went away.
- */
- private void scheduleRebindTimeout(ComponentName component) {
- // we need to intern() the String of the component, so that we can use it with Handler's
- // removeMessages(), which uses == operator to compare the tokens
- final String componentShortString = component.flattenToShortString().intern();
- final long rebindTimeout = getRebindTimeout();
- mHandler.removeMessages(REBINDING_TIMEOUT_MSG, componentShortString);
- Message msg = mHandler.obtainMessage(REBINDING_TIMEOUT_MSG);
- msg.obj = componentShortString;
- mHandler.sendMessageDelayed(msg, rebindTimeout);
- Slog.d(TAG, "Scheduled explicit rebinding for " + componentShortString + " in "
- + rebindTimeout + "ms");
- }
-
- // Intentionally not synchronized -- the variable is volatile and changes to its value
- // are inside synchronized blocks, providing a memory sync barrier; and this method
- // does not touch any other state protected by that lock.
- private IBackupTransport getBinder() {
- return mBinder;
- }
-
- // Intentionally not synchronized; same as getBinder()
- private String getName() {
- return mTransportName;
- }
-
- // Intentionally not synchronized; same as getBinder()
- private void bindIfUnbound() {
- if (mBinder == null) {
- Slog.d(TAG,
- "Rebinding to transport " + mTransportComponent.flattenToShortString());
- bindToTransport(mTransportComponent, this);
- }
- }
-
- private long getRebindTimeout() {
- final boolean isDeviceProvisioned = Settings.Global.getInt(
- mContext.getContentResolver(),
- Settings.Global.DEVICE_PROVISIONED, 0) != 0;
- return isDeviceProvisioned
- ? REBINDING_TIMEOUT_PROVISIONED_MS
- : REBINDING_TIMEOUT_UNPROVISIONED_MS;
- }
- }
-
- public interface TransportBoundListener {
- /** Should return true if this is a valid transport. */
- boolean onTransportBound(IBackupTransport binder);
- }
-
- private class RebindOnTimeoutHandler extends Handler {
-
- RebindOnTimeoutHandler(Looper looper) {
- super(looper);
- }
-
- @Override
- public void handleMessage(Message msg) {
- if (msg.what == REBINDING_TIMEOUT_MSG) {
- String componentShortString = (String) msg.obj;
- ComponentName transportComponent =
- ComponentName.unflattenFromString(componentShortString);
- synchronized (mTransportLock) {
- if (mBoundTransports.containsValue(transportComponent)) {
- Slog.d(TAG, "Explicit rebinding timeout passed, but already bound to "
- + componentShortString + " so not attempting to rebind");
- return;
- }
- Slog.d(TAG, "Explicit rebinding timeout passed, attempting rebinding to: "
- + componentShortString);
- // unbind the existing (broken) connection
- TransportConnection conn = mValidTransports.get(transportComponent);
- if (conn != null) {
- mContext.unbindService(conn);
- Slog.d(TAG, "Unbinding the existing (broken) connection to transport: "
- + componentShortString);
- }
- }
- // rebind to transport
- tryBindTransport(transportComponent);
- } else {
- Slog.e(TAG, "Unknown message sent to RebindOnTimeoutHandler, msg.what: "
- + msg.what);
- }
- }
- }
-
- private static void log_verbose(String message) {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Slog.v(TAG, message);
- }
- }
-
- // These only exists to make it testable with Robolectric, which is not updated to API level 24
- // yet.
- // TODO: Get rid of this once Robolectric is updated.
- private static ComponentName getComponentName(ServiceInfo serviceInfo) {
- return new ComponentName(serviceInfo.packageName, serviceInfo.name);
- }
-
- // These only exists to make it testable with Robolectric, which is not updated to API level 24
- // yet.
- // TODO: Get rid of this once Robolectric is updated.
- public static UserHandle createSystemUserHandle() {
- return new UserHandle(UserHandle.USER_SYSTEM);
+ private static Predicate<ComponentName> fromPackageFilter(String packageName) {
+ return transportComponent -> packageName.equals(transportComponent.getPackageName());
}
private static class TransportDescription {
diff --git a/services/backup/java/com/android/server/backup/internal/PerformBackupTask.java b/services/backup/java/com/android/server/backup/internal/PerformBackupTask.java
index c232241..cc3af8c 100644
--- a/services/backup/java/com/android/server/backup/internal/PerformBackupTask.java
+++ b/services/backup/java/com/android/server/backup/internal/PerformBackupTask.java
@@ -907,7 +907,15 @@
backupData = ParcelFileDescriptor.open(mBackupDataName,
ParcelFileDescriptor.MODE_READ_ONLY);
backupManagerService.addBackupTrace("sending data to transport");
- int flags = mUserInitiated ? BackupTransport.FLAG_USER_INITIATED : 0;
+
+ int userInitiatedFlag =
+ mUserInitiated ? BackupTransport.FLAG_USER_INITIATED : 0;
+ int incrementalFlag =
+ mSavedStateName.length() == 0
+ ? BackupTransport.FLAG_NON_INCREMENTAL
+ : BackupTransport.FLAG_INCREMENTAL;
+ int flags = userInitiatedFlag | incrementalFlag;
+
mStatus = transport.performBackup(mCurrentPackage, backupData, flags);
}
diff --git a/services/backup/java/com/android/server/backup/transport/OnTransportRegisteredListener.java b/services/backup/java/com/android/server/backup/transport/OnTransportRegisteredListener.java
new file mode 100644
index 0000000..391ec2d
--- /dev/null
+++ b/services/backup/java/com/android/server/backup/transport/OnTransportRegisteredListener.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.backup.transport;
+
+import com.android.server.backup.TransportManager;
+
+/**
+ * Listener called when a transport is registered with the {@link TransportManager}. Can be set
+ * using {@link TransportManager#setOnTransportRegisteredListener(OnTransportRegisteredListener)}.
+ */
+@FunctionalInterface
+public interface OnTransportRegisteredListener {
+ /**
+ * Called when a transport is successfully registered.
+ * @param transportName The name of the transport.
+ * @param transportDirName The dir name of the transport.
+ */
+ public void onTransportRegistered(String transportName, String transportDirName);
+}
diff --git a/services/backup/java/com/android/server/backup/transport/TransportClient.java b/services/backup/java/com/android/server/backup/transport/TransportClient.java
index 7bd9111..bd4a0bb 100644
--- a/services/backup/java/com/android/server/backup/transport/TransportClient.java
+++ b/services/backup/java/com/android/server/backup/transport/TransportClient.java
@@ -236,7 +236,7 @@
mBindIntent,
mConnection,
Context.BIND_AUTO_CREATE,
- TransportManager.createSystemUserHandle());
+ UserHandle.SYSTEM);
if (hasBound) {
// We don't need to set a time-out because we are guaranteed to get a call
// back in ServiceConnection, either an onServiceConnected() or
diff --git a/services/core/java/com/android/server/AlarmManagerService.java b/services/core/java/com/android/server/AlarmManagerService.java
index 86063c3..15c0f3c 100644
--- a/services/core/java/com/android/server/AlarmManagerService.java
+++ b/services/core/java/com/android/server/AlarmManagerService.java
@@ -89,10 +89,10 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.ArrayUtils;
import com.android.internal.util.DumpUtils;
import com.android.internal.util.LocalLog;
import com.android.server.ForceAppStandbyTracker.Listener;
-import com.android.server.LocalServices;
/**
* Alarm manager implementaion.
@@ -152,6 +152,8 @@
private long mLastTickSet;
private long mLastTickIssued; // elapsed
private long mLastTickReceived;
+ private long mLastTickAdded;
+ private long mLastTickRemoved;
int mBroadcastRefCount = 0;
PowerManager.WakeLock mWakeLock;
boolean mLastWakeLockUnimportantForLogging;
@@ -431,6 +433,9 @@
end = seed.maxWhenElapsed;
flags = seed.flags;
alarms.add(seed);
+ if (seed.operation == mTimeTickSender) {
+ mLastTickAdded = System.currentTimeMillis();
+ }
}
int size() {
@@ -453,6 +458,9 @@
index = 0 - index - 1;
}
alarms.add(index, alarm);
+ if (alarm.operation == mTimeTickSender) {
+ mLastTickAdded = System.currentTimeMillis();
+ }
if (DEBUG_BATCH) {
Slog.v(TAG, "Adding " + alarm + " to " + this);
}
@@ -484,6 +492,9 @@
if (alarm.alarmClock != null) {
mNextAlarmClockMayChange = true;
}
+ if (alarm.operation == mTimeTickSender) {
+ mLastTickRemoved = System.currentTimeMillis();
+ }
} else {
if (alarm.whenElapsed > newStart) {
newStart = alarm.whenElapsed;
@@ -700,6 +711,39 @@
}
return -1;
}
+ /** @return total count of the alarms in a set of alarm batches. */
+ static int getAlarmCount(ArrayList<Batch> batches) {
+ int ret = 0;
+
+ final int size = batches.size();
+ for (int i = 0; i < size; i++) {
+ ret += batches.get(i).size();
+ }
+ return ret;
+ }
+
+ boolean haveAlarmsTimeTickAlarm(ArrayList<Alarm> alarms) {
+ if (alarms.size() == 0) {
+ return false;
+ }
+ final int batchSize = alarms.size();
+ for (int j = 0; j < batchSize; j++) {
+ if (alarms.get(j).operation == mTimeTickSender) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ boolean haveBatchesTimeTickAlarm(ArrayList<Batch> batches) {
+ final int numBatches = batches.size();
+ for (int i = 0; i < numBatches; i++) {
+ if (haveAlarmsTimeTickAlarm(batches.get(i).alarms)) {
+ return true;
+ }
+ }
+ return false;
+ }
// The RTC clock has moved arbitrarily, so we need to recalculate all the batching
void rebatchAllAlarms() {
@@ -709,6 +753,11 @@
}
void rebatchAllAlarmsLocked(boolean doValidate) {
+ final int oldCount =
+ getAlarmCount(mAlarmBatches) + ArrayUtils.size(mPendingWhileIdleAlarms);
+ final boolean oldHasTick = haveBatchesTimeTickAlarm(mAlarmBatches)
+ || haveAlarmsTimeTickAlarm(mPendingWhileIdleAlarms);
+
ArrayList<Batch> oldSet = (ArrayList<Batch>) mAlarmBatches.clone();
mAlarmBatches.clear();
Alarm oldPendingIdleUntil = mPendingIdleUntil;
@@ -729,6 +778,18 @@
restorePendingWhileIdleAlarmsLocked();
}
}
+ final int newCount =
+ getAlarmCount(mAlarmBatches) + ArrayUtils.size(mPendingWhileIdleAlarms);
+ final boolean newHasTick = haveBatchesTimeTickAlarm(mAlarmBatches)
+ || haveAlarmsTimeTickAlarm(mPendingWhileIdleAlarms);
+
+ if (oldCount != newCount) {
+ Slog.wtf(TAG, "Rebatching: total count changed from " + oldCount + " to " + newCount);
+ }
+ if (oldHasTick != newHasTick) {
+ Slog.wtf(TAG, "Rebatching: hasTick changed from " + oldHasTick + " to " + newHasTick);
+ }
+
rescheduleKernelAlarmsLocked();
updateNextAlarmClockLocked();
}
@@ -1603,7 +1664,7 @@
final long nowRTC = System.currentTimeMillis();
final long nowELAPSED = SystemClock.elapsedRealtime();
- SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+ SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
pw.print(" nowRTC="); pw.print(nowRTC);
pw.print("="); pw.print(sdf.format(new Date(nowRTC)));
@@ -1613,10 +1674,11 @@
pw.print("="); pw.println(sdf.format(new Date(mLastTimeChangeClockTime)));
pw.print(" mLastTimeChangeRealtime="); pw.println(mLastTimeChangeRealtime);
pw.print(" mLastTickIssued=");
- TimeUtils.formatDuration(mLastTickIssued - nowELAPSED, pw);
- pw.println();
+ pw.println(sdf.format(new Date(nowRTC - (nowELAPSED - mLastTickIssued))));
pw.print(" mLastTickReceived="); pw.println(sdf.format(new Date(mLastTickReceived)));
pw.print(" mLastTickSet="); pw.println(sdf.format(new Date(mLastTickSet)));
+ pw.print(" mLastTickAdded="); pw.println(sdf.format(new Date(mLastTickAdded)));
+ pw.print(" mLastTickRemoved="); pw.println(sdf.format(new Date(mLastTickRemoved)));
pw.println();
if (!mInteractive) {
pw.print(" Time since non-interactive: ");
@@ -2400,6 +2462,10 @@
}
void removeLocked(final int uid) {
+ if (uid == Process.SYSTEM_UID) {
+ Slog.wtf(TAG, "removeLocked: Shouldn't for UID=" + uid);
+ return;
+ }
boolean didRemove = false;
final Predicate<Alarm> whichAlarms = (Alarm a) -> a.uid == uid;
for (int i = mAlarmBatches.size() - 1; i >= 0; i--) {
@@ -2448,6 +2514,7 @@
boolean didRemove = false;
final Predicate<Alarm> whichAlarms = (Alarm a) -> a.matches(packageName);
+ final boolean oldHasTick = haveBatchesTimeTickAlarm(mAlarmBatches);
for (int i = mAlarmBatches.size() - 1; i >= 0; i--) {
Batch b = mAlarmBatches.get(i);
didRemove |= b.remove(whichAlarms);
@@ -2455,6 +2522,11 @@
mAlarmBatches.remove(i);
}
}
+ final boolean newHasTick = haveBatchesTimeTickAlarm(mAlarmBatches);
+ if (oldHasTick != newHasTick) {
+ Slog.wtf(TAG, "removeLocked: hasTick changed from " + oldHasTick + " to " + newHasTick);
+ }
+
for (int i = mPendingWhileIdleAlarms.size() - 1; i >= 0; i--) {
final Alarm a = mPendingWhileIdleAlarms.get(i);
if (a.matches(packageName)) {
@@ -2484,6 +2556,10 @@
}
void removeForStoppedLocked(final int uid) {
+ if (uid == Process.SYSTEM_UID) {
+ Slog.wtf(TAG, "removeForStoppedLocked: Shouldn't for UID=" + uid);
+ return;
+ }
boolean didRemove = false;
final Predicate<Alarm> whichAlarms = (Alarm a) -> {
try {
@@ -2524,6 +2600,10 @@
}
void removeUserLocked(int userHandle) {
+ if (userHandle == UserHandle.USER_SYSTEM) {
+ Slog.wtf(TAG, "removeForStoppedLocked: Shouldn't for user=" + userHandle);
+ return;
+ }
boolean didRemove = false;
final Predicate<Alarm> whichAlarms =
(Alarm a) -> UserHandle.getUserId(a.creatorUid) == userHandle;
@@ -3688,6 +3768,9 @@
mDeliveryTracker, mHandler, null,
allowWhileIdle ? mIdleOptions : null);
} catch (PendingIntent.CanceledException e) {
+ if (alarm.operation == mTimeTickSender) {
+ Slog.wtf(TAG, "mTimeTickSender canceled");
+ }
if (alarm.repeatInterval > 0) {
// This IntentSender is no longer valid, but this
// is a repeating alarm, so toss it
diff --git a/services/core/java/com/android/server/BatteryService.java b/services/core/java/com/android/server/BatteryService.java
index dcb0fab..af0b66d 100644
--- a/services/core/java/com/android/server/BatteryService.java
+++ b/services/core/java/com/android/server/BatteryService.java
@@ -298,6 +298,8 @@
final int lowPowerModeTriggerLevel = Settings.Global.getInt(resolver,
Settings.Global.LOW_POWER_MODE_TRIGGER_LEVEL, defWarnLevel);
+ // NOTE: Keep the logic in sync with PowerUI.java in systemUI.
+ // TODO: Propagate this value from BatteryService to system UI, really.
mLowBatteryWarningLevel = Math.min(defWarnLevel, lowPowerModeTriggerLevel);
if (mLowBatteryWarningLevel == 0) {
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index 63ee9fa..77521df 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -226,7 +226,11 @@
@GuardedBy("mVpns")
private final SparseArray<Vpn> mVpns = new SparseArray<Vpn>();
+ // TODO: investigate if mLockdownEnabled can be removed and replaced everywhere by
+ // a direct call to LockdownVpnTracker.isEnabled().
+ @GuardedBy("mVpns")
private boolean mLockdownEnabled;
+ @GuardedBy("mVpns")
private LockdownVpnTracker mLockdownTracker;
final private Context mContext;
@@ -997,9 +1001,9 @@
}
private Network[] getVpnUnderlyingNetworks(int uid) {
- if (!mLockdownEnabled) {
- int user = UserHandle.getUserId(uid);
- synchronized (mVpns) {
+ synchronized (mVpns) {
+ if (!mLockdownEnabled) {
+ int user = UserHandle.getUserId(uid);
Vpn vpn = mVpns.get(user);
if (vpn != null && vpn.appliesToUid(uid)) {
return vpn.getUnderlyingNetworks();
@@ -1087,8 +1091,10 @@
if (isNetworkWithLinkPropertiesBlocked(state.linkProperties, uid, ignoreBlocked)) {
state.networkInfo.setDetailedState(DetailedState.BLOCKED, null, null);
}
- if (mLockdownTracker != null) {
- mLockdownTracker.augmentNetworkInfo(state.networkInfo);
+ synchronized (mVpns) {
+ if (mLockdownTracker != null) {
+ mLockdownTracker.augmentNetworkInfo(state.networkInfo);
+ }
}
}
@@ -1253,8 +1259,8 @@
result.put(nai.network, nc);
}
- if (!mLockdownEnabled) {
- synchronized (mVpns) {
+ synchronized (mVpns) {
+ if (!mLockdownEnabled) {
Vpn vpn = mVpns.get(userId);
if (vpn != null) {
Network[] networks = vpn.getUnderlyingNetworks();
@@ -1580,9 +1586,11 @@
}
private Intent makeGeneralIntent(NetworkInfo info, String bcastType) {
- if (mLockdownTracker != null) {
- info = new NetworkInfo(info);
- mLockdownTracker.augmentNetworkInfo(info);
+ synchronized (mVpns) {
+ if (mLockdownTracker != null) {
+ info = new NetworkInfo(info);
+ mLockdownTracker.augmentNetworkInfo(info);
+ }
}
Intent intent = new Intent(bcastType);
@@ -2500,6 +2508,7 @@
private void handleRemoveNetworkRequest(final NetworkRequestInfo nri) {
nri.unlinkDeathRecipient();
mNetworkRequests.remove(nri.request);
+
synchronized (mUidToNetworkRequestCount) {
int requests = mUidToNetworkRequestCount.get(nri.mUid, 0);
if (requests < 1) {
@@ -2512,6 +2521,7 @@
mUidToNetworkRequestCount.put(nri.mUid, requests - 1);
}
}
+
mNetworkRequestInfoLogs.log("RELEASE " + nri);
if (nri.request.isRequest()) {
boolean wasKept = false;
@@ -3434,9 +3444,9 @@
public boolean prepareVpn(@Nullable String oldPackage, @Nullable String newPackage,
int userId) {
enforceCrossUserPermission(userId);
- throwIfLockdownEnabled();
synchronized (mVpns) {
+ throwIfLockdownEnabled();
Vpn vpn = mVpns.get(userId);
if (vpn != null) {
return vpn.prepare(oldPackage, newPackage);
@@ -3480,9 +3490,9 @@
*/
@Override
public ParcelFileDescriptor establishVpn(VpnConfig config) {
- throwIfLockdownEnabled();
int user = UserHandle.getUserId(Binder.getCallingUid());
synchronized (mVpns) {
+ throwIfLockdownEnabled();
return mVpns.get(user).establish(config);
}
}
@@ -3493,13 +3503,13 @@
*/
@Override
public void startLegacyVpn(VpnProfile profile) {
- throwIfLockdownEnabled();
+ int user = UserHandle.getUserId(Binder.getCallingUid());
final LinkProperties egress = getActiveLinkProperties();
if (egress == null) {
throw new IllegalStateException("Missing active network connection");
}
- int user = UserHandle.getUserId(Binder.getCallingUid());
synchronized (mVpns) {
+ throwIfLockdownEnabled();
mVpns.get(user).startLegacyVpn(profile, mKeyStore, egress);
}
}
@@ -3525,11 +3535,11 @@
@Override
public VpnInfo[] getAllVpnInfo() {
enforceConnectivityInternalPermission();
- if (mLockdownEnabled) {
- return new VpnInfo[0];
- }
-
synchronized (mVpns) {
+ if (mLockdownEnabled) {
+ return new VpnInfo[0];
+ }
+
List<VpnInfo> infoList = new ArrayList<>();
for (int i = 0; i < mVpns.size(); i++) {
VpnInfo info = createVpnInfo(mVpns.valueAt(i));
@@ -3594,33 +3604,33 @@
return false;
}
- // Tear down existing lockdown if profile was removed
- mLockdownEnabled = LockdownVpnTracker.isEnabled();
- if (mLockdownEnabled) {
- byte[] profileTag = mKeyStore.get(Credentials.LOCKDOWN_VPN);
- if (profileTag == null) {
- Slog.e(TAG, "Lockdown VPN configured but cannot be read from keystore");
- return false;
- }
- String profileName = new String(profileTag);
- final VpnProfile profile = VpnProfile.decode(
- profileName, mKeyStore.get(Credentials.VPN + profileName));
- if (profile == null) {
- Slog.e(TAG, "Lockdown VPN configured invalid profile " + profileName);
- setLockdownTracker(null);
- return true;
- }
- int user = UserHandle.getUserId(Binder.getCallingUid());
- synchronized (mVpns) {
+ synchronized (mVpns) {
+ // Tear down existing lockdown if profile was removed
+ mLockdownEnabled = LockdownVpnTracker.isEnabled();
+ if (mLockdownEnabled) {
+ byte[] profileTag = mKeyStore.get(Credentials.LOCKDOWN_VPN);
+ if (profileTag == null) {
+ Slog.e(TAG, "Lockdown VPN configured but cannot be read from keystore");
+ return false;
+ }
+ String profileName = new String(profileTag);
+ final VpnProfile profile = VpnProfile.decode(
+ profileName, mKeyStore.get(Credentials.VPN + profileName));
+ if (profile == null) {
+ Slog.e(TAG, "Lockdown VPN configured invalid profile " + profileName);
+ setLockdownTracker(null);
+ return true;
+ }
+ int user = UserHandle.getUserId(Binder.getCallingUid());
Vpn vpn = mVpns.get(user);
if (vpn == null) {
Slog.w(TAG, "VPN for user " + user + " not ready yet. Skipping lockdown");
return false;
}
setLockdownTracker(new LockdownVpnTracker(mContext, mNetd, this, vpn, profile));
+ } else {
+ setLockdownTracker(null);
}
- } else {
- setLockdownTracker(null);
}
return true;
@@ -3630,6 +3640,7 @@
* Internally set new {@link LockdownVpnTracker}, shutting down any existing
* {@link LockdownVpnTracker}. Can be {@code null} to disable lockdown.
*/
+ @GuardedBy("mVpns")
private void setLockdownTracker(LockdownVpnTracker tracker) {
// Shutdown any existing tracker
final LockdownVpnTracker existing = mLockdownTracker;
@@ -3644,6 +3655,7 @@
}
}
+ @GuardedBy("mVpns")
private void throwIfLockdownEnabled() {
if (mLockdownEnabled) {
throw new IllegalStateException("Unavailable in lockdown mode");
@@ -3691,12 +3703,12 @@
enforceConnectivityInternalPermission();
enforceCrossUserPermission(userId);
- // Can't set always-on VPN if legacy VPN is already in lockdown mode.
- if (LockdownVpnTracker.isEnabled()) {
- return false;
- }
-
synchronized (mVpns) {
+ // Can't set always-on VPN if legacy VPN is already in lockdown mode.
+ if (LockdownVpnTracker.isEnabled()) {
+ return false;
+ }
+
Vpn vpn = mVpns.get(userId);
if (vpn == null) {
Slog.w(TAG, "User " + userId + " has no Vpn configuration");
@@ -3872,9 +3884,9 @@
}
userVpn = new Vpn(mHandler.getLooper(), mContext, mNetd, userId);
mVpns.put(userId, userVpn);
- }
- if (mUserManager.getUserInfo(userId).isPrimary() && LockdownVpnTracker.isEnabled()) {
- updateLockdownVpn();
+ if (mUserManager.getUserInfo(userId).isPrimary() && LockdownVpnTracker.isEnabled()) {
+ updateLockdownVpn();
+ }
}
}
@@ -3911,11 +3923,13 @@
}
private void onUserUnlocked(int userId) {
- // User present may be sent because of an unlock, which might mean an unlocked keystore.
- if (mUserManager.getUserInfo(userId).isPrimary() && LockdownVpnTracker.isEnabled()) {
- updateLockdownVpn();
- } else {
- startAlwaysOnVpn(userId);
+ synchronized (mVpns) {
+ // User present may be sent because of an unlock, which might mean an unlocked keystore.
+ if (mUserManager.getUserInfo(userId).isPrimary() && LockdownVpnTracker.isEnabled()) {
+ updateLockdownVpn();
+ } else {
+ startAlwaysOnVpn(userId);
+ }
}
}
@@ -4595,51 +4609,67 @@
}
/**
- * Update the NetworkCapabilities for {@code networkAgent} to {@code networkCapabilities}
- * augmented with any stateful capabilities implied from {@code networkAgent}
- * (e.g., validated status and captive portal status).
- *
- * @param oldScore score of the network before any of the changes that prompted us
- * to call this function.
- * @param nai the network having its capabilities updated.
- * @param networkCapabilities the new network capabilities.
+ * Augments the NetworkCapabilities passed in by a NetworkAgent with capabilities that are
+ * maintained here that the NetworkAgent is not aware of (e.g., validated, captive portal,
+ * and foreground status).
*/
- private void updateCapabilities(
- int oldScore, NetworkAgentInfo nai, NetworkCapabilities networkCapabilities) {
+ private NetworkCapabilities mixInCapabilities(NetworkAgentInfo nai, NetworkCapabilities nc) {
// Once a NetworkAgent is connected, complain if some immutable capabilities are removed.
- if (nai.everConnected && !nai.networkCapabilities.satisfiedByImmutableNetworkCapabilities(
- networkCapabilities)) {
- // TODO: consider not complaining when a network agent degrade its capabilities if this
+ if (nai.everConnected &&
+ !nai.networkCapabilities.satisfiedByImmutableNetworkCapabilities(nc)) {
+ // TODO: consider not complaining when a network agent degrades its capabilities if this
// does not cause any request (that is not a listen) currently matching that agent to
// stop being matched by the updated agent.
- String diff = nai.networkCapabilities.describeImmutableDifferences(networkCapabilities);
+ String diff = nai.networkCapabilities.describeImmutableDifferences(nc);
if (!TextUtils.isEmpty(diff)) {
Slog.wtf(TAG, "BUG: " + nai + " lost immutable capabilities:" + diff);
}
}
// Don't modify caller's NetworkCapabilities.
- networkCapabilities = new NetworkCapabilities(networkCapabilities);
+ NetworkCapabilities newNc = new NetworkCapabilities(nc);
if (nai.lastValidated) {
- networkCapabilities.addCapability(NET_CAPABILITY_VALIDATED);
+ newNc.addCapability(NET_CAPABILITY_VALIDATED);
} else {
- networkCapabilities.removeCapability(NET_CAPABILITY_VALIDATED);
+ newNc.removeCapability(NET_CAPABILITY_VALIDATED);
}
if (nai.lastCaptivePortalDetected) {
- networkCapabilities.addCapability(NET_CAPABILITY_CAPTIVE_PORTAL);
+ newNc.addCapability(NET_CAPABILITY_CAPTIVE_PORTAL);
} else {
- networkCapabilities.removeCapability(NET_CAPABILITY_CAPTIVE_PORTAL);
+ newNc.removeCapability(NET_CAPABILITY_CAPTIVE_PORTAL);
}
if (nai.isBackgroundNetwork()) {
- networkCapabilities.removeCapability(NET_CAPABILITY_FOREGROUND);
+ newNc.removeCapability(NET_CAPABILITY_FOREGROUND);
} else {
- networkCapabilities.addCapability(NET_CAPABILITY_FOREGROUND);
+ newNc.addCapability(NET_CAPABILITY_FOREGROUND);
}
- if (Objects.equals(nai.networkCapabilities, networkCapabilities)) return;
+ return newNc;
+ }
+
+ /**
+ * Update the NetworkCapabilities for {@code nai} to {@code nc}. Specifically:
+ *
+ * 1. Calls mixInCapabilities to merge the passed-in NetworkCapabilities {@code nc} with the
+ * capabilities we manage and store in {@code nai}, such as validated status and captive
+ * portal status)
+ * 2. Takes action on the result: changes network permissions, sends CAP_CHANGED callbacks, and
+ * potentially triggers rematches.
+ * 3. Directly informs other network stack components (NetworkStatsService, VPNs, etc. of the
+ * change.)
+ *
+ * @param oldScore score of the network before any of the changes that prompted us
+ * to call this function.
+ * @param nai the network having its capabilities updated.
+ * @param nc the new network capabilities.
+ */
+ private void updateCapabilities(int oldScore, NetworkAgentInfo nai, NetworkCapabilities nc) {
+ NetworkCapabilities newNc = mixInCapabilities(nai, nc);
+
+ if (Objects.equals(nai.networkCapabilities, newNc)) return;
final String oldPermission = getNetworkPermission(nai.networkCapabilities);
- final String newPermission = getNetworkPermission(networkCapabilities);
+ final String newPermission = getNetworkPermission(newNc);
if (!Objects.equals(oldPermission, newPermission) && nai.created && !nai.isVPN()) {
try {
mNetd.setNetworkPermission(nai.network.netId, newPermission);
@@ -4651,11 +4681,10 @@
final NetworkCapabilities prevNc;
synchronized (nai) {
prevNc = nai.networkCapabilities;
- nai.networkCapabilities = networkCapabilities;
+ nai.networkCapabilities = newNc;
}
- if (nai.getCurrentScore() == oldScore &&
- networkCapabilities.equalRequestableCapabilities(prevNc)) {
+ if (nai.getCurrentScore() == oldScore && newNc.equalRequestableCapabilities(prevNc)) {
// If the requestable capabilities haven't changed, and the score hasn't changed, then
// the change we're processing can't affect any requests, it can only affect the listens
// on this network. We might have been called by rematchNetworkAndRequests when a
@@ -4671,15 +4700,15 @@
// Report changes that are interesting for network statistics tracking.
if (prevNc != null) {
final boolean meteredChanged = prevNc.hasCapability(NET_CAPABILITY_NOT_METERED) !=
- networkCapabilities.hasCapability(NET_CAPABILITY_NOT_METERED);
+ newNc.hasCapability(NET_CAPABILITY_NOT_METERED);
final boolean roamingChanged = prevNc.hasCapability(NET_CAPABILITY_NOT_ROAMING) !=
- networkCapabilities.hasCapability(NET_CAPABILITY_NOT_ROAMING);
+ newNc.hasCapability(NET_CAPABILITY_NOT_ROAMING);
if (meteredChanged || roamingChanged) {
notifyIfacesChangedForNetworkStats();
}
}
- if (!networkCapabilities.hasTransport(TRANSPORT_VPN)) {
+ if (!newNc.hasTransport(TRANSPORT_VPN)) {
// Tell VPNs about updated capabilities, since they may need to
// bubble those changes through.
synchronized (mVpns) {
@@ -5203,11 +5232,13 @@
}
private void notifyLockdownVpn(NetworkAgentInfo nai) {
- if (mLockdownTracker != null) {
- if (nai != null && nai.isVPN()) {
- mLockdownTracker.onVpnStateChanged(nai.networkInfo);
- } else {
- mLockdownTracker.onNetworkInfoChanged();
+ synchronized (mVpns) {
+ if (mLockdownTracker != null) {
+ if (nai != null && nai.isVPN()) {
+ mLockdownTracker.onVpnStateChanged(nai.networkInfo);
+ } else {
+ mLockdownTracker.onNetworkInfoChanged();
+ }
}
}
}
@@ -5437,28 +5468,28 @@
@Override
public boolean addVpnAddress(String address, int prefixLength) {
- throwIfLockdownEnabled();
int user = UserHandle.getUserId(Binder.getCallingUid());
synchronized (mVpns) {
+ throwIfLockdownEnabled();
return mVpns.get(user).addAddress(address, prefixLength);
}
}
@Override
public boolean removeVpnAddress(String address, int prefixLength) {
- throwIfLockdownEnabled();
int user = UserHandle.getUserId(Binder.getCallingUid());
synchronized (mVpns) {
+ throwIfLockdownEnabled();
return mVpns.get(user).removeAddress(address, prefixLength);
}
}
@Override
public boolean setUnderlyingNetworksForVpn(Network[] networks) {
- throwIfLockdownEnabled();
int user = UserHandle.getUserId(Binder.getCallingUid());
- boolean success;
+ final boolean success;
synchronized (mVpns) {
+ throwIfLockdownEnabled();
success = mVpns.get(user).setUnderlyingNetworks(networks);
}
if (success) {
@@ -5518,31 +5549,31 @@
setAlwaysOnVpnPackage(userId, null, false);
setVpnPackageAuthorization(alwaysOnPackage, userId, false);
}
- }
- // Turn Always-on VPN off
- if (mLockdownEnabled && userId == UserHandle.USER_SYSTEM) {
- final long ident = Binder.clearCallingIdentity();
- try {
- mKeyStore.delete(Credentials.LOCKDOWN_VPN);
- mLockdownEnabled = false;
- setLockdownTracker(null);
- } finally {
- Binder.restoreCallingIdentity(ident);
+ // Turn Always-on VPN off
+ if (mLockdownEnabled && userId == UserHandle.USER_SYSTEM) {
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ mKeyStore.delete(Credentials.LOCKDOWN_VPN);
+ mLockdownEnabled = false;
+ setLockdownTracker(null);
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
}
- }
- // Turn VPN off
- VpnConfig vpnConfig = getVpnConfig(userId);
- if (vpnConfig != null) {
- if (vpnConfig.legacy) {
- prepareVpn(VpnConfig.LEGACY_VPN, VpnConfig.LEGACY_VPN, userId);
- } else {
- // Prevent this app (packagename = vpnConfig.user) from initiating VPN connections
- // in the future without user intervention.
- setVpnPackageAuthorization(vpnConfig.user, userId, false);
+ // Turn VPN off
+ VpnConfig vpnConfig = getVpnConfig(userId);
+ if (vpnConfig != null) {
+ if (vpnConfig.legacy) {
+ prepareVpn(VpnConfig.LEGACY_VPN, VpnConfig.LEGACY_VPN, userId);
+ } else {
+ // Prevent this app (packagename = vpnConfig.user) from initiating
+ // VPN connections in the future without user intervention.
+ setVpnPackageAuthorization(vpnConfig.user, userId, false);
- prepareVpn(null, VpnConfig.LEGACY_VPN, userId);
+ prepareVpn(null, VpnConfig.LEGACY_VPN, userId);
+ }
}
}
}
diff --git a/services/core/java/com/android/server/NetworkManagementService.java b/services/core/java/com/android/server/NetworkManagementService.java
index 40e6d26..8f646e7 100644
--- a/services/core/java/com/android/server/NetworkManagementService.java
+++ b/services/core/java/com/android/server/NetworkManagementService.java
@@ -2508,12 +2508,16 @@
@Override
public void removeNetwork(int netId) {
- mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
+ mContext.enforceCallingOrSelfPermission(NETWORK_STACK, TAG);
try {
- mConnector.execute("network", "destroy", netId);
- } catch (NativeDaemonConnectorException e) {
- throw e.rethrowAsParcelableException();
+ mNetdService.networkDestroy(netId);
+ } catch (ServiceSpecificException e) {
+ Log.w(TAG, "removeNetwork(" + netId + "): ", e);
+ throw e;
+ } catch (RemoteException e) {
+ Log.w(TAG, "removeNetwork(" + netId + "): ", e);
+ throw e.rethrowAsRuntimeException();
}
}
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index dc2f2a5..7361e70 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -49,6 +49,7 @@
import android.content.pm.UserInfo;
import android.content.res.Configuration;
import android.content.res.ObbInfo;
+import android.database.ContentObserver;
import android.net.TrafficStats;
import android.net.Uri;
import android.os.Binder;
@@ -170,6 +171,10 @@
// Static direct instance pointer for the tightly-coupled idle service to use
static StorageManagerService sSelf = null;
+ /* Read during boot to decide whether to enable zram when available */
+ private static final String ZRAM_ENABLED_PROPERTY =
+ "persist.sys.zram_enabled";
+
public static class Lifecycle extends SystemService {
private StorageManagerService mStorageManagerService;
@@ -733,6 +738,41 @@
// Start scheduling nominally-daily fstrim operations
MountServiceIdler.scheduleIdlePass(mContext);
+
+ // Toggle zram-enable system property in response to settings
+ mContext.getContentResolver().registerContentObserver(
+ Settings.Global.getUriFor(Settings.Global.ZRAM_ENABLED),
+ false /*notifyForDescendants*/,
+ new ContentObserver(null /* current thread */) {
+ @Override
+ public void onChange(boolean selfChange) {
+ refreshZramSettings();
+ }
+ });
+ refreshZramSettings();
+ }
+
+ /**
+ * Update the zram_enabled system property (which init reads to
+ * decide whether to enable zram) to reflect the zram_enabled
+ * preference (which we can change for experimentation purposes).
+ */
+ private void refreshZramSettings() {
+ String propertyValue = SystemProperties.get(ZRAM_ENABLED_PROPERTY);
+ if ("".equals(propertyValue)) {
+ return; // System doesn't have zram toggling support
+ }
+ String desiredPropertyValue =
+ Settings.Global.getInt(mContext.getContentResolver(),
+ Settings.Global.ZRAM_ENABLED,
+ 1) != 0
+ ? "1" : "0";
+ if (!desiredPropertyValue.equals(propertyValue)) {
+ // Avoid redundant disk writes by setting only if we're
+ // changing the property value. There's no race: we're the
+ // sole writer.
+ SystemProperties.set(ZRAM_ENABLED_PROPERTY, desiredPropertyValue);
+ }
}
/**
diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java
index 831c9cb..6747be3 100644
--- a/services/core/java/com/android/server/TelephonyRegistry.java
+++ b/services/core/java/com/android/server/TelephonyRegistry.java
@@ -147,6 +147,8 @@
private int[] mDataActivationState;
+ private boolean[] mUserMobileDataState;
+
private SignalStrength[] mSignalStrength;
private boolean[] mMessageWaiting;
@@ -304,6 +306,7 @@
mServiceState = new ServiceState[numPhones];
mVoiceActivationState = new int[numPhones];
mDataActivationState = new int[numPhones];
+ mUserMobileDataState = new boolean[numPhones];
mSignalStrength = new SignalStrength[numPhones];
mMessageWaiting = new boolean[numPhones];
mCallForwarding = new boolean[numPhones];
@@ -320,6 +323,7 @@
mCallIncomingNumber[i] = "";
mServiceState[i] = new ServiceState();
mSignalStrength[i] = new SignalStrength();
+ mUserMobileDataState[i] = false;
mMessageWaiting[i] = false;
mCallForwarding[i] = false;
mCellLocation[i] = new Bundle();
@@ -656,6 +660,13 @@
remove(r.binder);
}
}
+ if ((events & PhoneStateListener.LISTEN_USER_MOBILE_DATA_STATE) != 0) {
+ try {
+ r.callback.onUserMobileDataStateChanged(mUserMobileDataState[phoneId]);
+ } catch (RemoteException ex) {
+ remove(r.binder);
+ }
+ }
}
}
} else {
@@ -1012,6 +1023,33 @@
}
}
+ public void notifyUserMobileDataStateChangedForPhoneId(int phoneId, int subId, boolean state) {
+ if (!checkNotifyPermission("notifyUserMobileDataStateChanged()")) {
+ return;
+ }
+ if (VDBG) {
+ log("notifyUserMobileDataStateChangedForSubscriberPhoneID: subId=" + phoneId
+ + " state=" + state);
+ }
+ synchronized (mRecords) {
+ if (validatePhoneId(phoneId)) {
+ mMessageWaiting[phoneId] = state;
+ for (Record r : mRecords) {
+ if (r.matchPhoneStateListenerEvent(
+ PhoneStateListener.LISTEN_USER_MOBILE_DATA_STATE) &&
+ idMatch(r.subId, subId, phoneId)) {
+ try {
+ r.callback.onUserMobileDataStateChanged(state);
+ } catch (RemoteException ex) {
+ mRemoveList.add(r.binder);
+ }
+ }
+ }
+ }
+ handleRemoveListLocked();
+ }
+ }
+
public void notifyCallForwardingChanged(boolean cfi) {
notifyCallForwardingChangedForSubscriber(SubscriptionManager.DEFAULT_SUBSCRIPTION_ID, cfi);
}
@@ -1374,6 +1412,7 @@
pw.println("mServiceState=" + mServiceState[i]);
pw.println("mVoiceActivationState= " + mVoiceActivationState[i]);
pw.println("mDataActivationState= " + mDataActivationState[i]);
+ pw.println("mUserMobileDataState= " + mUserMobileDataState[i]);
pw.println("mSignalStrength=" + mSignalStrength[i]);
pw.println("mMessageWaiting=" + mMessageWaiting[i]);
pw.println("mCallForwarding=" + mCallForwarding[i]);
@@ -1755,6 +1794,18 @@
}
}
+ if ((events & PhoneStateListener.LISTEN_USER_MOBILE_DATA_STATE) != 0) {
+ try {
+ if (VDBG) {
+ log("checkPossibleMissNotify: onUserMobileDataStateChanged phoneId="
+ + phoneId + " umds=" + mUserMobileDataState[phoneId]);
+ }
+ r.callback.onUserMobileDataStateChanged(mUserMobileDataState[phoneId]);
+ } catch (RemoteException ex) {
+ mRemoveList.add(r.binder);
+ }
+ }
+
if ((events & PhoneStateListener.LISTEN_MESSAGE_WAITING_INDICATOR) != 0) {
try {
if (VDBG) {
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 6565187..c9b9a40 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -8401,7 +8401,7 @@
public void onDismissCancelled() throws RemoteException {
// Do nothing
}
- });
+ }, null /* message */);
} catch (RemoteException e) {
// Local call
}
@@ -25130,6 +25130,11 @@
public void registerScreenObserver(ScreenObserver observer) {
mScreenObservers.add(observer);
}
+
+ @Override
+ public boolean canStartMoreUsers() {
+ return mUserController.canStartMoreUsers();
+ }
}
/**
@@ -25277,11 +25282,15 @@
}
@Override
- public void dismissKeyguard(IBinder token, IKeyguardDismissCallback callback)
- throws RemoteException {
+ public void dismissKeyguard(IBinder token, IKeyguardDismissCallback callback,
+ CharSequence message) throws RemoteException {
+ if (message != null) {
+ enforceCallingPermission(permission.SHOW_KEYGUARD_MESSAGE,
+ "dismissKeyguard()");
+ }
final long callingId = Binder.clearCallingIdentity();
try {
- mKeyguardController.dismissKeyguard(token, callback);
+ mKeyguardController.dismissKeyguard(token, callback, message);
} finally {
Binder.restoreCallingIdentity(callingId);
}
@@ -25336,6 +25345,10 @@
}
}
}
+ if (updateFrameworkRes && mWindowManager != null) {
+ ActivityThread.currentActivityThread().getExecutor().execute(
+ mWindowManager::onOverlayChanged);
+ }
}
/**
diff --git a/services/core/java/com/android/server/am/ActivityStartInterceptor.java b/services/core/java/com/android/server/am/ActivityStartInterceptor.java
index 6684f25..0480646 100644
--- a/services/core/java/com/android/server/am/ActivityStartInterceptor.java
+++ b/services/core/java/com/android/server/am/ActivityStartInterceptor.java
@@ -30,6 +30,7 @@
import static android.content.pm.ApplicationInfo.FLAG_SUSPENDED;
import android.app.ActivityOptions;
+import android.app.AppGlobals;
import android.app.KeyguardManager;
import android.app.admin.DevicePolicyManagerInternal;
import android.content.Context;
@@ -40,10 +41,12 @@
import android.content.pm.ResolveInfo;
import android.content.pm.UserInfo;
import android.os.Binder;
+import android.os.RemoteException;
import android.os.UserHandle;
import android.os.UserManager;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.app.HarmfulAppWarningActivity;
import com.android.internal.app.UnlaunchableAppActivity;
import com.android.server.LocalServices;
@@ -115,6 +118,15 @@
mCallingPackage = callingPackage;
}
+ private IntentSender createIntentSenderForOriginalIntent(int callingUid, int flags) {
+ 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*/);
+ return new IntentSender(target);
+ }
+
/**
* Intercept the launch intent based on various signals. If an interception happened the
* internal variables get assigned and need to be read explicitly by the caller.
@@ -144,6 +156,11 @@
// be unlocked when profile's user is running.
return true;
}
+ if (interceptHarmfulAppIfNeeded()) {
+ // If the app has a "harmful app" warning associated with it, we should ask to uninstall
+ // before issuing the work challenge.
+ return true;
+ }
return interceptWorkProfileChallengeIfNeeded();
}
@@ -152,13 +169,10 @@
if (!mUserManager.isQuietModeEnabled(UserHandle.of(mUserId))) {
return false;
}
- IIntentSender target = mService.getIntentSenderLocked(
- INTENT_SENDER_ACTIVITY, mCallingPackage, mCallingUid, mUserId, null, null, 0,
- new Intent[] {mIntent}, new String[] {mResolvedType},
- FLAG_CANCEL_CURRENT | FLAG_ONE_SHOT, null);
+ IntentSender target = createIntentSenderForOriginalIntent(mCallingUid,
+ FLAG_CANCEL_CURRENT | FLAG_ONE_SHOT);
- mIntent = UnlaunchableAppActivity.createInQuietModeDialogIntent(mUserId,
- new IntentSender(target));
+ mIntent = UnlaunchableAppActivity.createInQuietModeDialogIntent(mUserId, target);
mCallingPid = mRealCallingPid;
mCallingUid = mRealCallingUid;
mResolvedType = null;
@@ -240,11 +254,8 @@
return null;
}
// TODO(b/28935539): should allow certain activities to bypass work challenge
- final IIntentSender target = mService.getIntentSenderLocked(
- INTENT_SENDER_ACTIVITY, callingPackage,
- Binder.getCallingUid(), userId, null, null, 0, new Intent[]{ intent },
- new String[]{ resolvedType },
- FLAG_CANCEL_CURRENT | FLAG_ONE_SHOT | FLAG_IMMUTABLE, null);
+ final IntentSender target = createIntentSenderForOriginalIntent(Binder.getCallingUid(),
+ FLAG_CANCEL_CURRENT | FLAG_ONE_SHOT | FLAG_IMMUTABLE);
final KeyguardManager km = (KeyguardManager) mServiceContext
.getSystemService(KEYGUARD_SERVICE);
final Intent newIntent = km.createConfirmDeviceCredentialIntent(null, null, userId);
@@ -254,8 +265,36 @@
newIntent.setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS |
FLAG_ACTIVITY_TASK_ON_HOME);
newIntent.putExtra(EXTRA_PACKAGE_NAME, aInfo.packageName);
- newIntent.putExtra(EXTRA_INTENT, new IntentSender(target));
+ newIntent.putExtra(EXTRA_INTENT, target);
return newIntent;
}
+ private boolean interceptHarmfulAppIfNeeded() {
+ CharSequence harmfulAppWarning;
+ try {
+ harmfulAppWarning = AppGlobals.getPackageManager().getHarmfulAppWarning(
+ mAInfo.packageName, mUserId);
+ } catch (RemoteException e) {
+ return false;
+ }
+
+ if (harmfulAppWarning == null) {
+ return false;
+ }
+
+ final IntentSender target = createIntentSenderForOriginalIntent(mCallingUid,
+ FLAG_CANCEL_CURRENT | FLAG_ONE_SHOT | FLAG_IMMUTABLE);
+
+ mIntent = HarmfulAppWarningActivity.createHarmfulAppWarningIntent(mServiceContext,
+ mAInfo.packageName, target, harmfulAppWarning);
+
+ mCallingPid = mRealCallingPid;
+ mCallingUid = mRealCallingUid;
+ mResolvedType = null;
+
+ mRInfo = mSupervisor.resolveIntent(mIntent, mResolvedType, mUserId);
+ mAInfo = mSupervisor.resolveActivity(mIntent, mRInfo, mStartFlags, null /*profilerInfo*/);
+ return true;
+ }
+
}
diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java
index c9aa9a2..81e8eb0 100644
--- a/services/core/java/com/android/server/am/BatteryStatsService.java
+++ b/services/core/java/com/android/server/am/BatteryStatsService.java
@@ -933,21 +933,6 @@
}
}
- public void noteWifiMulticastEnabledFromSource(WorkSource ws) {
- enforceCallingPermission();
- synchronized (mStats) {
- mStats.noteWifiMulticastEnabledFromSourceLocked(ws);
- }
- }
-
- @Override
- public void noteWifiMulticastDisabledFromSource(WorkSource ws) {
- enforceCallingPermission();
- synchronized (mStats) {
- mStats.noteWifiMulticastDisabledFromSourceLocked(ws);
- }
- }
-
@Override
public void noteNetworkInterfaceType(String iface, int networkType) {
enforceCallingPermission();
diff --git a/services/core/java/com/android/server/am/ClientLifecycleManager.java b/services/core/java/com/android/server/am/ClientLifecycleManager.java
index 014f708..ae8d9fc 100644
--- a/services/core/java/com/android/server/am/ClientLifecycleManager.java
+++ b/services/core/java/com/android/server/am/ClientLifecycleManager.java
@@ -21,6 +21,7 @@
import android.app.servertransaction.ClientTransaction;
import android.app.servertransaction.ClientTransactionItem;
import android.app.servertransaction.ActivityLifecycleItem;
+import android.os.Binder;
import android.os.IBinder;
import android.os.RemoteException;
@@ -42,9 +43,14 @@
* @see ClientTransaction
*/
void scheduleTransaction(ClientTransaction transaction) throws RemoteException {
+ final IApplicationThread client = transaction.getClient();
transaction.schedule();
- // TODO: b/70616950
- //transaction.recycle();
+ if (!(client instanceof Binder)) {
+ // If client is not an instance of Binder - it's a remote call and at this point it is
+ // safe to recycle the object. All objects used for local calls will be recycled after
+ // the transaction is executed on client in ActivityThread.
+ transaction.recycle();
+ }
}
/**
diff --git a/services/core/java/com/android/server/am/GlobalSettingsToPropertiesMapper.java b/services/core/java/com/android/server/am/GlobalSettingsToPropertiesMapper.java
index 5632fc0..c9afc17 100644
--- a/services/core/java/com/android/server/am/GlobalSettingsToPropertiesMapper.java
+++ b/services/core/java/com/android/server/am/GlobalSettingsToPropertiesMapper.java
@@ -37,7 +37,8 @@
private static final String[][] sGlobalSettingsMapping = new String[][] {
// List mapping entries in the following format:
- // {Settings.Global.SETTING_NAME, "system_property_name"},
+ // {Settings.Global.SETTING_NAME, "system_property_name"},
+ {Settings.Global.SYS_VDSO, "sys.vdso"},
};
diff --git a/services/core/java/com/android/server/am/KeyguardController.java b/services/core/java/com/android/server/am/KeyguardController.java
index 35f4f25..4d7bc1e 100644
--- a/services/core/java/com/android/server/am/KeyguardController.java
+++ b/services/core/java/com/android/server/am/KeyguardController.java
@@ -150,7 +150,7 @@
}
}
- void dismissKeyguard(IBinder token, IKeyguardDismissCallback callback) {
+ void dismissKeyguard(IBinder token, IKeyguardDismissCallback callback, CharSequence message) {
final ActivityRecord activityRecord = ActivityRecord.forTokenLocked(token);
if (activityRecord == null || !activityRecord.visibleIgnoringKeyguard) {
failCallback(callback);
@@ -164,7 +164,7 @@
mStackSupervisor.wakeUp("dismissKeyguard");
}
- mWindowManager.dismissKeyguard(callback);
+ mWindowManager.dismissKeyguard(callback, message);
}
private void setKeyguardGoingAway(boolean keyguardGoingAway) {
@@ -304,7 +304,7 @@
// insecure case, we actually show it on top of the lockscreen. See #canShowWhileOccluded.
if (!mOccluded && mDismissingKeyguardActivity != null
&& mWindowManager.isKeyguardSecure()) {
- mWindowManager.dismissKeyguard(null /* callback */);
+ mWindowManager.dismissKeyguard(null /* callback */, null /* message */);
mDismissalRequested = true;
// If we are about to unocclude the Keyguard, but we can dismiss it without security,
diff --git a/services/core/java/com/android/server/am/LockTaskController.java b/services/core/java/com/android/server/am/LockTaskController.java
index ba3e25a..21f9135 100644
--- a/services/core/java/com/android/server/am/LockTaskController.java
+++ b/services/core/java/com/android/server/am/LockTaskController.java
@@ -752,7 +752,7 @@
USER_CURRENT) != 0;
if (shouldLockKeyguard) {
mWindowManager.lockNow(null);
- mWindowManager.dismissKeyguard(null /* callback */);
+ mWindowManager.dismissKeyguard(null /* callback */, null /* message */);
getLockPatternUtils().requireCredentialEntry(USER_ALL);
}
} catch (Settings.SettingNotFoundException e) {
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index c4fdffa..a327a01 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -101,6 +101,7 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
+import java.util.Iterator;
import java.util.List;
import java.util.Objects;
import java.util.Set;
@@ -249,39 +250,51 @@
}
}
- void stopRunningUsersLU(int maxRunningUsers) {
- int currentlyRunning = mUserLru.size();
- int i = 0;
- while (currentlyRunning > maxRunningUsers && i < mUserLru.size()) {
- Integer oldUserId = mUserLru.get(i);
- UserState oldUss = mStartedUsers.get(oldUserId);
- if (oldUss == null) {
+ List<Integer> getRunningUsersLU() {
+ ArrayList<Integer> runningUsers = new ArrayList<>();
+ for (Integer userId : mUserLru) {
+ UserState uss = mStartedUsers.get(userId);
+ if (uss == null) {
// Shouldn't happen, but be sane if it does.
- mUserLru.remove(i);
- currentlyRunning--;
continue;
}
- if (oldUss.state == UserState.STATE_STOPPING
- || oldUss.state == UserState.STATE_SHUTDOWN) {
+ if (uss.state == UserState.STATE_STOPPING
+ || uss.state == UserState.STATE_SHUTDOWN) {
// This user is already stopping, doesn't count.
- currentlyRunning--;
- i++;
continue;
}
- if (oldUserId == UserHandle.USER_SYSTEM || oldUserId == mCurrentUserId) {
- // Owner/System user and current user can't be stopped. We count it as running
- // when it is not a pure system user.
- if (UserInfo.isSystemOnly(oldUserId)) {
- currentlyRunning--;
+ if (userId == UserHandle.USER_SYSTEM) {
+ // We only count system user as running when it is not a pure system user.
+ if (UserInfo.isSystemOnly(userId)) {
+ continue;
}
- i++;
+ }
+ runningUsers.add(userId);
+ }
+ return runningUsers;
+ }
+
+ void stopRunningUsersLU(int maxRunningUsers) {
+ List<Integer> currentlyRunning = getRunningUsersLU();
+ Iterator<Integer> iterator = currentlyRunning.iterator();
+ while (currentlyRunning.size() > maxRunningUsers && iterator.hasNext()) {
+ Integer userId = iterator.next();
+ if (userId == UserHandle.USER_SYSTEM || userId == mCurrentUserId) {
+ // Owner/System user and current user can't be stopped
continue;
}
- // This is a user to be stopped.
- if (stopUsersLU(oldUserId, false, null) == USER_OP_SUCCESS) {
- currentlyRunning--;
+ if (stopUsersLU(userId, false, null) == USER_OP_SUCCESS) {
+ iterator.remove();
}
- i++;
+ }
+ }
+
+ /**
+ * Returns if more users can be started without stopping currently running users.
+ */
+ boolean canStartMoreUsers() {
+ synchronized (mLock) {
+ return getRunningUsersLU().size() < mMaxRunningUsers;
}
}
@@ -768,34 +781,23 @@
/**
* Stops the guest or ephemeral user if it has gone to the background.
*/
- private void stopGuestOrEphemeralUserIfBackground() {
- IntArray userIds = new IntArray();
- synchronized (mLock) {
- final int num = mUserLru.size();
- for (int i = 0; i < num; i++) {
- Integer oldUserId = mUserLru.get(i);
- UserState oldUss = mStartedUsers.get(oldUserId);
- if (oldUserId == UserHandle.USER_SYSTEM || oldUserId == mCurrentUserId
- || oldUss.state == UserState.STATE_STOPPING
- || oldUss.state == UserState.STATE_SHUTDOWN) {
- continue;
- }
- userIds.add(oldUserId);
- }
+ private void stopGuestOrEphemeralUserIfBackground(int oldUserId) {
+ if (DEBUG_MU) Slog.i(TAG, "Stop guest or ephemeral user if background: " + oldUserId);
+ UserState oldUss = mStartedUsers.get(oldUserId);
+ if (oldUserId == UserHandle.USER_SYSTEM || oldUserId == mCurrentUserId
+ || oldUss.state == UserState.STATE_STOPPING
+ || oldUss.state == UserState.STATE_SHUTDOWN) {
+ return;
}
- final int userIdsSize = userIds.size();
- for (int i = 0; i < userIdsSize; i++) {
- int oldUserId = userIds.get(i);
- UserInfo userInfo = getUserInfo(oldUserId);
- if (userInfo.isEphemeral()) {
- LocalServices.getService(UserManagerInternal.class).onEphemeralUserStop(oldUserId);
- }
- if (userInfo.isGuest() || userInfo.isEphemeral()) {
- // This is a user to be stopped.
- synchronized (mLock) {
- stopUsersLU(oldUserId, true, null);
- }
- break;
+
+ UserInfo userInfo = getUserInfo(oldUserId);
+ if (userInfo.isEphemeral()) {
+ LocalServices.getService(UserManagerInternal.class).onEphemeralUserStop(oldUserId);
+ }
+ if (userInfo.isGuest() || userInfo.isEphemeral()) {
+ // This is a user to be stopped.
+ synchronized (mLock) {
+ stopUsersLU(oldUserId, true, null);
}
}
}
@@ -1333,7 +1335,7 @@
mHandler.removeMessages(REPORT_USER_SWITCH_COMPLETE_MSG);
mHandler.sendMessage(mHandler.obtainMessage(REPORT_USER_SWITCH_COMPLETE_MSG,
newUserId, 0));
- stopGuestOrEphemeralUserIfBackground();
+ stopGuestOrEphemeralUserIfBackground(oldUserId);
stopBackgroundUsersIfEnforced(oldUserId);
}
diff --git a/services/core/java/com/android/server/broadcastradio/BroadcastRadioService.java b/services/core/java/com/android/server/broadcastradio/BroadcastRadioService.java
index 7d3b670..10e6cad 100644
--- a/services/core/java/com/android/server/broadcastradio/BroadcastRadioService.java
+++ b/services/core/java/com/android/server/broadcastradio/BroadcastRadioService.java
@@ -25,6 +25,7 @@
import android.hardware.radio.ITunerCallback;
import android.hardware.radio.RadioManager;
import android.os.ParcelableException;
+import android.os.RemoteException;
import android.util.Slog;
import com.android.server.SystemService;
@@ -86,7 +87,7 @@
@Override
public ITuner openTuner(int moduleId, RadioManager.BandConfig bandConfig,
- boolean withAudio, ITunerCallback callback) {
+ boolean withAudio, ITunerCallback callback) throws RemoteException {
Slog.i(TAG, "openTuner(" + moduleId + ", _, " + withAudio + ", _)");
enforcePolicyAccess();
if (callback == null) {
diff --git a/services/core/java/com/android/server/broadcastradio/hal1/Tuner.java b/services/core/java/com/android/server/broadcastradio/hal1/Tuner.java
index e5090ed..f9b35f5 100644
--- a/services/core/java/com/android/server/broadcastradio/hal1/Tuner.java
+++ b/services/core/java/com/android/server/broadcastradio/hal1/Tuner.java
@@ -21,6 +21,7 @@
import android.graphics.BitmapFactory;
import android.hardware.radio.ITuner;
import android.hardware.radio.ITunerCallback;
+import android.hardware.radio.ProgramList;
import android.hardware.radio.ProgramSelector;
import android.hardware.radio.RadioManager;
import android.os.IBinder;
@@ -249,8 +250,7 @@
}
}
- @Override
- public List<RadioManager.ProgramInfo> getProgramList(Map vendorFilter) {
+ List<RadioManager.ProgramInfo> getProgramList(Map vendorFilter) {
Map<String, String> sFilter = vendorFilter;
synchronized (mLock) {
checkNotClosedLocked();
@@ -263,6 +263,16 @@
}
@Override
+ public void startProgramListUpdates(ProgramList.Filter filter) {
+ mTunerCallback.startProgramListUpdates(filter);
+ }
+
+ @Override
+ public void stopProgramListUpdates() {
+ mTunerCallback.stopProgramListUpdates();
+ }
+
+ @Override
public boolean isConfigFlagSupported(int flag) {
return flag == RadioManager.CONFIG_FORCE_ANALOG;
}
diff --git a/services/core/java/com/android/server/broadcastradio/hal1/TunerCallback.java b/services/core/java/com/android/server/broadcastradio/hal1/TunerCallback.java
index 673ff88..18f56ed 100644
--- a/services/core/java/com/android/server/broadcastradio/hal1/TunerCallback.java
+++ b/services/core/java/com/android/server/broadcastradio/hal1/TunerCallback.java
@@ -19,6 +19,7 @@
import android.annotation.NonNull;
import android.hardware.radio.ITuner;
import android.hardware.radio.ITunerCallback;
+import android.hardware.radio.ProgramList;
import android.hardware.radio.RadioManager;
import android.hardware.radio.RadioMetadata;
import android.hardware.radio.RadioTuner;
@@ -28,6 +29,10 @@
import java.util.List;
import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.stream.Collectors;
class TunerCallback implements ITunerCallback {
private static final String TAG = "BroadcastRadioService.TunerCallback";
@@ -40,6 +45,8 @@
@NonNull private final Tuner mTuner;
@NonNull private final ITunerCallback mClientCallback;
+ private final AtomicReference<ProgramList.Filter> mProgramListFilter = new AtomicReference<>();
+
TunerCallback(@NonNull Tuner tuner, @NonNull ITunerCallback clientCallback, int halRev) {
mTuner = tuner;
mClientCallback = clientCallback;
@@ -78,6 +85,15 @@
mTuner.close();
}
+ void startProgramListUpdates(@NonNull ProgramList.Filter filter) {
+ mProgramListFilter.set(Objects.requireNonNull(filter));
+ sendProgramListUpdate();
+ }
+
+ void stopProgramListUpdates() {
+ mProgramListFilter.set(null);
+ }
+
@Override
public void onError(int status) {
dispatch(() -> mClientCallback.onError(status));
@@ -121,6 +137,28 @@
@Override
public void onProgramListChanged() {
dispatch(() -> mClientCallback.onProgramListChanged());
+ sendProgramListUpdate();
+ }
+
+ private void sendProgramListUpdate() {
+ ProgramList.Filter filter = mProgramListFilter.get();
+ if (filter == null) return;
+
+ List<RadioManager.ProgramInfo> modified;
+ try {
+ modified = mTuner.getProgramList(filter.getVendorFilter());
+ } catch (IllegalStateException ex) {
+ Slog.d(TAG, "Program list not ready yet");
+ return;
+ }
+ Set<RadioManager.ProgramInfo> modifiedSet = modified.stream().collect(Collectors.toSet());
+ ProgramList.Chunk chunk = new ProgramList.Chunk(true, true, modifiedSet, null);
+ dispatch(() -> mClientCallback.onProgramListUpdated(chunk));
+ }
+
+ @Override
+ public void onProgramListUpdated(ProgramList.Chunk chunk) {
+ dispatch(() -> mClientCallback.onProgramListUpdated(chunk));
}
@Override
diff --git a/services/core/java/com/android/server/broadcastradio/hal2/BroadcastRadioService.java b/services/core/java/com/android/server/broadcastradio/hal2/BroadcastRadioService.java
index 9158ff0..fc9a5d6 100644
--- a/services/core/java/com/android/server/broadcastradio/hal2/BroadcastRadioService.java
+++ b/services/core/java/com/android/server/broadcastradio/hal2/BroadcastRadioService.java
@@ -82,7 +82,7 @@
}
public ITuner openSession(int moduleId, @Nullable RadioManager.BandConfig legacyConfig,
- boolean withAudio, @NonNull ITunerCallback callback) {
+ boolean withAudio, @NonNull ITunerCallback callback) throws RemoteException {
Objects.requireNonNull(callback);
if (!withAudio) {
diff --git a/services/core/java/com/android/server/broadcastradio/hal2/Convert.java b/services/core/java/com/android/server/broadcastradio/hal2/Convert.java
index 2c129bb..60a927c 100644
--- a/services/core/java/com/android/server/broadcastradio/hal2/Convert.java
+++ b/services/core/java/com/android/server/broadcastradio/hal2/Convert.java
@@ -20,15 +20,22 @@
import android.annotation.Nullable;
import android.hardware.broadcastradio.V2_0.AmFmBandRange;
import android.hardware.broadcastradio.V2_0.AmFmRegionConfig;
+import android.hardware.broadcastradio.V2_0.ProgramFilter;
+import android.hardware.broadcastradio.V2_0.ProgramIdentifier;
+import android.hardware.broadcastradio.V2_0.ProgramInfo;
+import android.hardware.broadcastradio.V2_0.ProgramInfoFlags;
+import android.hardware.broadcastradio.V2_0.ProgramListChunk;
import android.hardware.broadcastradio.V2_0.Properties;
import android.hardware.broadcastradio.V2_0.Result;
import android.hardware.broadcastradio.V2_0.VendorKeyValue;
+import android.hardware.radio.ProgramList;
import android.hardware.radio.ProgramSelector;
import android.hardware.radio.RadioManager;
import android.os.ParcelableException;
import android.util.Slog;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
@@ -36,6 +43,7 @@
import java.util.Map;
import java.util.Objects;
import java.util.Set;
+import java.util.stream.Collectors;
class Convert {
private static final String TAG = "BcRadio2Srv.convert";
@@ -78,43 +86,52 @@
return map;
}
+ private static @ProgramSelector.ProgramType int identifierTypeToProgramType(
+ @ProgramSelector.IdentifierType int idType) {
+ switch (idType) {
+ case ProgramSelector.IDENTIFIER_TYPE_AMFM_FREQUENCY:
+ case ProgramSelector.IDENTIFIER_TYPE_RDS_PI:
+ // TODO(b/69958423): verify AM/FM with frequency range
+ return ProgramSelector.PROGRAM_TYPE_FM;
+ case ProgramSelector.IDENTIFIER_TYPE_HD_STATION_ID_EXT:
+ // TODO(b/69958423): verify AM/FM with frequency range
+ return ProgramSelector.PROGRAM_TYPE_FM_HD;
+ case ProgramSelector.IDENTIFIER_TYPE_DAB_SIDECC:
+ case ProgramSelector.IDENTIFIER_TYPE_DAB_ENSEMBLE:
+ case ProgramSelector.IDENTIFIER_TYPE_DAB_SCID:
+ case ProgramSelector.IDENTIFIER_TYPE_DAB_FREQUENCY:
+ return ProgramSelector.PROGRAM_TYPE_DAB;
+ case ProgramSelector.IDENTIFIER_TYPE_DRMO_SERVICE_ID:
+ case ProgramSelector.IDENTIFIER_TYPE_DRMO_FREQUENCY:
+ return ProgramSelector.PROGRAM_TYPE_DRMO;
+ case ProgramSelector.IDENTIFIER_TYPE_SXM_SERVICE_ID:
+ case ProgramSelector.IDENTIFIER_TYPE_SXM_CHANNEL:
+ return ProgramSelector.PROGRAM_TYPE_SXM;
+ }
+ if (idType >= ProgramSelector.IDENTIFIER_TYPE_VENDOR_PRIMARY_START
+ && idType <= ProgramSelector.IDENTIFIER_TYPE_VENDOR_PRIMARY_END) {
+ return idType;
+ }
+ return ProgramSelector.PROGRAM_TYPE_INVALID;
+ }
+
private static @NonNull int[]
identifierTypesToProgramTypes(@NonNull int[] idTypes) {
Set<Integer> pTypes = new HashSet<>();
for (int idType : idTypes) {
- switch (idType) {
- case ProgramSelector.IDENTIFIER_TYPE_AMFM_FREQUENCY:
- case ProgramSelector.IDENTIFIER_TYPE_RDS_PI:
- // TODO(b/69958423): verify AM/FM with region info
- pTypes.add(ProgramSelector.PROGRAM_TYPE_AM);
- pTypes.add(ProgramSelector.PROGRAM_TYPE_FM);
- break;
- case ProgramSelector.IDENTIFIER_TYPE_HD_STATION_ID_EXT:
- // TODO(b/69958423): verify AM/FM with region info
- pTypes.add(ProgramSelector.PROGRAM_TYPE_AM_HD);
- pTypes.add(ProgramSelector.PROGRAM_TYPE_FM_HD);
- break;
- case ProgramSelector.IDENTIFIER_TYPE_DAB_SIDECC:
- case ProgramSelector.IDENTIFIER_TYPE_DAB_ENSEMBLE:
- case ProgramSelector.IDENTIFIER_TYPE_DAB_SCID:
- case ProgramSelector.IDENTIFIER_TYPE_DAB_FREQUENCY:
- pTypes.add(ProgramSelector.PROGRAM_TYPE_DAB);
- break;
- case ProgramSelector.IDENTIFIER_TYPE_DRMO_SERVICE_ID:
- case ProgramSelector.IDENTIFIER_TYPE_DRMO_FREQUENCY:
- pTypes.add(ProgramSelector.PROGRAM_TYPE_DRMO);
- break;
- case ProgramSelector.IDENTIFIER_TYPE_SXM_SERVICE_ID:
- case ProgramSelector.IDENTIFIER_TYPE_SXM_CHANNEL:
- pTypes.add(ProgramSelector.PROGRAM_TYPE_SXM);
- break;
- default:
- break;
+ int pType = identifierTypeToProgramType(idType);
+
+ if (pType == ProgramSelector.PROGRAM_TYPE_INVALID) continue;
+
+ pTypes.add(pType);
+ if (pType == ProgramSelector.PROGRAM_TYPE_FM) {
+ // TODO(b/69958423): verify AM/FM with region info
+ pTypes.add(ProgramSelector.PROGRAM_TYPE_AM);
}
- if (idType >= ProgramSelector.IDENTIFIER_TYPE_VENDOR_PRIMARY_START
- && idType <= ProgramSelector.IDENTIFIER_TYPE_VENDOR_PRIMARY_END) {
- pTypes.add(idType);
+ if (pType == ProgramSelector.PROGRAM_TYPE_FM_HD) {
+ // TODO(b/69958423): verify AM/FM with region info
+ pTypes.add(ProgramSelector.PROGRAM_TYPE_AM_HD);
}
}
@@ -189,6 +206,64 @@
false, // isBgScanSupported is deprecated
supportedProgramTypes,
supportedIdentifierTypes,
- vendorInfoFromHal(prop.vendorInfo));
+ vendorInfoFromHal(prop.vendorInfo)
+ );
+ }
+
+ static @NonNull ProgramIdentifier programIdentifierToHal(
+ @NonNull ProgramSelector.Identifier id) {
+ ProgramIdentifier hwId = new ProgramIdentifier();
+ hwId.type = id.getType();
+ hwId.value = id.getValue();
+ return hwId;
+ }
+
+ static @NonNull ProgramSelector.Identifier programIdentifierFromHal(@NonNull ProgramIdentifier id) {
+ return new ProgramSelector.Identifier(id.type, id.value);
+ }
+
+ static @NonNull ProgramSelector programSelectorFromHal(
+ @NonNull android.hardware.broadcastradio.V2_0.ProgramSelector sel) {
+ ProgramSelector.Identifier[] secondaryIds = sel.secondaryIds.stream().map(
+ id -> programIdentifierFromHal(id)).toArray(ProgramSelector.Identifier[]::new);
+
+ return new ProgramSelector(
+ identifierTypeToProgramType(sel.primaryId.type),
+ programIdentifierFromHal(sel.primaryId),
+ secondaryIds, null);
+ }
+
+ static @NonNull RadioManager.ProgramInfo programInfoFromHal(@NonNull ProgramInfo info) {
+ return new RadioManager.ProgramInfo(
+ programSelectorFromHal(info.selector),
+ (info.infoFlags & ProgramInfoFlags.TUNED) != 0,
+ (info.infoFlags & ProgramInfoFlags.STEREO) != 0,
+ false, // TODO(b/69860743): digital
+ info.signalQuality,
+ null, // TODO(b/69860743): metadata
+ info.infoFlags,
+ vendorInfoFromHal(info.vendorInfo)
+ );
+ }
+
+ static @NonNull ProgramFilter programFilterToHal(@NonNull ProgramList.Filter filter) {
+ ProgramFilter hwFilter = new ProgramFilter();
+
+ filter.getIdentifierTypes().stream().forEachOrdered(hwFilter.identifierTypes::add);
+ filter.getIdentifiers().stream().forEachOrdered(
+ id -> hwFilter.identifiers.add(programIdentifierToHal(id)));
+ hwFilter.includeCategories = filter.areCategoriesIncluded();
+ hwFilter.excludeModifications = filter.areModificationsExcluded();
+
+ return hwFilter;
+ }
+
+ static @NonNull ProgramList.Chunk programListChunkFromHal(@NonNull ProgramListChunk chunk) {
+ Set<RadioManager.ProgramInfo> modified = chunk.modified.stream().map(
+ info -> programInfoFromHal(info)).collect(Collectors.toSet());
+ Set<ProgramSelector.Identifier> removed = chunk.removed.stream().map(
+ id -> programIdentifierFromHal(id)).collect(Collectors.toSet());
+
+ return new ProgramList.Chunk(chunk.purge, chunk.complete, modified, removed);
}
}
diff --git a/services/core/java/com/android/server/broadcastradio/hal2/RadioModule.java b/services/core/java/com/android/server/broadcastradio/hal2/RadioModule.java
index 45b2190..c8e15c1 100644
--- a/services/core/java/com/android/server/broadcastradio/hal2/RadioModule.java
+++ b/services/core/java/com/android/server/broadcastradio/hal2/RadioModule.java
@@ -63,20 +63,16 @@
}
}
- public @NonNull TunerSession openSession(@NonNull android.hardware.radio.ITunerCallback userCb) {
+ public @NonNull TunerSession openSession(@NonNull android.hardware.radio.ITunerCallback userCb)
+ throws RemoteException {
TunerCallback cb = new TunerCallback(Objects.requireNonNull(userCb));
Mutable<ITunerSession> hwSession = new Mutable<>();
MutableInt halResult = new MutableInt(Result.UNKNOWN_ERROR);
- try {
- mService.openSession(cb, (int result, ITunerSession session) -> {
- hwSession.value = session;
- halResult.value = result;
- });
- } catch (RemoteException ex) {
- Slog.e(TAG, "failed to open session", ex);
- throw new ParcelableException(ex);
- }
+ mService.openSession(cb, (int result, ITunerSession session) -> {
+ hwSession.value = session;
+ halResult.value = result;
+ });
Convert.throwOnError("openSession", halResult.value);
Objects.requireNonNull(hwSession.value);
diff --git a/services/core/java/com/android/server/broadcastradio/hal2/TunerCallback.java b/services/core/java/com/android/server/broadcastradio/hal2/TunerCallback.java
index c9084ee..ed2a1b3 100644
--- a/services/core/java/com/android/server/broadcastradio/hal2/TunerCallback.java
+++ b/services/core/java/com/android/server/broadcastradio/hal2/TunerCallback.java
@@ -56,7 +56,9 @@
public void onCurrentProgramInfoChanged(ProgramInfo info) {}
@Override
- public void onProgramListUpdated(ProgramListChunk chunk) {}
+ public void onProgramListUpdated(ProgramListChunk chunk) {
+ dispatch(() -> mClientCb.onProgramListUpdated(Convert.programListChunkFromHal(chunk)));
+ }
@Override
public void onAntennaStateChange(boolean connected) {}
diff --git a/services/core/java/com/android/server/broadcastradio/hal2/TunerSession.java b/services/core/java/com/android/server/broadcastradio/hal2/TunerSession.java
index 8ed646a..e093c9d 100644
--- a/services/core/java/com/android/server/broadcastradio/hal2/TunerSession.java
+++ b/services/core/java/com/android/server/broadcastradio/hal2/TunerSession.java
@@ -22,6 +22,7 @@
import android.hardware.broadcastradio.V2_0.ITunerSession;
import android.hardware.broadcastradio.V2_0.Result;
import android.hardware.radio.ITuner;
+import android.hardware.radio.ProgramList;
import android.hardware.radio.ProgramSelector;
import android.hardware.radio.RadioManager;
import android.media.AudioSystem;
@@ -184,10 +185,19 @@
}
@Override
- public List<RadioManager.ProgramInfo> getProgramList(Map vendorFilter) {
+ public void startProgramListUpdates(ProgramList.Filter filter) throws RemoteException {
synchronized (mLock) {
checkNotClosedLocked();
- return null;
+ int halResult = mHwSession.startProgramListUpdates(Convert.programFilterToHal(filter));
+ Convert.throwOnError("startProgramListUpdates", halResult);
+ }
+ }
+
+ @Override
+ public void stopProgramListUpdates() throws RemoteException {
+ synchronized (mLock) {
+ checkNotClosedLocked();
+ mHwSession.stopProgramListUpdates();
}
}
@@ -226,17 +236,12 @@
}
@Override
- public void setConfigFlag(int flag, boolean value) {
+ public void setConfigFlag(int flag, boolean value) throws RemoteException {
Slog.v(TAG, "setConfigFlag " + ConfigFlag.toString(flag) + " = " + value);
synchronized (mLock) {
checkNotClosedLocked();
- int halResult;
- try {
- halResult = mHwSession.setConfigFlag(flag, value);
- } catch (RemoteException ex) {
- throw new RuntimeException("Failed to set flag " + ConfigFlag.toString(flag), ex);
- }
+ int halResult = mHwSession.setConfigFlag(flag, value);
Convert.throwOnError("setConfigFlag", halResult);
}
}
diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java
index c7a4315..3c2d724 100644
--- a/services/core/java/com/android/server/connectivity/Vpn.java
+++ b/services/core/java/com/android/server/connectivity/Vpn.java
@@ -128,7 +128,7 @@
// Length of time (in milliseconds) that an app hosting an always-on VPN is placed on
// the device idle whitelist during service launch and VPN bootstrap.
- private static final long VPN_LAUNCH_IDLE_WHITELIST_DURATION = 60 * 1000;
+ private static final long VPN_LAUNCH_IDLE_WHITELIST_DURATION_MS = 60 * 1000;
// TODO: create separate trackers for each unique VPN to support
// automated reconnection
@@ -183,10 +183,10 @@
@GuardedBy("this")
private Set<UidRange> mBlockedUsers = new ArraySet<>();
- // Handle of user initiating VPN.
+ // Handle of the user initiating VPN.
private final int mUserHandle;
- // Listen to package remove and change event in this user
+ // Listen to package removal and change events (update/uninstall) for this user
private final BroadcastReceiver mPackageIntentReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
@@ -197,14 +197,14 @@
}
synchronized (Vpn.this) {
- // Avoid race that always-on package has been unset
+ // Avoid race where always-on package has been unset
if (!packageName.equals(getAlwaysOnPackage())) {
return;
}
final String action = intent.getAction();
- Log.i(TAG, "Received broadcast " + action + " for always-on package " + packageName
- + " in user " + mUserHandle);
+ Log.i(TAG, "Received broadcast " + action + " for always-on VPN package "
+ + packageName + " in user " + mUserHandle);
switch(action) {
case Intent.ACTION_PACKAGE_REPLACED:
@@ -248,7 +248,8 @@
Log.wtf(TAG, "Problem registering observer", e);
}
- mNetworkInfo = new NetworkInfo(ConnectivityManager.TYPE_VPN, 0, NETWORKTYPE, "");
+ mNetworkInfo = new NetworkInfo(ConnectivityManager.TYPE_VPN, 0 /* subtype */, NETWORKTYPE,
+ "" /* subtypeName */);
mNetworkCapabilities = new NetworkCapabilities();
mNetworkCapabilities.addTransportType(NetworkCapabilities.TRANSPORT_VPN);
mNetworkCapabilities.removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN);
@@ -258,7 +259,7 @@
}
/**
- * Set if this object is responsible for watching for {@link NetworkInfo}
+ * Set whether this object is responsible for watching for {@link NetworkInfo}
* teardown. When {@code false}, teardown is handled externally by someone
* else.
*/
@@ -481,7 +482,6 @@
}
private void unregisterPackageChangeReceiverLocked() {
- // register previous intent filter
if (mIsPackageIntentReceiverRegistered) {
mContext.unregisterReceiver(mPackageIntentReceiver);
mIsPackageIntentReceiverRegistered = false;
@@ -582,7 +582,7 @@
DeviceIdleController.LocalService idleController =
LocalServices.getService(DeviceIdleController.LocalService.class);
idleController.addPowerSaveTempWhitelistApp(Process.myUid(), alwaysOnPackage,
- VPN_LAUNCH_IDLE_WHITELIST_DURATION, mUserHandle, false, "vpn");
+ VPN_LAUNCH_IDLE_WHITELIST_DURATION_MS, mUserHandle, false, "vpn");
// Start the VPN service declared in the app's manifest.
Intent serviceIntent = new Intent(VpnConfig.SERVICE_INTERFACE);
@@ -612,9 +612,10 @@
* It uses {@link VpnConfig#LEGACY_VPN} as its package name, and
* it can be revoked by itself.
*
- * Note: when we added VPN pre-consent in http://ag/522961 the names oldPackage
- * and newPackage become misleading, because when an app is pre-consented, we
- * actually prepare oldPackage, not newPackage.
+ * Note: when we added VPN pre-consent in
+ * https://android.googlesource.com/platform/frameworks/base/+/0554260
+ * the names oldPackage and newPackage became misleading, because when
+ * an app is pre-consented, we actually prepare oldPackage, not newPackage.
*
* Their meanings actually are:
*
@@ -630,7 +631,7 @@
* @param oldPackage The package name of the old VPN application
* @param newPackage The package name of the new VPN application
*
- * @return true if the operation is succeeded.
+ * @return true if the operation succeeded.
*/
public synchronized boolean prepare(String oldPackage, String newPackage) {
if (oldPackage != null) {
@@ -639,7 +640,7 @@
return false;
}
- // Package is not same or old package was reinstalled.
+ // Package is not the same or old package was reinstalled.
if (!isCurrentPreparedPackage(oldPackage)) {
// The package doesn't match. We return false (to obtain user consent) unless the
// user has already consented to that VPN package.
@@ -861,8 +862,8 @@
long token = Binder.clearCallingIdentity();
try {
- mNetworkAgent = new NetworkAgent(mLooper, mContext, NETWORKTYPE,
- mNetworkInfo, mNetworkCapabilities, lp, 0, networkMisc) {
+ mNetworkAgent = new NetworkAgent(mLooper, mContext, NETWORKTYPE /* logtag */,
+ mNetworkInfo, mNetworkCapabilities, lp, 0 /* score */, networkMisc) {
@Override
public void unwanted() {
// We are user controlled, not driven by NetworkRequest.
@@ -936,7 +937,7 @@
}
ResolveInfo info = AppGlobals.getPackageManager().resolveService(intent,
- null, 0, mUserHandle);
+ null, 0, mUserHandle);
if (info == null) {
throw new SecurityException("Cannot find " + config.user);
}
@@ -944,7 +945,7 @@
throw new SecurityException(config.user + " does not require " + BIND_VPN_SERVICE);
}
} catch (RemoteException e) {
- throw new SecurityException("Cannot find " + config.user);
+ throw new SecurityException("Cannot find " + config.user);
} finally {
Binder.restoreCallingIdentity(token);
}
@@ -1337,7 +1338,7 @@
}
private void enforceControlPermissionOrInternalCaller() {
- // Require caller to be either an application with CONTROL_VPN permission or a process
+ // Require the caller to be either an application with CONTROL_VPN permission or a process
// in the system server.
mContext.enforceCallingOrSelfPermission(Manifest.permission.CONTROL_VPN,
"Unauthorized Caller");
@@ -1417,7 +1418,7 @@
}
/**
- * This method should only be called by ConnectivityService. Because it doesn't
+ * This method should only be called by ConnectivityService because it doesn't
* have enough data to fill VpnInfo.primaryUnderlyingIface field.
*/
public synchronized VpnInfo getVpnInfo() {
@@ -1768,7 +1769,7 @@
* Bringing up a VPN connection takes time, and that is all this thread
* does. Here we have plenty of time. The only thing we need to take
* care of is responding to interruptions as soon as possible. Otherwise
- * requests will be piled up. This can be done in a Handler as a state
+ * requests will pile up. This could be done in a Handler as a state
* machine, but it is much easier to read in the current form.
*/
private class LegacyVpnRunner extends Thread {
@@ -1781,7 +1782,7 @@
private final AtomicInteger mOuterConnection =
new AtomicInteger(ConnectivityManager.TYPE_NONE);
- private long mTimer = -1;
+ private long mBringupStartTime = -1;
/**
* Watch for the outer connection (passing in the constructor) going away.
@@ -1861,8 +1862,8 @@
synchronized (TAG) {
Log.v(TAG, "Executing");
try {
- execute();
- monitorDaemons();
+ bringup();
+ waitForDaemonsToStop();
interrupted(); // Clear interrupt flag if execute called exit.
} catch (InterruptedException e) {
} finally {
@@ -1883,30 +1884,27 @@
}
}
- private void checkpoint(boolean yield) throws InterruptedException {
+ private void checkInterruptAndDelay(boolean sleepLonger) throws InterruptedException {
long now = SystemClock.elapsedRealtime();
- if (mTimer == -1) {
- mTimer = now;
- Thread.sleep(1);
- } else if (now - mTimer <= 60000) {
- Thread.sleep(yield ? 200 : 1);
+ if (now - mBringupStartTime <= 60000) {
+ Thread.sleep(sleepLonger ? 200 : 1);
} else {
updateState(DetailedState.FAILED, "checkpoint");
- throw new IllegalStateException("Time is up");
+ throw new IllegalStateException("VPN bringup took too long");
}
}
- private void execute() {
- // Catch all exceptions so we can clean up few things.
+ private void bringup() {
+ // Catch all exceptions so we can clean up a few things.
boolean initFinished = false;
try {
// Initialize the timer.
- checkpoint(false);
+ mBringupStartTime = SystemClock.elapsedRealtime();
// Wait for the daemons to stop.
for (String daemon : mDaemons) {
while (!SystemService.isStopped(daemon)) {
- checkpoint(true);
+ checkInterruptAndDelay(true);
}
}
@@ -1943,7 +1941,7 @@
// Wait for the daemon to start.
while (!SystemService.isRunning(daemon)) {
- checkpoint(true);
+ checkInterruptAndDelay(true);
}
// Create the control socket.
@@ -1959,7 +1957,7 @@
} catch (Exception e) {
// ignore
}
- checkpoint(true);
+ checkInterruptAndDelay(true);
}
mSockets[i].setSoTimeout(500);
@@ -1973,7 +1971,7 @@
out.write(bytes.length >> 8);
out.write(bytes.length);
out.write(bytes);
- checkpoint(false);
+ checkInterruptAndDelay(false);
}
out.write(0xFF);
out.write(0xFF);
@@ -1989,7 +1987,7 @@
} catch (Exception e) {
// ignore
}
- checkpoint(true);
+ checkInterruptAndDelay(true);
}
}
@@ -2002,7 +2000,7 @@
throw new IllegalStateException(daemon + " is dead");
}
}
- checkpoint(true);
+ checkInterruptAndDelay(true);
}
// Now we are connected. Read and parse the new state.
@@ -2058,8 +2056,8 @@
// Set the start time
mConfig.startTime = SystemClock.elapsedRealtime();
- // Check if the thread is interrupted while we are waiting.
- checkpoint(false);
+ // Check if the thread was interrupted while we were waiting on the lock.
+ checkInterruptAndDelay(false);
// Check if the interface is gone while we are waiting.
if (jniCheck(mConfig.interfaze) == 0) {
@@ -2082,10 +2080,11 @@
}
/**
- * Monitor the daemons we started, moving to disconnected state if the
- * underlying services fail.
+ * Check all daemons every two seconds. Return when one of them is stopped.
+ * The caller will move to the disconnected state when this function returns,
+ * which can happen if a daemon failed or if the VPN was torn down.
*/
- private void monitorDaemons() throws InterruptedException{
+ private void waitForDaemonsToStop() throws InterruptedException {
if (!mNetworkInfo.isConnected()) {
return;
}
diff --git a/services/core/java/com/android/server/content/SyncJobService.java b/services/core/java/com/android/server/content/SyncJobService.java
index d957ca0..40a93c1 100644
--- a/services/core/java/com/android/server/content/SyncJobService.java
+++ b/services/core/java/com/android/server/content/SyncJobService.java
@@ -22,10 +22,12 @@
import android.os.Message;
import android.os.Messenger;
import android.os.RemoteException;
+import android.os.SystemClock;
import android.util.Log;
import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseBooleanArray;
+import android.util.SparseLongArray;
import com.android.internal.annotations.GuardedBy;
@@ -44,6 +46,9 @@
@GuardedBy("mLock")
private final SparseBooleanArray mStartedSyncs = new SparseBooleanArray();
+ @GuardedBy("mLock")
+ private final SparseLongArray mJobStartUptimes = new SparseLongArray();
+
private final SyncLogger mLogger = SyncLogger.getInstance();
/**
@@ -82,7 +87,9 @@
synchronized (mLock) {
final int jobId = params.getJobId();
mJobParamsMap.put(jobId, params);
+
mStartedSyncs.delete(jobId);
+ mJobStartUptimes.put(jobId, SystemClock.uptimeMillis());
}
Message m = Message.obtain();
m.what = SyncManager.SyncHandler.MESSAGE_START_SYNC;
@@ -113,14 +120,24 @@
final int jobId = params.getJobId();
mJobParamsMap.remove(jobId);
- if (!mStartedSyncs.get(jobId)) {
- final String message = "Job " + jobId + " didn't start: params=" +
- jobParametersToString(params);
- mLogger.log(message);
- Slog.wtf(TAG, message);
+ final long startUptime = mJobStartUptimes.get(jobId);
+ final long nowUptime = SystemClock.uptimeMillis();
+ if (startUptime == 0) {
+ wtf("Job " + jobId + " start uptime not found: "
+ + " params=" + jobParametersToString(params));
+ } else if ((nowUptime - startUptime) > 60 * 1000) {
+ // WTF if startSyncH() hasn't happened, *unless* onStopJob() was called too soon.
+ // (1 minute threshold.)
+ if (!mStartedSyncs.get(jobId)) {
+ wtf("Job " + jobId + " didn't start: "
+ + " startUptime=" + startUptime
+ + " nowUptime=" + nowUptime
+ + " params=" + jobParametersToString(params));
+ }
}
mStartedSyncs.delete(jobId);
+ mJobStartUptimes.delete(jobId);
}
Message m = Message.obtain();
m.what = SyncManager.SyncHandler.MESSAGE_STOP_SYNC;
@@ -169,4 +186,9 @@
+ SyncOperation.maybeCreateFromJobExtras(params.getExtras());
}
}
+
+ private void wtf(String message) {
+ mLogger.log(message);
+ Slog.wtf(TAG, message);
+ }
}
diff --git a/services/core/java/com/android/server/display/BrightnessTracker.java b/services/core/java/com/android/server/display/BrightnessTracker.java
index cbb1c01..1e94e00 100644
--- a/services/core/java/com/android/server/display/BrightnessTracker.java
+++ b/services/core/java/com/android/server/display/BrightnessTracker.java
@@ -149,7 +149,7 @@
/**
* Start listening for brightness slider events
*
- * @param brightness the initial screen brightness
+ * @param initialBrightness the initial screen brightness
*/
public void start(float initialBrightness) {
if (DEBUG) {
@@ -219,8 +219,8 @@
if (includePackage) {
out.add(events[i]);
} else {
- BrightnessChangeEvent event = new BrightnessChangeEvent((events[i]));
- event.packageName = null;
+ BrightnessChangeEvent event = new BrightnessChangeEvent((events[i]),
+ /* redactPackage */ true);
out.add(event);
}
}
@@ -246,7 +246,8 @@
}
private void handleBrightnessChanged(float brightness, boolean userInitiated) {
- final BrightnessChangeEvent event;
+ BrightnessChangeEvent.Builder builder;
+
synchronized (mDataCollectionLock) {
if (!mStarted) {
// Not currently gathering brightness change information
@@ -263,9 +264,9 @@
return;
}
-
- event = new BrightnessChangeEvent();
- event.timeStamp = mInjector.currentTimeMillis();
+ builder = new BrightnessChangeEvent.Builder();
+ builder.setBrightness(brightness);
+ builder.setTimeStamp(mInjector.currentTimeMillis());
final int readingCount = mLastSensorReadings.size();
if (readingCount == 0) {
@@ -273,8 +274,8 @@
return;
}
- event.luxValues = new float[readingCount];
- event.luxTimestamps = new long[readingCount];
+ float[] luxValues = new float[readingCount];
+ long[] luxTimestamps = new long[readingCount];
int pos = 0;
@@ -282,33 +283,35 @@
long currentTimeMillis = mInjector.currentTimeMillis();
long elapsedTimeNanos = mInjector.elapsedRealtimeNanos();
for (LightData reading : mLastSensorReadings) {
- event.luxValues[pos] = reading.lux;
- event.luxTimestamps[pos] = currentTimeMillis -
+ luxValues[pos] = reading.lux;
+ luxTimestamps[pos] = currentTimeMillis -
TimeUnit.NANOSECONDS.toMillis(elapsedTimeNanos - reading.timestamp);
++pos;
}
+ builder.setLuxValues(luxValues);
+ builder.setLuxTimestamps(luxTimestamps);
- event.batteryLevel = mLastBatteryLevel;
- event.lastBrightness = previousBrightness;
+ builder.setBatteryLevel(mLastBatteryLevel);
+ builder.setLastBrightness(previousBrightness);
}
- event.brightness = brightness;
-
try {
final ActivityManager.StackInfo focusedStack = mInjector.getFocusedStack();
- event.userId = focusedStack.userId;
- event.packageName = focusedStack.topActivity.getPackageName();
+ builder.setUserId(focusedStack.userId);
+ builder.setPackageName(focusedStack.topActivity.getPackageName());
} catch (RemoteException e) {
// Really shouldn't be possible.
+ return;
}
- event.nightMode = mInjector.getSecureIntForUser(mContentResolver,
+ builder.setNightMode(mInjector.getSecureIntForUser(mContentResolver,
Settings.Secure.NIGHT_DISPLAY_ACTIVATED, 0, UserHandle.USER_CURRENT)
- == 1;
- event.colorTemperature = mInjector.getSecureIntForUser(mContentResolver,
+ == 1);
+ builder.setColorTemperature(mInjector.getSecureIntForUser(mContentResolver,
Settings.Secure.NIGHT_DISPLAY_COLOR_TEMPERATURE,
- 0, UserHandle.USER_CURRENT);
+ 0, UserHandle.USER_CURRENT));
+ BrightnessChangeEvent event = builder.build();
if (DEBUG) {
Slog.d(TAG, "Event " + event.brightness + " " + event.packageName);
}
@@ -457,40 +460,43 @@
}
tag = parser.getName();
if (TAG_EVENT.equals(tag)) {
- BrightnessChangeEvent event = new BrightnessChangeEvent();
+ BrightnessChangeEvent.Builder builder = new BrightnessChangeEvent.Builder();
String brightness = parser.getAttributeValue(null, ATTR_NITS);
- event.brightness = Float.parseFloat(brightness);
+ builder.setBrightness(Float.parseFloat(brightness));
String timestamp = parser.getAttributeValue(null, ATTR_TIMESTAMP);
- event.timeStamp = Long.parseLong(timestamp);
- event.packageName = parser.getAttributeValue(null, ATTR_PACKAGE_NAME);
+ builder.setTimeStamp(Long.parseLong(timestamp));
+ builder.setPackageName(parser.getAttributeValue(null, ATTR_PACKAGE_NAME));
String user = parser.getAttributeValue(null, ATTR_USER);
- event.userId = mInjector.getUserId(mUserManager, Integer.parseInt(user));
+ builder.setUserId(mInjector.getUserId(mUserManager, Integer.parseInt(user)));
String batteryLevel = parser.getAttributeValue(null, ATTR_BATTERY_LEVEL);
- event.batteryLevel = Float.parseFloat(batteryLevel);
+ builder.setBatteryLevel(Float.parseFloat(batteryLevel));
String nightMode = parser.getAttributeValue(null, ATTR_NIGHT_MODE);
- event.nightMode = Boolean.parseBoolean(nightMode);
+ builder.setNightMode(Boolean.parseBoolean(nightMode));
String colorTemperature =
parser.getAttributeValue(null, ATTR_COLOR_TEMPERATURE);
- event.colorTemperature = Integer.parseInt(colorTemperature);
+ builder.setColorTemperature(Integer.parseInt(colorTemperature));
String lastBrightness = parser.getAttributeValue(null, ATTR_LAST_NITS);
- event.lastBrightness = Float.parseFloat(lastBrightness);
+ builder.setLastBrightness(Float.parseFloat(lastBrightness));
String luxValue = parser.getAttributeValue(null, ATTR_LUX);
String luxTimestamp = parser.getAttributeValue(null, ATTR_LUX_TIMESTAMPS);
- String[] luxValues = luxValue.split(",");
- String[] luxTimestamps = luxTimestamp.split(",");
- if (luxValues.length != luxTimestamps.length) {
+ String[] luxValuesStrings = luxValue.split(",");
+ String[] luxTimestampsStrings = luxTimestamp.split(",");
+ if (luxValuesStrings.length != luxTimestampsStrings.length) {
continue;
}
- event.luxValues = new float[luxValues.length];
- event.luxTimestamps = new long[luxValues.length];
+ float[] luxValues = new float[luxValuesStrings.length];
+ long[] luxTimestamps = new long[luxValuesStrings.length];
for (int i = 0; i < luxValues.length; ++i) {
- event.luxValues[i] = Float.parseFloat(luxValues[i]);
- event.luxTimestamps[i] = Long.parseLong(luxTimestamps[i]);
+ luxValues[i] = Float.parseFloat(luxValuesStrings[i]);
+ luxTimestamps[i] = Long.parseLong(luxTimestampsStrings[i]);
}
+ builder.setLuxValues(luxValues);
+ builder.setLuxTimestamps(luxTimestamps);
+ BrightnessChangeEvent event = builder.build();
if (DEBUG) {
Slog.i(TAG, "Read event " + event.brightness
+ " " + event.packageName);
diff --git a/services/core/java/com/android/server/display/ColorFade.java b/services/core/java/com/android/server/display/ColorFade.java
index 85686ae..4f53ed4 100644
--- a/services/core/java/com/android/server/display/ColorFade.java
+++ b/services/core/java/com/android/server/display/ColorFade.java
@@ -99,7 +99,7 @@
private final float mProjMatrix[] = new float[16];
private final int[] mGLBuffers = new int[2];
private int mTexCoordLoc, mVertexLoc, mTexUnitLoc, mProjMatrixLoc, mTexMatrixLoc;
- private int mOpacityLoc, mGammaLoc, mSaturationLoc;
+ private int mOpacityLoc, mGammaLoc;
private int mProgram;
// Vertex and corresponding texture coordinates.
@@ -245,7 +245,6 @@
mOpacityLoc = GLES20.glGetUniformLocation(mProgram, "opacity");
mGammaLoc = GLES20.glGetUniformLocation(mProgram, "gamma");
- mSaturationLoc = GLES20.glGetUniformLocation(mProgram, "saturation");
mTexUnitLoc = GLES20.glGetUniformLocation(mProgram, "texUnit");
GLES20.glUseProgram(mProgram);
@@ -393,9 +392,8 @@
double cos = Math.cos(Math.PI * one_minus_level);
double sign = cos < 0 ? -1 : 1;
float opacity = (float) -Math.pow(one_minus_level, 2) + 1;
- float saturation = (float) Math.pow(level, 4);
float gamma = (float) ((0.5d * sign * Math.pow(cos, 2) + 0.5d) * 0.9d + 0.1d);
- drawFaded(opacity, 1.f / gamma, saturation);
+ drawFaded(opacity, 1.f / gamma);
if (checkGlErrors("drawFrame")) {
return false;
}
@@ -407,10 +405,9 @@
return showSurface(1.0f);
}
- private void drawFaded(float opacity, float gamma, float saturation) {
+ private void drawFaded(float opacity, float gamma) {
if (DEBUG) {
- Slog.d(TAG, "drawFaded: opacity=" + opacity + ", gamma=" + gamma +
- ", saturation=" + saturation);
+ Slog.d(TAG, "drawFaded: opacity=" + opacity + ", gamma=" + gamma);
}
// Use shaders
GLES20.glUseProgram(mProgram);
@@ -420,7 +417,6 @@
GLES20.glUniformMatrix4fv(mTexMatrixLoc, 1, false, mTexMatrix, 0);
GLES20.glUniform1f(mOpacityLoc, opacity);
GLES20.glUniform1f(mGammaLoc, gamma);
- GLES20.glUniform1f(mSaturationLoc, saturation);
// Use textures
GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
diff --git a/services/core/java/com/android/server/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
index 483b02c..23e4c9b 100644
--- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
@@ -16,6 +16,7 @@
package com.android.server.display;
+import android.app.ActivityThread;
import android.content.res.Resources;
import com.android.server.LocalServices;
import com.android.server.lights.Light;
@@ -392,7 +393,7 @@
| DisplayDeviceInfo.FLAG_SUPPORTS_PROTECTED_BUFFERS;
}
- final Resources res = getContext().getResources();
+ final Resources res = getOverlayContext().getResources();
if (mBuiltInDisplayId == SurfaceControl.BUILT_IN_DISPLAY_ID_MAIN) {
mInfo.name = res.getString(
com.android.internal.R.string.display_manager_built_in_display_name);
@@ -687,6 +688,11 @@
}
}
+ /** Supplies a context whose Resources apply runtime-overlays */
+ Context getOverlayContext() {
+ return ActivityThread.currentActivityThread().getSystemUiContext();
+ }
+
/**
* Keeps track of a display configuration.
*/
diff --git a/services/core/java/com/android/server/location/GnssLocationProvider.java b/services/core/java/com/android/server/location/GnssLocationProvider.java
index e6de07d..e158819 100644
--- a/services/core/java/com/android/server/location/GnssLocationProvider.java
+++ b/services/core/java/com/android/server/location/GnssLocationProvider.java
@@ -84,8 +84,6 @@
import com.android.internal.location.ProviderProperties;
import com.android.internal.location.ProviderRequest;
-import com.android.server.power.BatterySaverPolicy;
-
import libcore.io.IoUtils;
import java.io.File;
@@ -579,7 +577,7 @@
final PowerSaveState result =
mPowerManager.getPowerSaveState(ServiceType.GPS);
switch (result.gpsMode) {
- case BatterySaverPolicy.GPS_MODE_DISABLED_WHEN_SCREEN_OFF:
+ case PowerManager.LOCATION_MODE_GPS_DISABLED_WHEN_SCREEN_OFF:
// If we are in battery saver mode and the screen is off, disable GPS.
disableGps |= result.batterySaverEnabled && !mPowerManager.isInteractive();
break;
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java
index 516828b..ee08c38 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsService.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java
@@ -79,10 +79,10 @@
import android.security.keystore.KeyProperties;
import android.security.keystore.KeyProtection;
import android.security.keystore.UserNotAuthenticatedException;
-import android.security.recoverablekeystore.KeyEntryRecoveryData;
-import android.security.recoverablekeystore.KeyStoreRecoveryData;
-import android.security.recoverablekeystore.KeyStoreRecoveryMetadata;
-import android.security.recoverablekeystore.RecoverableKeyStoreLoader.RecoverableKeyStoreLoaderException;
+import android.security.keystore.EntryRecoveryData;
+import android.security.keystore.RecoveryData;
+import android.security.keystore.RecoveryMetadata;
+import android.security.keystore.RecoveryManagerException;
import android.service.gatekeeper.GateKeeperResponse;
import android.service.gatekeeper.IGateKeeperService;
import android.text.TextUtils;
@@ -1968,7 +1968,7 @@
}
@Override
- public KeyStoreRecoveryData getRecoveryData(@NonNull byte[] account) throws RemoteException {
+ public RecoveryData getRecoveryData(@NonNull byte[] account) throws RemoteException {
return mRecoverableKeyStoreManager.getRecoveryData(account);
}
@@ -1982,8 +1982,8 @@
}
@Override
- public void setServerParameters(long serverParameters) throws RemoteException {
- mRecoverableKeyStoreManager.setServerParameters(serverParameters);
+ public void setServerParams(byte[] serverParams) throws RemoteException {
+ mRecoverableKeyStoreManager.setServerParams(serverParams);
}
@Override
@@ -1997,7 +1997,7 @@
}
@Override
- public void setRecoverySecretTypes(@NonNull @KeyStoreRecoveryMetadata.UserSecretType
+ public void setRecoverySecretTypes(@NonNull @RecoveryMetadata.UserSecretType
int[] secretTypes) throws RemoteException {
mRecoverableKeyStoreManager.setRecoverySecretTypes(secretTypes);
}
@@ -2014,7 +2014,7 @@
}
@Override
- public void recoverySecretAvailable(@NonNull KeyStoreRecoveryMetadata recoverySecret)
+ public void recoverySecretAvailable(@NonNull RecoveryMetadata recoverySecret)
throws RemoteException {
mRecoverableKeyStoreManager.recoverySecretAvailable(recoverySecret);
}
@@ -2022,7 +2022,7 @@
@Override
public byte[] startRecoverySession(@NonNull String sessionId,
@NonNull byte[] verifierPublicKey, @NonNull byte[] vaultParams,
- @NonNull byte[] vaultChallenge, @NonNull List<KeyStoreRecoveryMetadata> secrets)
+ @NonNull byte[] vaultChallenge, @NonNull List<RecoveryMetadata> secrets)
throws RemoteException {
return mRecoverableKeyStoreManager.startRecoverySession(sessionId, verifierPublicKey,
vaultParams, vaultChallenge, secrets);
@@ -2030,7 +2030,7 @@
@Override
public Map<String, byte[]> recoverKeys(@NonNull String sessionId,
- @NonNull byte[] recoveryKeyBlob, @NonNull List<KeyEntryRecoveryData> applicationKeys)
+ @NonNull byte[] recoveryKeyBlob, @NonNull List<EntryRecoveryData> applicationKeys)
throws RemoteException {
return mRecoverableKeyStoreManager.recoverKeys(
sessionId, recoveryKeyBlob, applicationKeys);
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncTask.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncTask.java
index 6079873..5fe11b1 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncTask.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncTask.java
@@ -16,15 +16,15 @@
package com.android.server.locksettings.recoverablekeystore;
-import static android.security.recoverablekeystore.KeyStoreRecoveryMetadata.TYPE_LOCKSCREEN;
+import static android.security.keystore.RecoveryMetadata.TYPE_LOCKSCREEN;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
-import android.security.recoverablekeystore.KeyDerivationParameters;
-import android.security.recoverablekeystore.KeyEntryRecoveryData;
-import android.security.recoverablekeystore.KeyStoreRecoveryData;
-import android.security.recoverablekeystore.KeyStoreRecoveryMetadata;
+import android.security.keystore.KeyDerivationParams;
+import android.security.keystore.EntryRecoveryData;
+import android.security.keystore.RecoveryData;
+import android.security.keystore.RecoveryMetadata;
import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
@@ -175,7 +175,7 @@
return;
}
- Long deviceId = mRecoverableKeyStoreDb.getServerParameters(mUserId, recoveryAgentUid);
+ byte[] deviceId = mRecoverableKeyStoreDb.getServerParams(mUserId, recoveryAgentUid);
if (deviceId == null) {
Log.w(TAG, "No device ID set for user " + mUserId);
return;
@@ -232,8 +232,8 @@
byte[] vaultParams = KeySyncUtils.packVaultParams(
publicKey,
counterId,
- TRUSTED_HARDWARE_MAX_ATTEMPTS,
- deviceId);
+ deviceId,
+ TRUSTED_HARDWARE_MAX_ATTEMPTS);
byte[] encryptedRecoveryKey;
try {
@@ -250,12 +250,13 @@
return;
}
// TODO: store raw data in RecoveryServiceMetadataEntry and generate Parcelables later
- KeyStoreRecoveryMetadata metadata = new KeyStoreRecoveryMetadata(
+ // TODO: use Builder.
+ RecoveryMetadata metadata = new RecoveryMetadata(
/*userSecretType=*/ TYPE_LOCKSCREEN,
/*lockScreenUiFormat=*/ getUiFormat(mCredentialType, mCredential),
- /*keyDerivationParameters=*/ KeyDerivationParameters.createSha256Parameters(salt),
+ /*keyDerivationParams=*/ KeyDerivationParams.createSha256Params(salt),
/*secret=*/ new byte[0]);
- ArrayList<KeyStoreRecoveryMetadata> metadataList = new ArrayList<>();
+ ArrayList<RecoveryMetadata> metadataList = new ArrayList<>();
metadataList.add(metadata);
int snapshotVersion = incrementSnapshotVersion(recoveryAgentUid);
@@ -263,7 +264,8 @@
// If application keys are not updated, snapshot will not be created on next unlock.
mRecoverableKeyStoreDb.setShouldCreateSnapshot(mUserId, recoveryAgentUid, false);
- mRecoverySnapshotStorage.put(recoveryAgentUid, new KeyStoreRecoveryData(
+ // TODO: use Builder.
+ mRecoverySnapshotStorage.put(recoveryAgentUid, new RecoveryData(
snapshotVersion,
/*recoveryMetadata=*/ metadataList,
/*applicationKeyBlobs=*/ createApplicationKeyEntries(encryptedApplicationKeys),
@@ -306,7 +308,7 @@
*/
private boolean shoudCreateSnapshot(int recoveryAgentUid) {
int[] types = mRecoverableKeyStoreDb.getRecoverySecretTypes(mUserId, recoveryAgentUid);
- if (!ArrayUtils.contains(types, KeyStoreRecoveryMetadata.TYPE_LOCKSCREEN)) {
+ if (!ArrayUtils.contains(types, RecoveryMetadata.TYPE_LOCKSCREEN)) {
// Only lockscreen type is supported.
// We will need to pass extra argument to KeySyncTask to support custom pass phrase.
return false;
@@ -329,14 +331,14 @@
* @return The format - either pattern, pin, or password.
*/
@VisibleForTesting
- @KeyStoreRecoveryMetadata.LockScreenUiFormat static int getUiFormat(
+ @RecoveryMetadata.LockScreenUiFormat static int getUiFormat(
int credentialType, String credential) {
if (credentialType == LockPatternUtils.CREDENTIAL_TYPE_PATTERN) {
- return KeyStoreRecoveryMetadata.TYPE_PATTERN;
+ return RecoveryMetadata.TYPE_PATTERN;
} else if (isPin(credential)) {
- return KeyStoreRecoveryMetadata.TYPE_PIN;
+ return RecoveryMetadata.TYPE_PIN;
} else {
- return KeyStoreRecoveryMetadata.TYPE_PASSWORD;
+ return RecoveryMetadata.TYPE_PASSWORD;
}
}
@@ -399,12 +401,12 @@
return keyGenerator.generateKey();
}
- private static List<KeyEntryRecoveryData> createApplicationKeyEntries(
+ private static List<EntryRecoveryData> createApplicationKeyEntries(
Map<String, byte[]> encryptedApplicationKeys) {
- ArrayList<KeyEntryRecoveryData> keyEntries = new ArrayList<>();
+ ArrayList<EntryRecoveryData> keyEntries = new ArrayList<>();
for (String alias : encryptedApplicationKeys.keySet()) {
keyEntries.add(
- new KeyEntryRecoveryData(
+ new EntryRecoveryData(
alias,
encryptedApplicationKeys.get(alias)));
}
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncUtils.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncUtils.java
index e851d8c..b4bef17 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncUtils.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncUtils.java
@@ -37,8 +37,7 @@
import javax.crypto.SecretKey;
/**
- * Utility functions for the flow where the RecoverableKeyStoreLoader syncs keys with remote
- * storage.
+ * Utility functions for the flow where the RecoveryManager syncs keys with remote storage.
*
* @hide
*/
@@ -288,17 +287,17 @@
*
* @param thmPublicKey Public key of the trusted hardware module.
* @param counterId ID referring to the specific counter in the hardware module.
- * @param maxAttempts Maximum allowed guesses before trusted hardware wipes key.
* @param deviceId ID of the device.
+ * @param maxAttempts Maximum allowed guesses before trusted hardware wipes key.
* @return The binary vault params, ready for sync.
*/
public static byte[] packVaultParams(
- PublicKey thmPublicKey, long counterId, int maxAttempts, long deviceId) {
+ PublicKey thmPublicKey, long counterId, byte[] deviceId, int maxAttempts) {
return ByteBuffer.allocate(VAULT_PARAMS_LENGTH_BYTES)
.order(ByteOrder.LITTLE_ENDIAN)
.put(SecureBox.encodePublicKey(thmPublicKey))
.putLong(counterId)
- .putLong(deviceId)
+ .putLong(0L) // TODO: replace with device Id.
.putInt(maxAttempts)
.array();
}
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 a6f7766..7658178 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java
@@ -16,32 +16,28 @@
package com.android.server.locksettings.recoverablekeystore;
-import static android.security.recoverablekeystore.RecoverableKeyStoreLoader
- .ERROR_BAD_X509_CERTIFICATE;
-import static android.security.recoverablekeystore.RecoverableKeyStoreLoader.ERROR_DATABASE_ERROR;
-import static android.security.recoverablekeystore.RecoverableKeyStoreLoader
- .ERROR_DECRYPTION_FAILED;
-import static android.security.recoverablekeystore.RecoverableKeyStoreLoader.ERROR_INSECURE_USER;
-import static android.security.recoverablekeystore.RecoverableKeyStoreLoader
- .ERROR_KEYSTORE_INTERNAL_ERROR;
-import static android.security.recoverablekeystore.RecoverableKeyStoreLoader
- .ERROR_NOT_YET_SUPPORTED;
-import static android.security.recoverablekeystore.RecoverableKeyStoreLoader
- .ERROR_UNEXPECTED_MISSING_ALGORITHM;
+import static android.security.keystore.RecoveryManagerException.ERROR_BAD_X509_CERTIFICATE;
+import static android.security.keystore.RecoveryManagerException.ERROR_DATABASE_ERROR;
+import static android.security.keystore.RecoveryManagerException.ERROR_DECRYPTION_FAILED;
+import static android.security.keystore.RecoveryManagerException.ERROR_INSECURE_USER;
+import static android.security.keystore.RecoveryManagerException.ERROR_KEYSTORE_INTERNAL_ERROR;
+import static android.security.keystore.RecoveryManagerException.ERROR_UNEXPECTED_MISSING_ALGORITHM;
+import static android.security.keystore.RecoveryManagerException.ERROR_NO_SNAPSHOT_PENDING;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.PendingIntent;
import android.content.Context;
+import android.Manifest;
import android.os.Binder;
import android.os.RemoteException;
import android.os.ServiceSpecificException;
import android.os.UserHandle;
-import android.security.recoverablekeystore.KeyEntryRecoveryData;
-import android.security.recoverablekeystore.KeyStoreRecoveryData;
-import android.security.recoverablekeystore.KeyStoreRecoveryMetadata;
-import android.security.recoverablekeystore.RecoverableKeyStoreLoader;
+import android.security.keystore.EntryRecoveryData;
+import android.security.keystore.RecoveryData;
+import android.security.keystore.RecoveryMetadata;
+import android.security.keystore.RecoveryManager;
import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
@@ -69,7 +65,7 @@
import javax.crypto.AEADBadTagException;
/**
- * Class with {@link RecoverableKeyStoreLoader} API implementation and internal methods to interact
+ * Class with {@link RecoveryManager} API implementation and internal methods to interact
* with {@code LockSettingsService}.
*
* @hide
@@ -148,6 +144,7 @@
throws RemoteException {
checkRecoverKeyStorePermission();
int userId = UserHandle.getCallingUserId();
+ int uid = Binder.getCallingUid();
// TODO: open /system/etc/security/... cert file, and check the signature on the public keys
PublicKey publicKey;
try {
@@ -162,7 +159,10 @@
throw new ServiceSpecificException(
ERROR_BAD_X509_CERTIFICATE, "Not a valid X509 certificate.");
}
- mDatabase.setRecoveryServicePublicKey(userId, Binder.getCallingUid(), publicKey);
+ long updatedRows = mDatabase.setRecoveryServicePublicKey(userId, uid, publicKey);
+ if (updatedRows > 0) {
+ mDatabase.setShouldCreateSnapshot(userId, uid, true);
+ }
}
/**
@@ -171,13 +171,13 @@
* @return recovery data
* @hide
*/
- public @NonNull KeyStoreRecoveryData getRecoveryData(@NonNull byte[] account)
+ public @NonNull RecoveryData getRecoveryData(@NonNull byte[] account)
throws RemoteException {
checkRecoverKeyStorePermission();
int uid = Binder.getCallingUid();
- KeyStoreRecoveryData snapshot = mSnapshotStorage.get(uid);
+ RecoveryData snapshot = mSnapshotStorage.get(uid);
if (snapshot == null) {
- throw new ServiceSpecificException(RecoverableKeyStoreLoader.ERROR_NO_SNAPSHOT_PENDING);
+ throw new ServiceSpecificException(ERROR_NO_SNAPSHOT_PENDING);
}
return snapshot;
}
@@ -201,10 +201,14 @@
throw new UnsupportedOperationException();
}
- public void setServerParameters(long serverParameters) throws RemoteException {
+ public void setServerParams(byte[] serverParams) throws RemoteException {
checkRecoverKeyStorePermission();
int userId = UserHandle.getCallingUserId();
- mDatabase.setServerParameters(userId, Binder.getCallingUid(), serverParameters);
+ int uid = Binder.getCallingUid();
+ long updatedRows = mDatabase.setServerParams(userId, uid, serverParams);
+ if (updatedRows > 0) {
+ mDatabase.setShouldCreateSnapshot(userId, uid, true);
+ }
}
/**
@@ -253,11 +257,15 @@
* @hide
*/
public void setRecoverySecretTypes(
- @NonNull @KeyStoreRecoveryMetadata.UserSecretType int[] secretTypes)
+ @NonNull @RecoveryMetadata.UserSecretType int[] secretTypes)
throws RemoteException {
checkRecoverKeyStorePermission();
- mDatabase.setRecoverySecretTypes(UserHandle.getCallingUserId(), Binder.getCallingUid(),
- secretTypes);
+ int userId = UserHandle.getCallingUserId();
+ int uid = Binder.getCallingUid();
+ long updatedRows = mDatabase.setRecoverySecretTypes(userId, uid, secretTypes);
+ if (updatedRows > 0) {
+ mDatabase.setShouldCreateSnapshot(userId, uid, true);
+ }
}
/**
@@ -273,7 +281,7 @@
}
/**
- * Gets secret types RecoverableKeyStoreLoaders is waiting for to create new Recovery Data.
+ * Gets secret types RecoveryManagers is waiting for to create new Recovery Data.
*
* @return secret types
* @hide
@@ -284,9 +292,9 @@
}
public void recoverySecretAvailable(
- @NonNull KeyStoreRecoveryMetadata recoverySecret) throws RemoteException {
+ @NonNull RecoveryMetadata recoverySecret) throws RemoteException {
int uid = Binder.getCallingUid();
- if (recoverySecret.getLockScreenUiFormat() == KeyStoreRecoveryMetadata.TYPE_LOCKSCREEN) {
+ if (recoverySecret.getLockScreenUiFormat() == RecoveryMetadata.TYPE_LOCKSCREEN) {
throw new SecurityException(
"Caller " + uid + " is not allowed to set lock screen secret");
}
@@ -312,16 +320,13 @@
@NonNull byte[] verifierPublicKey,
@NonNull byte[] vaultParams,
@NonNull byte[] vaultChallenge,
- @NonNull List<KeyStoreRecoveryMetadata> secrets)
+ @NonNull List<RecoveryMetadata> secrets)
throws RemoteException {
checkRecoverKeyStorePermission();
int uid = Binder.getCallingUid();
if (secrets.size() != 1) {
- // TODO: support multiple secrets
- throw new ServiceSpecificException(
- ERROR_NOT_YET_SUPPORTED,
- "Only a single KeyStoreRecoveryMetadata is supported");
+ throw new UnsupportedOperationException("Only a single RecoveryMetadata is supported");
}
PublicKey publicKey;
@@ -379,7 +384,7 @@
public Map<String, byte[]> recoverKeys(
@NonNull String sessionId,
@NonNull byte[] encryptedRecoveryKey,
- @NonNull List<KeyEntryRecoveryData> applicationKeys)
+ @NonNull List<EntryRecoveryData> applicationKeys)
throws RemoteException {
checkRecoverKeyStorePermission();
int uid = Binder.getCallingUid();
@@ -469,9 +474,9 @@
*/
private Map<String, byte[]> recoverApplicationKeys(
@NonNull byte[] recoveryKey,
- @NonNull List<KeyEntryRecoveryData> applicationKeys) throws RemoteException {
+ @NonNull List<EntryRecoveryData> applicationKeys) throws RemoteException {
HashMap<String, byte[]> keyMaterialByAlias = new HashMap<>();
- for (KeyEntryRecoveryData applicationKey : applicationKeys) {
+ for (EntryRecoveryData applicationKey : applicationKeys) {
String alias = applicationKey.getAlias();
byte[] encryptedKeyMaterial = applicationKey.getEncryptedKeyMaterial();
@@ -555,7 +560,7 @@
private void checkRecoverKeyStorePermission() {
mContext.enforceCallingOrSelfPermission(
- RecoverableKeyStoreLoader.PERMISSION_RECOVER_KEYSTORE,
+ Manifest.permission.RECOVER_KEYSTORE,
"Caller " + Binder.getCallingUid() + " doesn't have RecoverKeyStore permission.");
}
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/WrappedKey.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/WrappedKey.java
index 54aa9f0..0042e10 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/WrappedKey.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/WrappedKey.java
@@ -17,7 +17,7 @@
package com.android.server.locksettings.recoverablekeystore;
import android.util.Log;
-import android.security.recoverablekeystore.RecoverableKeyStoreLoader;
+import android.security.keystore.RecoveryManager;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
@@ -97,7 +97,7 @@
/*nonce=*/ cipher.getIV(),
/*keyMaterial=*/ encryptedKeyMaterial,
/*platformKeyGenerationId=*/ wrappingKey.getGenerationId(),
- RecoverableKeyStoreLoader.RECOVERY_STATUS_SYNC_IN_PROGRESS);
+ RecoveryManager.RECOVERY_STATUS_SYNC_IN_PROGRESS);
}
/**
@@ -107,14 +107,14 @@
* @param keyMaterial The encrypted bytes of the key material.
* @param platformKeyGenerationId The generation ID of the key used to wrap this key.
*
- * @see RecoverableKeyStoreLoader.RECOVERY_STATUS_SYNC_IN_PROGRESS
+ * @see RecoveryManager.RECOVERY_STATUS_SYNC_IN_PROGRESS
* @hide
*/
public WrappedKey(byte[] nonce, byte[] keyMaterial, int platformKeyGenerationId) {
mNonce = nonce;
mKeyMaterial = keyMaterial;
mPlatformKeyGenerationId = platformKeyGenerationId;
- mRecoveryStatus = RecoverableKeyStoreLoader.RECOVERY_STATUS_SYNC_IN_PROGRESS;
+ mRecoveryStatus = RecoveryManager.RECOVERY_STATUS_SYNC_IN_PROGRESS;
}
/**
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDb.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDb.java
index c6f3ede..eb2da80 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDb.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDb.java
@@ -336,17 +336,8 @@
* @hide
*/
public long setRecoveryServicePublicKey(int userId, int uid, PublicKey publicKey) {
- SQLiteDatabase db = mKeyStoreDbHelper.getWritableDatabase();
- ContentValues values = new ContentValues();
- values.put(RecoveryServiceMetadataEntry.COLUMN_NAME_PUBLIC_KEY, publicKey.getEncoded());
- String selection =
- RecoveryServiceMetadataEntry.COLUMN_NAME_USER_ID + " = ? AND "
- + RecoveryServiceMetadataEntry.COLUMN_NAME_UID + " = ?";
- String[] selectionArguments = {Integer.toString(userId), Integer.toString(uid)};
-
- ensureRecoveryServiceMetadataEntryExists(userId, uid);
- return db.update(
- RecoveryServiceMetadataEntry.TABLE_NAME, values, selection, selectionArguments);
+ return setBytes(userId, uid, RecoveryServiceMetadataEntry.COLUMN_NAME_PUBLIC_KEY,
+ publicKey.getEncoded());
}
/**
@@ -393,63 +384,27 @@
*/
@Nullable
public PublicKey getRecoveryServicePublicKey(int userId, int uid) {
- SQLiteDatabase db = mKeyStoreDbHelper.getReadableDatabase();
-
- String[] projection = {
- RecoveryServiceMetadataEntry._ID,
- RecoveryServiceMetadataEntry.COLUMN_NAME_USER_ID,
- RecoveryServiceMetadataEntry.COLUMN_NAME_UID,
- RecoveryServiceMetadataEntry.COLUMN_NAME_PUBLIC_KEY};
- String selection =
- RecoveryServiceMetadataEntry.COLUMN_NAME_USER_ID + " = ? AND "
- + RecoveryServiceMetadataEntry.COLUMN_NAME_UID + " = ?";
- String[] selectionArguments = {Integer.toString(userId), Integer.toString(uid)};
-
- try (
- Cursor cursor = db.query(
- RecoveryServiceMetadataEntry.TABLE_NAME,
- projection,
- selection,
- selectionArguments,
- /*groupBy=*/ null,
- /*having=*/ null,
- /*orderBy=*/ null)
- ) {
- int count = cursor.getCount();
- if (count == 0) {
- return null;
- }
- if (count > 1) {
- Log.wtf(TAG,
- String.format(Locale.US,
- "%d PublicKey entries found for userId=%d uid=%d. "
- + "Should only ever be 0 or 1.", count, userId, uid));
- return null;
- }
- cursor.moveToFirst();
- int idx = cursor.getColumnIndexOrThrow(
- RecoveryServiceMetadataEntry.COLUMN_NAME_PUBLIC_KEY);
- if (cursor.isNull(idx)) {
- return null;
- }
- byte[] keyBytes = cursor.getBlob(idx);
- try {
- return decodeX509Key(keyBytes);
- } catch (InvalidKeySpecException e) {
- Log.wtf(TAG,
- String.format(Locale.US,
- "Recovery service public key entry cannot be decoded for "
- + "userId=%d uid=%d.",
- userId, uid));
- return null;
- }
+ byte[] keyBytes =
+ getBytes(userId, uid, RecoveryServiceMetadataEntry.COLUMN_NAME_PUBLIC_KEY);
+ if (keyBytes == null) {
+ return null;
+ }
+ try {
+ return decodeX509Key(keyBytes);
+ } catch (InvalidKeySpecException e) {
+ Log.wtf(TAG,
+ String.format(Locale.US,
+ "Recovery service public key entry cannot be decoded for "
+ + "userId=%d uid=%d.",
+ userId, uid));
+ return null;
}
}
/**
* Updates the list of user secret types used for end-to-end encryption.
* If no secret types are set, recovery snapshot will not be created.
- * See {@code KeyStoreRecoveryMetadata}
+ * See {@code RecoveryMetadata}
*
* @param userId The userId of the profile the application is running under.
* @param uid The uid of the application.
@@ -617,14 +572,14 @@
*
* @param userId The userId of the profile the application is running under.
* @param uid The uid of the application.
- * @param serverParameters The server parameters.
+ * @param serverParams The server parameters.
* @return The primary key of the inserted row, or -1 if failed.
*
* @hide
*/
- public long setServerParameters(int userId, int uid, long serverParameters) {
- return setLong(userId, uid,
- RecoveryServiceMetadataEntry.COLUMN_NAME_SERVER_PARAMETERS, serverParameters);
+ public long setServerParams(int userId, int uid, byte[] serverParams) {
+ return setBytes(userId, uid,
+ RecoveryServiceMetadataEntry.COLUMN_NAME_SERVER_PARAMS, serverParams);
}
/**
@@ -638,9 +593,8 @@
* @hide
*/
@Nullable
- public Long getServerParameters(int userId, int uid) {
- return getLong(userId, uid, RecoveryServiceMetadataEntry.COLUMN_NAME_SERVER_PARAMETERS);
-
+ public byte[] getServerParams(int userId, int uid) {
+ return getBytes(userId, uid, RecoveryServiceMetadataEntry.COLUMN_NAME_SERVER_PARAMS);
}
/**
@@ -704,6 +658,7 @@
return res != null && res != 0L;
}
+
/**
* Returns given long value from the database.
*
@@ -785,6 +740,86 @@
}
/**
+ * Returns given binary value from the database.
+ *
+ * @param userId The userId of the profile the application is running under.
+ * @param uid The uid of the application who initialized the local recovery components.
+ * @param key from {@code RecoveryServiceMetadataEntry}
+ * @return The value that were previously set, or null if there's none.
+ *
+ * @hide
+ */
+ private byte[] getBytes(int userId, int uid, String key) {
+ SQLiteDatabase db = mKeyStoreDbHelper.getReadableDatabase();
+
+ String[] projection = {
+ RecoveryServiceMetadataEntry._ID,
+ RecoveryServiceMetadataEntry.COLUMN_NAME_USER_ID,
+ RecoveryServiceMetadataEntry.COLUMN_NAME_UID,
+ key};
+ String selection =
+ RecoveryServiceMetadataEntry.COLUMN_NAME_USER_ID + " = ? AND "
+ + RecoveryServiceMetadataEntry.COLUMN_NAME_UID + " = ?";
+ String[] selectionArguments = {Integer.toString(userId), Integer.toString(uid)};
+
+ try (
+ Cursor cursor = db.query(
+ RecoveryServiceMetadataEntry.TABLE_NAME,
+ projection,
+ selection,
+ selectionArguments,
+ /*groupBy=*/ null,
+ /*having=*/ null,
+ /*orderBy=*/ null)
+ ) {
+ int count = cursor.getCount();
+ if (count == 0) {
+ return null;
+ }
+ if (count > 1) {
+ Log.wtf(TAG,
+ String.format(Locale.US,
+ "%d entries found for userId=%d uid=%d. "
+ + "Should only ever be 0 or 1.", count, userId, uid));
+ return null;
+ }
+ cursor.moveToFirst();
+ int idx = cursor.getColumnIndexOrThrow(key);
+ if (cursor.isNull(idx)) {
+ return null;
+ } else {
+ return cursor.getBlob(idx);
+ }
+ }
+ }
+
+ /**
+ * Sets a binary value in the database.
+ *
+ * @param userId The userId of the profile the application is running under.
+ * @param uid The uid of the application who initialized the local recovery components.
+ * @param key defined in {@code RecoveryServiceMetadataEntry}
+ * @param value new value.
+ * @return The primary key of the inserted row, or -1 if failed.
+ *
+ * @hide
+ */
+
+ private long setBytes(int userId, int uid, String key, byte[] value) {
+ SQLiteDatabase db = mKeyStoreDbHelper.getWritableDatabase();
+ ContentValues values = new ContentValues();
+ values.put(key, value);
+ String selection =
+ RecoveryServiceMetadataEntry.COLUMN_NAME_USER_ID + " = ? AND "
+ + RecoveryServiceMetadataEntry.COLUMN_NAME_UID + " = ?";
+ String[] selectionArguments = {Integer.toString(userId), Integer.toString(uid)};
+
+ ensureRecoveryServiceMetadataEntryExists(userId, uid);
+ return db.update(
+ RecoveryServiceMetadataEntry.TABLE_NAME, values, selection, selectionArguments);
+ }
+
+ /**
* Creates an empty row in the recovery service metadata table if such a row doesn't exist for
* the given userId and uid, so db.update will succeed.
*/
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbContract.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbContract.java
index 597ae4c..4ee282b 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbContract.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbContract.java
@@ -64,7 +64,7 @@
static final String COLUMN_NAME_LAST_SYNCED_AT = "last_synced_at";
/**
- * Status of the key sync {@code RecoverableKeyStoreLoader#setRecoveryStatus}
+ * Status of the key sync {@code RecoveryManager#setRecoveryStatus}
*/
static final String COLUMN_NAME_RECOVERY_STATUS = "recovery_status";
}
@@ -130,6 +130,6 @@
/**
* The server parameters of the recovery service.
*/
- static final String COLUMN_NAME_SERVER_PARAMETERS = "server_parameters";
+ static final String COLUMN_NAME_SERVER_PARAMS = "server_params";
}
}
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbHelper.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbHelper.java
index 6eb47ee..d96671c 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbHelper.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbHelper.java
@@ -61,7 +61,7 @@
+ RecoveryServiceMetadataEntry.COLUMN_NAME_PUBLIC_KEY + " BLOB,"
+ RecoveryServiceMetadataEntry.COLUMN_NAME_SECRET_TYPES + " TEXT,"
+ RecoveryServiceMetadataEntry.COLUMN_NAME_COUNTER_ID + " INTEGER,"
- + RecoveryServiceMetadataEntry.COLUMN_NAME_SERVER_PARAMETERS + " INTEGER,"
+ + RecoveryServiceMetadataEntry.COLUMN_NAME_SERVER_PARAMS + " BLOB,"
+ "UNIQUE("
+ RecoveryServiceMetadataEntry.COLUMN_NAME_USER_ID + ","
+ RecoveryServiceMetadataEntry.COLUMN_NAME_UID + "))";
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverySnapshotStorage.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverySnapshotStorage.java
index 011b374..158b1e3 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverySnapshotStorage.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverySnapshotStorage.java
@@ -17,7 +17,7 @@
package com.android.server.locksettings.recoverablekeystore.storage;
import android.annotation.Nullable;
-import android.security.recoverablekeystore.KeyStoreRecoveryData;
+import android.security.keystore.RecoveryData;
import android.util.SparseArray;
import com.android.internal.annotations.GuardedBy;
@@ -34,12 +34,12 @@
*/
public class RecoverySnapshotStorage {
@GuardedBy("this")
- private final SparseArray<KeyStoreRecoveryData> mSnapshotByUid = new SparseArray<>();
+ private final SparseArray<RecoveryData> mSnapshotByUid = new SparseArray<>();
/**
* Sets the latest {@code snapshot} for the recovery agent {@code uid}.
*/
- public synchronized void put(int uid, KeyStoreRecoveryData snapshot) {
+ public synchronized void put(int uid, RecoveryData snapshot) {
mSnapshotByUid.put(uid, snapshot);
}
@@ -47,7 +47,7 @@
* Returns the latest snapshot for the recovery agent {@code uid}, or null if none exists.
*/
@Nullable
- public synchronized KeyStoreRecoveryData get(int uid) {
+ public synchronized RecoveryData get(int uid) {
return mSnapshotByUid.get(uid);
}
diff --git a/services/core/java/com/android/server/net/watchlist/OWNERS b/services/core/java/com/android/server/net/watchlist/OWNERS
new file mode 100644
index 0000000..a3d4b85
--- /dev/null
+++ b/services/core/java/com/android/server/net/watchlist/OWNERS
@@ -0,0 +1,3 @@
+rickywai@google.com
+alanstokes@google.com
+simonjw@google.com
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 575c44d..16b9257 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -2917,12 +2917,34 @@
}
}
+ /**
+ * Sets the notification policy. Apps that target API levels below
+ * {@link android.os.Build.VERSION_CODES#P} cannot make DND silence
+ * {@link Policy#PRIORITY_CATEGORY_ALARMS} or
+ * {@link Policy#PRIORITY_CATEGORY_MEDIA_SYSTEM_OTHER}
+ */
@Override
public void setNotificationPolicy(String pkg, Policy policy) {
enforcePolicyAccess(pkg, "setNotificationPolicy");
final long identity = Binder.clearCallingIdentity();
try {
+ final ApplicationInfo applicationInfo = mPackageManager.getApplicationInfo(pkg,
+ 0, UserHandle.getUserId(MY_UID));
+
+ if (applicationInfo.targetSdkVersion <= Build.VERSION_CODES.O_MR1) {
+ Policy currPolicy = mZenModeHelper.getNotificationPolicy();
+
+ int priorityCategories = policy.priorityCategories
+ | (currPolicy.priorityCategories & Policy.PRIORITY_CATEGORY_ALARMS)
+ | (currPolicy.priorityCategories &
+ Policy.PRIORITY_CATEGORY_MEDIA_SYSTEM_OTHER);
+ policy = new Policy(priorityCategories,
+ policy.priorityCallSenders, policy.priorityMessageSenders,
+ policy.suppressedVisualEffects);
+ }
+
mZenModeHelper.setNotificationPolicy(policy);
+ } catch (RemoteException e) {
} finally {
Binder.restoreCallingIdentity(identity);
}
diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java
index 700ccad..b5d2844 100644
--- a/services/core/java/com/android/server/notification/ZenModeHelper.java
+++ b/services/core/java/com/android/server/notification/ZenModeHelper.java
@@ -822,21 +822,24 @@
@VisibleForTesting
protected void applyRestrictions() {
- final boolean zen = mZenMode != Global.ZEN_MODE_OFF;
+ final boolean zenPriorityOnly = mZenMode == Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS;
+ final boolean zenSilence = mZenMode == Global.ZEN_MODE_NO_INTERRUPTIONS;
+ final boolean zenAlarmsOnly = mZenMode == Global.ZEN_MODE_ALARMS;
// notification restrictions
final boolean muteNotifications =
(mSuppressedEffects & SUPPRESSED_EFFECT_NOTIFICATIONS) != 0;
// call restrictions
- final boolean muteCalls = zen && !mConfig.allowCalls && !mConfig.allowRepeatCallers
+ final boolean muteCalls = zenAlarmsOnly
+ || (zenPriorityOnly && !mConfig.allowCalls && !mConfig.allowRepeatCallers)
|| (mSuppressedEffects & SUPPRESSED_EFFECT_CALLS) != 0;
// alarm restrictions
- final boolean muteAlarms = zen && !mConfig.allowAlarms;
+ final boolean muteAlarms = zenPriorityOnly && !mConfig.allowAlarms;
// alarm restrictions
- final boolean muteMediaAndSystemSounds = zen && !mConfig.allowMediaSystemOther;
+ final boolean muteMediaAndSystemSounds = zenPriorityOnly && !mConfig.allowMediaSystemOther;
// total silence restrictions
- final boolean muteEverything = mZenMode == Global.ZEN_MODE_NO_INTERRUPTIONS
- || areAllBehaviorSoundsMuted();
+ final boolean muteEverything = zenSilence
+ || (zenPriorityOnly && areAllBehaviorSoundsMuted());
for (int usage : AudioAttributes.SDK_USAGES) {
final int suppressionBehavior = AudioAttributes.SUPPRESSIBLE_USAGES.get(usage);
diff --git a/services/core/java/com/android/server/pm/InstantAppRegistry.java b/services/core/java/com/android/server/pm/InstantAppRegistry.java
index c964f91..af20cd7 100644
--- a/services/core/java/com/android/server/pm/InstantAppRegistry.java
+++ b/services/core/java/com/android/server/pm/InstantAppRegistry.java
@@ -49,7 +49,6 @@
import com.android.internal.os.SomeArgs;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.XmlUtils;
-import com.android.server.pm.permission.BasePermission;
import libcore.io.IoUtils;
import org.xmlpull.v1.XmlPullParser;
@@ -302,7 +301,7 @@
// into account but also allow the value from the old computation to avoid
// data loss.
final String[] signaturesSha256Digests = PackageUtils.computeSignaturesSha256Digests(
- pkg.mSignatures);
+ pkg.mSigningDetails.signatures);
final String signaturesSha256Digest = PackageUtils.computeSignaturesSha256Digest(
signaturesSha256Digests);
@@ -313,7 +312,7 @@
}
// For backwards compatibility we accept match based on first signature
- if (pkg.mSignatures.length > 1 && currentCookieFile.equals(computeInstantCookieFile(
+ if (pkg.mSigningDetails.signatures.length > 1 && currentCookieFile.equals(computeInstantCookieFile(
pkg.packageName, signaturesSha256Digests[0], userId))) {
return;
}
@@ -1176,12 +1175,13 @@
// We prefer the modern computation procedure where all certs are taken
// into account and delete the file derived via the legacy hash computation.
File newCookieFile = computeInstantCookieFile(pkg.packageName,
- PackageUtils.computeSignaturesSha256Digest(pkg.mSignatures), userId);
- if (pkg.mSignatures.length > 0) {
- File oldCookieFile = peekInstantCookieFile(pkg.packageName, userId);
- if (oldCookieFile != null && !newCookieFile.equals(oldCookieFile)) {
- oldCookieFile.delete();
- }
+ PackageUtils.computeSignaturesSha256Digest(pkg.mSigningDetails.signatures), userId);
+ if (!pkg.mSigningDetails.hasSignatures()) {
+ Slog.wtf(LOG_TAG, "Parsed Instant App contains no valid signatures!");
+ }
+ File oldCookieFile = peekInstantCookieFile(pkg.packageName, userId);
+ if (oldCookieFile != null && !newCookieFile.equals(oldCookieFile)) {
+ oldCookieFile.delete();
}
cancelPendingPersistLPw(pkg, userId);
addPendingPersistCookieLPw(userId, pkg, cookie, newCookieFile);
diff --git a/services/core/java/com/android/server/pm/KeySetManagerService.java b/services/core/java/com/android/server/pm/KeySetManagerService.java
index fca9585..93d3b77 100644
--- a/services/core/java/com/android/server/pm/KeySetManagerService.java
+++ b/services/core/java/com/android/server/pm/KeySetManagerService.java
@@ -188,7 +188,7 @@
throw new PackageManagerException(INSTALL_FAILED_INVALID_APK,
"Passed invalid package to keyset validation.");
}
- ArraySet<PublicKey> signingKeys = pkg.mSigningKeys;
+ ArraySet<PublicKey> signingKeys = pkg.mSigningDetails.publicKeys;
if (signingKeys == null || !(signingKeys.size() > 0) || signingKeys.contains(null)) {
throw new PackageManagerException(INSTALL_FAILED_INVALID_APK,
"Package has invalid signing-key-set.");
@@ -223,7 +223,7 @@
PackageSetting ps = mPackages.get(pkg.packageName);
Preconditions.checkNotNull(ps, "pkg: " + pkg.packageName
+ "does not have a corresponding entry in mPackages.");
- addSigningKeySetToPackageLPw(ps, pkg.mSigningKeys);
+ addSigningKeySetToPackageLPw(ps, pkg.mSigningDetails.publicKeys);
if (pkg.mKeySetMapping != null) {
addDefinedKeySetsToPackageLPw(ps, pkg.mKeySetMapping);
if (pkg.mUpgradeKeySets != null) {
@@ -371,7 +371,7 @@
long[] upgradeKeySets = oldPS.keySetData.getUpgradeKeySets();
for (int i = 0; i < upgradeKeySets.length; i++) {
Set<PublicKey> upgradeSet = getPublicKeysFromKeySetLPr(upgradeKeySets[i]);
- if (upgradeSet != null && newPkg.mSigningKeys.containsAll(upgradeSet)) {
+ if (upgradeSet != null && newPkg.mSigningDetails.publicKeys.containsAll(upgradeSet)) {
return true;
}
}
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index 5577de8..a43818a 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -58,7 +58,6 @@
import android.content.pm.PackageParser.ApkLite;
import android.content.pm.PackageParser.PackageLite;
import android.content.pm.PackageParser.PackageParserException;
-import android.content.pm.Signature;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Binder;
@@ -84,6 +83,7 @@
import android.util.ExceptionUtils;
import android.util.MathUtils;
import android.util.Slog;
+import android.util.apk.ApkSignatureVerifier;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.content.NativeLibraryHelper;
@@ -107,7 +107,7 @@
import java.io.FileFilter;
import java.io.FileOutputStream;
import java.io.IOException;
-import java.security.cert.Certificate;
+import java.security.cert.CertificateException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@@ -227,9 +227,7 @@
@GuardedBy("mLock")
private long mVersionCode;
@GuardedBy("mLock")
- private Signature[] mSignatures;
- @GuardedBy("mLock")
- private Certificate[][] mCertificates;
+ private PackageParser.SigningDetails mSigningDetails;
/**
* Path to the validated base APK for this session, which may point at an
@@ -857,7 +855,7 @@
}
Preconditions.checkNotNull(mPackageName);
- Preconditions.checkNotNull(mSignatures);
+ Preconditions.checkNotNull(mSigningDetails);
Preconditions.checkNotNull(mResolvedBaseFile);
if (needToAskForPermissionsLocked()) {
@@ -938,7 +936,7 @@
mRelinquished = true;
mPm.installStage(mPackageName, stageDir, localObserver, params,
- mInstallerPackageName, mInstallerUid, user, mCertificates);
+ mInstallerPackageName, mInstallerUid, user, mSigningDetails);
}
/**
@@ -957,7 +955,7 @@
throws PackageManagerException {
mPackageName = null;
mVersionCode = -1;
- mSignatures = null;
+ mSigningDetails = PackageParser.SigningDetails.UNKNOWN;
mResolvedBaseFile = null;
mResolvedStagedFiles.clear();
@@ -990,11 +988,8 @@
for (File addedFile : addedFiles) {
final ApkLite apk;
try {
- int flags = PackageParser.PARSE_COLLECT_CERTIFICATES;
- if ((params.installFlags & PackageManager.INSTALL_INSTANT_APP) != 0) {
- flags |= PackageParser.PARSE_IS_EPHEMERAL;
- }
- apk = PackageParser.parseApkLite(addedFile, flags);
+ apk = PackageParser.parseApkLite(
+ addedFile, PackageParser.PARSE_COLLECT_CERTIFICATES);
} catch (PackageParserException e) {
throw PackageManagerException.from(e);
}
@@ -1009,9 +1004,8 @@
mPackageName = apk.packageName;
mVersionCode = apk.getLongVersionCode();
}
- if (mSignatures == null) {
- mSignatures = apk.signatures;
- mCertificates = apk.certificates;
+ if (mSigningDetails == PackageParser.SigningDetails.UNKNOWN) {
+ mSigningDetails = apk.signingDetails;
}
assertApkConsistentLocked(String.valueOf(addedFile), apk);
@@ -1060,8 +1054,15 @@
mPackageName = pkgInfo.packageName;
mVersionCode = pkgInfo.getLongVersionCode();
}
- if (mSignatures == null) {
- mSignatures = pkgInfo.signatures;
+ if (mSigningDetails == PackageParser.SigningDetails.UNKNOWN) {
+ try {
+ mSigningDetails = ApkSignatureVerifier.plsCertsNoVerifyOnlyCerts(
+ pkgInfo.applicationInfo.sourceDir,
+ PackageParser.SigningDetails.SignatureSchemeVersion.JAR);
+ } catch (PackageParserException e) {
+ throw new PackageManagerException(INSTALL_FAILED_INVALID_APK,
+ "Couldn't obtain signatures from base APK");
+ }
}
}
@@ -1155,7 +1156,7 @@
+ " version code " + apk.versionCode + " inconsistent with "
+ mVersionCode);
}
- if (!Signature.areExactMatch(mSignatures, apk.signatures)) {
+ if (!mSigningDetails.signaturesMatchExactly(apk.signingDetails)) {
throw new PackageManagerException(INSTALL_FAILED_INVALID_APK,
tag + " signatures are inconsistent");
}
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 44aad44..1e2f2b2 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -17,6 +17,7 @@
package com.android.server.pm;
import static android.Manifest.permission.DELETE_PACKAGES;
+import static android.Manifest.permission.SET_HARMFUL_APP_WARNINGS;
import static android.Manifest.permission.INSTALL_PACKAGES;
import static android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS;
import static android.Manifest.permission.READ_EXTERNAL_STORAGE;
@@ -167,7 +168,6 @@
import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
import android.content.pm.PackageManager.LegacyPackageDeleteObserver;
-import android.content.pm.PackageManager.PackageInfoFlags;
import android.content.pm.PackageManagerInternal.PackageListObserver;
import android.content.pm.PackageParser;
import android.content.pm.PackageParser.ActivityIntentInfo;
@@ -176,6 +176,7 @@
import android.content.pm.PackageParser.PackageParserException;
import android.content.pm.PackageParser.ParseFlags;
import android.content.pm.PackageParser.ServiceIntentInfo;
+import android.content.pm.PackageParser.SigningDetails.SignatureSchemeVersion;
import android.content.pm.PackageStats;
import android.content.pm.PackageUserState;
import android.content.pm.ParceledListSlice;
@@ -337,7 +338,6 @@
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
import java.security.SecureRandom;
-import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.util.ArrayList;
import java.util.Arrays;
@@ -2748,7 +2748,6 @@
for (String deletedAppName : possiblyDeletedUpdatedSystemApps) {
PackageParser.Package deletedPkg = mPackages.get(deletedAppName);
mSettings.removeDisabledSystemPackageLPw(deletedAppName);
-
final String msg;
if (deletedPkg == null) {
// should have found an update, but, we didn't; remove everything
@@ -5360,7 +5359,7 @@
|| filterAppAccessLPr(ps2, callingUid, callingUserId)) {
return PackageManager.SIGNATURE_UNKNOWN_PACKAGE;
}
- return compareSignatures(p1.mSignatures, p2.mSignatures);
+ return compareSignatures(p1.mSigningDetails.signatures, p2.mSigningDetails.signatures);
}
}
@@ -8184,36 +8183,36 @@
}
private void collectCertificatesLI(PackageSetting ps, PackageParser.Package pkg,
- final @ParseFlags int parseFlags) throws PackageManagerException {
+ final @ParseFlags int parseFlags, boolean forceCollect) 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);
- if (ps != null
+ if (ps != null && !forceCollect
&& ps.codePathString.equals(pkg.codePath)
&& ps.timeStamp == lastModifiedTime
&& !isCompatSignatureUpdateNeeded(pkg)
&& !isRecoverSignatureUpdateNeeded(pkg)) {
- long mSigningKeySetId = ps.keySetData.getProperSigningKeySet();
- final KeySetManagerService ksms = mSettings.mKeySetManagerService;
- ArraySet<PublicKey> signingKs;
- synchronized (mPackages) {
- signingKs = ksms.getPublicKeysFromKeySetLPr(mSigningKeySetId);
- }
if (ps.signatures.mSignatures != null
&& ps.signatures.mSignatures.length != 0
- && signingKs != null) {
- // Optimization: reuse the existing cached certificates
+ && ps.signatures.mSignatureSchemeVersion != SignatureSchemeVersion.UNKNOWN) {
+ // Optimization: reuse the existing cached signing data
// if the package appears to be unchanged.
- pkg.mSignatures = ps.signatures.mSignatures;
- pkg.mSigningKeys = signingKs;
- return;
+ try {
+ pkg.mSigningDetails = new PackageParser.SigningDetails(ps.signatures.mSignatures,
+ ps.signatures.mSignatureSchemeVersion);
+ return;
+ } catch (CertificateException e) {
+ Slog.e(TAG, "Attempt to read public keys from persisted signatures failed for "
+ + ps.name, e);
+ }
}
Slog.w(TAG, "PackageSetting for " + ps.name
+ " is missing signatures. Collecting certs again to recover them.");
} else {
- Slog.i(TAG, toString() + " changed; collecting certs");
+ Slog.i(TAG, pkg.codePath + " changed; collecting certs" +
+ (forceCollect ? " (forced)" : ""));
}
try {
@@ -8293,14 +8292,14 @@
}
// Scan the parent
- PackageParser.Package scannedPkg = scanPackageInternalLI(pkg, parseFlags,
+ PackageParser.Package scannedPkg = addForInitLI(pkg, parseFlags,
scanFlags, currentTime, user);
// Scan the children
final int childCount = (pkg.childPackages != null) ? pkg.childPackages.size() : 0;
for (int i = 0; i < childCount; i++) {
PackageParser.Package childPackage = pkg.childPackages.get(i);
- scanPackageInternalLI(childPackage, parseFlags, scanFlags,
+ addForInitLI(childPackage, parseFlags, scanFlags,
currentTime, user);
}
@@ -8313,50 +8312,85 @@
}
/**
- * Scans a package and returns the newly parsed package.
- * @throws PackageManagerException on a parse error.
+ * Adds a new package to the internal data structures during platform initialization.
+ * <p>After adding, the package is known to the system and available for querying.
+ * <p>For packages located on the device ROM [eg. packages located in /system, /vendor,
+ * etc...], additional checks are performed. Basic verification [such as ensuring
+ * matching signatures, checking version codes, etc...] occurs if the package is
+ * identical to a previously known package. If the package fails a signature check,
+ * the version installed on /data will be removed. If the version of the new package
+ * is less than or equal than the version on /data, it will be ignored.
+ * <p>Regardless of the package location, the results are applied to the internal
+ * structures and the package is made available to the rest of the system.
+ * <p>NOTE: The return value should be removed. It's the passed in package object.
*/
- private PackageParser.Package scanPackageInternalLI(PackageParser.Package pkg,
+ private PackageParser.Package addForInitLI(PackageParser.Package pkg,
@ParseFlags int parseFlags, @ScanFlags int scanFlags, long currentTime,
@Nullable UserHandle user)
throws PackageManagerException {
- PackageSetting ps = null;
- PackageSetting updatedPs;
- // reader
- synchronized (mPackages) {
- // Look to see if we already know about this package.
- String oldName = mSettings.getRenamedPackageLPr(pkg.packageName);
- if (pkg.mOriginalPackages != null && pkg.mOriginalPackages.contains(oldName)) {
- // This package has been renamed to its original name. Let's
- // use that.
- ps = mSettings.getPackageLPr(oldName);
- }
- // If there was no original package, see one for the real package name.
- if (ps == null) {
- ps = mSettings.getPackageLPr(pkg.packageName);
- }
- // Check to see if this package could be hiding/updating a system
- // package. Must look for it either under the original or real
- // package name depending on our state.
- updatedPs = mSettings.getDisabledSystemPkgLPr(ps != null ? ps.name : pkg.packageName);
- if (DEBUG_INSTALL && updatedPs != null) Slog.d(TAG, "updatedPkg = " + updatedPs);
+ final boolean scanSystemPartition = (parseFlags & PackageParser.PARSE_IS_SYSTEM_DIR) != 0;
+ final String renamedPkgName;
+ final PackageSetting disabledPkgSetting;
+ final boolean isSystemPkgUpdated;
+ final boolean pkgAlreadyExists;
+ PackageSetting pkgSetting;
- // If this is a package we don't know about on the system partition, we
- // may need to remove disabled child packages on the system partition
- // or may need to not add child packages if the parent apk is updated
- // on the data partition and no longer defines this child package.
- if ((scanFlags & SCAN_AS_SYSTEM) != 0) {
- // If this is a parent package for an updated system app and this system
- // app got an OTA update which no longer defines some of the child packages
- // we have to prune them from the disabled system packages.
- PackageSetting disabledPs = mSettings.getDisabledSystemPkgLPr(pkg.packageName);
- if (disabledPs != null) {
+ // NOTE: installPackageLI() has the same code to setup the package's
+ // application info. This probably should be done lower in the call
+ // stack [such as scanPackageOnly()]. However, we verify the application
+ // info prior to that [in scanPackageNew()] and thus have to setup
+ // the application info early.
+ pkg.setApplicationVolumeUuid(pkg.volumeUuid);
+ pkg.setApplicationInfoCodePath(pkg.codePath);
+ pkg.setApplicationInfoBaseCodePath(pkg.baseCodePath);
+ pkg.setApplicationInfoSplitCodePaths(pkg.splitCodePaths);
+ pkg.setApplicationInfoResourcePath(pkg.codePath);
+ pkg.setApplicationInfoBaseResourcePath(pkg.baseCodePath);
+ pkg.setApplicationInfoSplitResourcePaths(pkg.splitCodePaths);
+
+ synchronized (mPackages) {
+ renamedPkgName = mSettings.getRenamedPackageLPr(pkg.mRealPackage);
+ final String realPkgName = getRealPackageName(pkg, renamedPkgName);
+ if (realPkgName != null) {
+ ensurePackageRenamed(pkg, renamedPkgName);
+ }
+ final PackageSetting originalPkgSetting = getOriginalPackageLocked(pkg, renamedPkgName);
+ final PackageSetting installedPkgSetting = mSettings.getPackageLPr(pkg.packageName);
+ pkgSetting = originalPkgSetting == null ? installedPkgSetting : originalPkgSetting;
+ pkgAlreadyExists = pkgSetting != null;
+ final String disabledPkgName = pkgAlreadyExists ? pkgSetting.name : pkg.packageName;
+ disabledPkgSetting = mSettings.getDisabledSystemPkgLPr(disabledPkgName);
+ isSystemPkgUpdated = disabledPkgSetting != null;
+
+ if (DEBUG_INSTALL && isSystemPkgUpdated) {
+ Slog.d(TAG, "updatedPkg = " + disabledPkgSetting);
+ }
+
+ final SharedUserSetting sharedUserSetting = (pkg.mSharedUserId != null)
+ ? mSettings.getSharedUserLPw(pkg.mSharedUserId,
+ 0 /*pkgFlags*/, 0 /*pkgPrivateFlags*/, true)
+ : null;
+ if (DEBUG_PACKAGE_SCANNING
+ && (parseFlags & PackageParser.PARSE_CHATTY) != 0
+ && sharedUserSetting != null) {
+ Log.d(TAG, "Shared UserID " + pkg.mSharedUserId
+ + " (uid=" + sharedUserSetting.userId + "):"
+ + " packages=" + sharedUserSetting.packages);
+ }
+
+ if (scanSystemPartition) {
+ // Potentially prune child packages. If the application on the /system
+ // partition has been updated via OTA, but, is still disabled by a
+ // version on /data, cycle through all of its children packages and
+ // remove children that are no longer defined.
+ if (isSystemPkgUpdated) {
final int scannedChildCount = (pkg.childPackages != null)
? pkg.childPackages.size() : 0;
- final int disabledChildCount = disabledPs.childPackageNames != null
- ? disabledPs.childPackageNames.size() : 0;
+ final int disabledChildCount = disabledPkgSetting.childPackageNames != null
+ ? disabledPkgSetting.childPackageNames.size() : 0;
for (int i = 0; i < disabledChildCount; i++) {
- String disabledChildPackageName = disabledPs.childPackageNames.get(i);
+ String disabledChildPackageName =
+ disabledPkgSetting.childPackageNames.get(i);
boolean disabledPackageAvailable = false;
for (int j = 0; j < scannedChildCount; j++) {
PackageParser.Package childPkg = pkg.childPackages.get(j);
@@ -8364,261 +8398,119 @@
disabledPackageAvailable = true;
break;
}
- }
- if (!disabledPackageAvailable) {
- mSettings.removeDisabledSystemPackageLPw(disabledChildPackageName);
- }
- }
- }
- }
- }
-
- final boolean isUpdatedPkg = updatedPs != null;
- final boolean isUpdatedSystemPkg = isUpdatedPkg && (scanFlags & SCAN_AS_SYSTEM) != 0;
- boolean isUpdatedPkgBetter = false;
- // First check if this is a system package that may involve an update
- if (isUpdatedSystemPkg) {
- // If new package is not located in "/system/priv-app" (e.g. due to an OTA),
- // it needs to drop FLAG_PRIVILEGED.
- if (locationIsPrivileged(pkg.codePath)) {
- updatedPs.pkgPrivateFlags |= ApplicationInfo.PRIVATE_FLAG_PRIVILEGED;
- } else {
- updatedPs.pkgPrivateFlags &= ~ApplicationInfo.PRIVATE_FLAG_PRIVILEGED;
- }
- // If new package is not located in "/oem" (e.g. due to an OTA),
- // it needs to drop FLAG_OEM.
- if (locationIsOem(pkg.codePath)) {
- updatedPs.pkgPrivateFlags |= ApplicationInfo.PRIVATE_FLAG_OEM;
- } else {
- updatedPs.pkgPrivateFlags &= ~ApplicationInfo.PRIVATE_FLAG_OEM;
- }
- // If new package is not located in "/vendor" (e.g. due to an OTA),
- // it needs to drop FLAG_VENDOR.
- if (locationIsVendor(pkg.codePath)) {
- updatedPs.pkgPrivateFlags |= ApplicationInfo.PRIVATE_FLAG_VENDOR;
- } else {
- updatedPs.pkgPrivateFlags &= ~ApplicationInfo.PRIVATE_FLAG_VENDOR;
- }
-
- if (ps != null && !ps.codePathString.equals(pkg.codePath)) {
- // The path has changed from what was last scanned... check the
- // version of the new path against what we have stored to determine
- // what to do.
- if (DEBUG_INSTALL) Slog.d(TAG, "Path changing from " + ps.codePath);
- if (pkg.getLongVersionCode() <= ps.versionCode) {
- // The system package has been updated and the code path does not match
- // Ignore entry. Skip it.
- if (DEBUG_INSTALL) Slog.i(TAG, "Package " + ps.name + " at " + pkg.codePath
- + " ignored: updated version " + ps.versionCode
- + " better than this " + pkg.getLongVersionCode());
- if (!updatedPs.codePathString.equals(pkg.codePath)) {
- Slog.w(PackageManagerService.TAG, "Code path for hidden system pkg "
- + ps.name + " changing from " + updatedPs.codePathString
- + " to " + pkg.codePath);
- final File codePath = new File(pkg.codePath);
- updatedPs.codePath = codePath;
- updatedPs.codePathString = pkg.codePath;
- updatedPs.resourcePath = codePath;
- updatedPs.resourcePathString = pkg.codePath;
- }
- updatedPs.pkg = pkg;
- updatedPs.versionCode = pkg.getLongVersionCode();
-
- // Update the disabled system child packages to point to the package too.
- final int childCount = updatedPs.childPackageNames != null
- ? updatedPs.childPackageNames.size() : 0;
- for (int i = 0; i < childCount; i++) {
- String childPackageName = updatedPs.childPackageNames.get(i);
- PackageSetting updatedChildPkg = mSettings.getDisabledSystemPkgLPr(
- childPackageName);
- if (updatedChildPkg != null) {
- updatedChildPkg.pkg = pkg;
- updatedChildPkg.versionCode = pkg.getLongVersionCode();
+ }
+ if (!disabledPackageAvailable) {
+ mSettings.removeDisabledSystemPackageLPw(disabledChildPackageName);
}
}
- } else {
- // The current app on the system partition is better than
- // what we have updated to on the data partition; switch
- // back to the system partition version.
- // At this point, its safely assumed that package installation for
- // apps in system partition will go through. If not there won't be a working
- // version of the app
- // writer
- synchronized (mPackages) {
- // Just remove the loaded entries from package lists.
- mPackages.remove(ps.name);
- }
-
- logCriticalInfo(Log.WARN, "Package " + ps.name + " at " + pkg.codePath
- + " reverting from " + ps.codePathString
- + ": new version " + pkg.getLongVersionCode()
- + " better than installed " + ps.versionCode);
-
- InstallArgs args = createInstallArgsForExisting(packageFlagsToInstallFlags(ps),
- ps.codePathString, ps.resourcePathString, getAppDexInstructionSets(ps));
- synchronized (mInstallLock) {
- args.cleanUpResourcesLI();
- }
- synchronized (mPackages) {
- mSettings.enableSystemPackageLPw(ps.name);
- }
- isUpdatedPkgBetter = true;
+ // we're updating the disabled package, so, scan it as the package setting
+ final ScanRequest request = new ScanRequest(pkg, sharedUserSetting,
+ disabledPkgSetting /* pkgSetting */, null /* disabledPkgSetting */,
+ null /* originalPkgSetting */, null, parseFlags, scanFlags,
+ (pkg == mPlatformPackage), user);
+ scanPackageOnlyLI(request, mFactoryTest, -1L);
}
}
}
- String resourcePath = null;
- String baseResourcePath = null;
- if ((parseFlags & PackageParser.PARSE_FORWARD_LOCK) != 0 && !isUpdatedPkgBetter) {
- if (ps != null && ps.resourcePathString != null) {
- resourcePath = ps.resourcePathString;
- baseResourcePath = ps.resourcePathString;
- } else {
- // Should not happen at all. Just log an error.
- Slog.e(TAG, "Resource path not set for package " + pkg.packageName);
+ final boolean newPkgChangedPaths =
+ pkgAlreadyExists && !pkgSetting.codePathString.equals(pkg.codePath);
+ final boolean newPkgVersionGreater =
+ pkgAlreadyExists && pkg.getLongVersionCode() > pkgSetting.versionCode;
+ final boolean isSystemPkgBetter = scanSystemPartition && isSystemPkgUpdated
+ && newPkgChangedPaths && newPkgVersionGreater;
+ if (isSystemPkgBetter) {
+ // The version of the application on /system is greater than the version on
+ // /data. Switch back to the application on /system.
+ // It's safe to assume the application on /system will correctly scan. If not,
+ // there won't be a working copy of the application.
+ synchronized (mPackages) {
+ // just remove the loaded entries from package lists
+ mPackages.remove(pkgSetting.name);
}
- } else {
- resourcePath = pkg.codePath;
- baseResourcePath = pkg.baseCodePath;
+
+ logCriticalInfo(Log.WARN,
+ "System package updated;"
+ + " name: " + pkgSetting.name
+ + "; " + pkgSetting.versionCode + " --> " + pkg.getLongVersionCode()
+ + "; " + pkgSetting.codePathString + " --> " + pkg.codePath);
+
+ final InstallArgs args = createInstallArgsForExisting(
+ packageFlagsToInstallFlags(pkgSetting), pkgSetting.codePathString,
+ pkgSetting.resourcePathString, getAppDexInstructionSets(pkgSetting));
+ args.cleanUpResourcesLI();
+ synchronized (mPackages) {
+ mSettings.enableSystemPackageLPw(pkgSetting.name);
+ }
}
- // Set application objects path explicitly.
- pkg.setApplicationVolumeUuid(pkg.volumeUuid);
- pkg.setApplicationInfoCodePath(pkg.codePath);
- pkg.setApplicationInfoBaseCodePath(pkg.baseCodePath);
- pkg.setApplicationInfoSplitCodePaths(pkg.splitCodePaths);
- pkg.setApplicationInfoResourcePath(resourcePath);
- pkg.setApplicationInfoBaseResourcePath(baseResourcePath);
- pkg.setApplicationInfoSplitResourcePaths(pkg.splitCodePaths);
-
- // throw an exception if we have an update to a system application, but, it's not more
- // recent than the package we've already scanned
- if (isUpdatedSystemPkg && !isUpdatedPkgBetter) {
- // Set CPU Abis to application info.
- if ((scanFlags & SCAN_FIRST_BOOT_OR_UPGRADE) != 0) {
- final String cpuAbiOverride = deriveAbiOverride(pkg.cpuAbiOverride, updatedPs);
- derivePackageAbi(pkg, cpuAbiOverride, false);
- } else {
- pkg.applicationInfo.primaryCpuAbi = updatedPs.primaryCpuAbiString;
- pkg.applicationInfo.secondaryCpuAbi = updatedPs.secondaryCpuAbiString;
- }
- pkg.mExtras = updatedPs;
-
+ if (scanSystemPartition && isSystemPkgUpdated && !isSystemPkgBetter) {
+ // The version of the application on the /system partition is less than or
+ // equal to the version on the /data partition. Throw an exception and use
+ // the application already installed on the /data partition.
throw new PackageManagerException(Log.WARN, "Package " + pkg.packageName + " at "
- + pkg.codePath + " ignored: updated version " + updatedPs.versionCode
+ + pkg.codePath + " ignored: updated version " + disabledPkgSetting.versionCode
+ " better than this " + pkg.getLongVersionCode());
}
- if (isUpdatedPkg) {
- // updated system applications don't initially have the SCAN_AS_SYSTEM flag set
- scanFlags |= SCAN_AS_SYSTEM;
+ // Verify certificates against what was last scanned. If it is an updated priv app, we will
+ // force the verification. 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.
+ collectCertificatesLI(pkgSetting, pkg, parseFlags,
+ PackageManagerServiceUtils.isApkVerificationForced(disabledPkgSetting));
- // An updated privileged application will not have the PARSE_IS_PRIVILEGED
- // flag set initially
- if ((updatedPs.pkgPrivateFlags & ApplicationInfo.PRIVATE_FLAG_PRIVILEGED) != 0) {
- scanFlags |= SCAN_AS_PRIVILEGED;
- }
-
- // An updated OEM app will not have the SCAN_AS_OEM
- // flag set initially
- if ((updatedPs.pkgPrivateFlags & ApplicationInfo.PRIVATE_FLAG_OEM) != 0) {
- scanFlags |= SCAN_AS_OEM;
- }
-
- // An updated vendor app will not have the SCAN_AS_VENDOR
- // flag set initially
- if ((updatedPs.pkgPrivateFlags & ApplicationInfo.PRIVATE_FLAG_VENDOR) != 0) {
- scanFlags |= SCAN_AS_VENDOR;
- }
- }
-
- // Verify certificates against what was last scanned
- collectCertificatesLI(ps, pkg, parseFlags);
-
- /*
- * A new system app appeared, but we already had a non-system one of the
- * same name installed earlier.
- */
boolean shouldHideSystemApp = false;
- if (!isUpdatedPkg && ps != null
- && (parseFlags & PackageParser.PARSE_IS_SYSTEM_DIR) != 0 && !isSystemApp(ps)) {
- /*
- * Check to make sure the signatures match first. If they don't,
- * wipe the installed application and its data.
- */
- if (compareSignatures(ps.signatures.mSignatures, pkg.mSignatures)
+ // 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.mSignatures, pkg.mSigningDetails.signatures)
!= PackageManager.SIGNATURE_MATCH) {
- logCriticalInfo(Log.WARN, "Package " + ps.name + " appeared on system, but"
- + " signatures don't match existing userdata copy; removing");
+ logCriticalInfo(Log.WARN,
+ "System package signature mismatch;"
+ + " name: " + pkgSetting.name);
try (PackageFreezer freezer = freezePackage(pkg.packageName,
"scanPackageInternalLI")) {
deletePackageLIF(pkg.packageName, null, true, null, 0, null, false, null);
}
- ps = null;
- } else {
- /*
- * If the newly-added system app is an older version than the
- * already installed version, hide it. It will be scanned later
- * and re-added like an update.
- */
- if (pkg.getLongVersionCode() <= ps.versionCode) {
- shouldHideSystemApp = true;
- logCriticalInfo(Log.INFO, "Package " + ps.name + " appeared at " + pkg.codePath
- + " but new version " + pkg.getLongVersionCode()
- + " better than installed " + ps.versionCode + "; hiding system");
- } else {
- /*
- * The newly found system app is a newer version that the
- * one previously installed. Simply remove the
- * already-installed application and replace it with our own
- * while keeping the application data.
- */
- logCriticalInfo(Log.WARN, "Package " + ps.name + " at " + pkg.codePath
- + " reverting from " + ps.codePathString + ": new version "
- + pkg.getLongVersionCode() + " better than installed "
- + ps.versionCode);
- InstallArgs args = createInstallArgsForExisting(packageFlagsToInstallFlags(ps),
- ps.codePathString, ps.resourcePathString, getAppDexInstructionSets(ps));
- synchronized (mInstallLock) {
- args.cleanUpResourcesLI();
- }
+ pkgSetting = null;
+ } else if (newPkgVersionGreater) {
+ // The application on /system is newer than the application on /data.
+ // Simply remove the application on /data [keeping application data]
+ // and replace it with the version on /system.
+ logCriticalInfo(Log.WARN,
+ "System package enabled;"
+ + " name: " + pkgSetting.name
+ + "; " + pkgSetting.versionCode + " --> " + pkg.getLongVersionCode()
+ + "; " + pkgSetting.codePathString + " --> " + pkg.codePath);
+ InstallArgs args = createInstallArgsForExisting(
+ packageFlagsToInstallFlags(pkgSetting), pkgSetting.codePathString,
+ pkgSetting.resourcePathString, getAppDexInstructionSets(pkgSetting));
+ synchronized (mInstallLock) {
+ args.cleanUpResourcesLI();
}
+ } else {
+ // The application on /system is older than the application on /data. Hide
+ // the application on /system and the version on /data will be scanned later
+ // and re-added like an update.
+ shouldHideSystemApp = true;
+ logCriticalInfo(Log.INFO,
+ "System package disabled;"
+ + " name: " + pkgSetting.name
+ + "; old: " + pkgSetting.codePathString + " @ " + pkgSetting.versionCode
+ + "; new: " + pkg.codePath + " @ " + pkg.codePath);
}
}
- // The apk is forward locked (not public) if its code and resources
- // are kept in different files. (except for app in either system or
- // vendor path).
- // TODO grab this value from PackageSettings
- if ((parseFlags & PackageParser.PARSE_IS_SYSTEM_DIR) == 0) {
- if (ps != null && !ps.codePath.equals(ps.resourcePath)) {
- parseFlags |= PackageParser.PARSE_FORWARD_LOCK;
- }
- }
-
- final int userId = ((user == null) ? 0 : user.getIdentifier());
- if (ps != null && ps.getInstantApp(userId)) {
- scanFlags |= SCAN_AS_INSTANT_APP;
- }
- if (ps != null && ps.getVirtulalPreload(userId)) {
- scanFlags |= SCAN_AS_VIRTUAL_PRELOAD;
- }
-
- // Note that we invoke the following method only if we are about to unpack an application
- PackageParser.Package scannedPkg = scanPackageNewLI(pkg, parseFlags, scanFlags
+ final PackageParser.Package scannedPkg = scanPackageNewLI(pkg, parseFlags, scanFlags
| SCAN_UPDATE_SIGNATURE, currentTime, user);
- /*
- * If the system app should be overridden by a previously installed
- * data, hide the system app now and let the /data/app scan pick it up
- * again.
- */
if (shouldHideSystemApp) {
synchronized (mPackages) {
mSettings.disableSystemPackageLPw(pkg.packageName, true);
}
}
-
return scannedPkg;
}
@@ -9518,9 +9410,10 @@
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.mSignatures)
+ ? PackageUtils.computeSignaturesSha256Digests(
+ libPkg.mSigningDetails.signatures)
: PackageUtils.computeSignaturesSha256Digests(
- new Signature[]{libPkg.mSignatures[0]});
+ 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
@@ -9717,9 +9610,52 @@
}
}
+ /**
+ * Returns the actual scan flags depending upon the state of the other settings.
+ * <p>Updated system applications will not have the following flags set
+ * by default and need to be adjusted after the fact:
+ * <ul>
+ * <li>{@link #SCAN_AS_SYSTEM}</li>
+ * <li>{@link #SCAN_AS_PRIVILEGED}</li>
+ * <li>{@link #SCAN_AS_OEM}</li>
+ * <li>{@link #SCAN_AS_VENDOR}</li>
+ * <li>{@link #SCAN_AS_INSTANT_APP}</li>
+ * <li>{@link #SCAN_AS_VIRTUAL_PRELOAD}</li>
+ * </ul>
+ */
+ private static @ScanFlags int adjustScanFlags(@ScanFlags int scanFlags,
+ PackageSetting pkgSetting, PackageSetting disabledPkgSetting, UserHandle user) {
+ if (disabledPkgSetting != null) {
+ // updated system application, must at least have SCAN_AS_SYSTEM
+ scanFlags |= SCAN_AS_SYSTEM;
+ if ((disabledPkgSetting.pkgPrivateFlags
+ & ApplicationInfo.PRIVATE_FLAG_PRIVILEGED) != 0) {
+ scanFlags |= SCAN_AS_PRIVILEGED;
+ }
+ if ((disabledPkgSetting.pkgPrivateFlags
+ & ApplicationInfo.PRIVATE_FLAG_OEM) != 0) {
+ scanFlags |= SCAN_AS_OEM;
+ }
+ if ((disabledPkgSetting.pkgPrivateFlags
+ & ApplicationInfo.PRIVATE_FLAG_VENDOR) != 0) {
+ scanFlags |= SCAN_AS_VENDOR;
+ }
+ }
+ if (pkgSetting != null) {
+ final int userId = ((user == null) ? 0 : user.getIdentifier());
+ if (pkgSetting.getInstantApp(userId)) {
+ scanFlags |= SCAN_AS_INSTANT_APP;
+ }
+ if (pkgSetting.getVirtulalPreload(userId)) {
+ scanFlags |= SCAN_AS_VIRTUAL_PRELOAD;
+ }
+ }
+ return scanFlags;
+ }
+
@GuardedBy("mInstallLock")
private PackageParser.Package scanPackageNewLI(@NonNull PackageParser.Package pkg,
- final @ParseFlags int parseFlags, final @ScanFlags int scanFlags, long currentTime,
+ final @ParseFlags int parseFlags, @ScanFlags int scanFlags, long currentTime,
@Nullable UserHandle user) throws PackageManagerException {
final String renamedPkgName = mSettings.getRenamedPackageLPr(pkg.mRealPackage);
@@ -9737,6 +9673,7 @@
+ " was transferred to another, but its .apk remains");
}
+ scanFlags = adjustScanFlags(scanFlags, pkgSetting, disabledPkgSetting, user);
synchronized (mPackages) {
applyPolicy(pkg, parseFlags, scanFlags);
assertPackageIsValid(pkg, parseFlags, scanFlags);
@@ -9791,6 +9728,7 @@
final @ScanFlags int scanFlags = request.scanFlags;
final PackageSetting oldPkgSetting = request.oldPkgSetting;
final PackageSetting originalPkgSetting = request.originalPkgSetting;
+ final PackageSetting disabledPkgSetting = request.disabledPkgSetting;
final UserHandle user = request.user;
final String realPkgName = request.realPkgName;
final PackageSetting pkgSetting = result.pkgSetting;
@@ -9856,14 +9794,14 @@
if (ksms.checkUpgradeKeySetLocked(signatureCheckPs, pkg)) {
// We just determined the app is signed correctly, so bring
// over the latest parsed certs.
- pkgSetting.signatures.mSignatures = pkg.mSignatures;
+ pkgSetting.signatures.mSignatures = pkg.mSigningDetails.signatures;
} else {
if ((parseFlags & PackageParser.PARSE_IS_SYSTEM_DIR) == 0) {
throw new PackageManagerException(INSTALL_FAILED_UPDATE_INCOMPATIBLE,
"Package " + pkg.packageName + " upgrade keys do not match the "
+ "previously installed version");
} else {
- pkgSetting.signatures.mSignatures = pkg.mSignatures;
+ pkgSetting.signatures.mSignatures = pkg.mSigningDetails.signatures;
String msg = "System package " + pkg.packageName
+ " signature changed; retaining data.";
reportSettingsProblem(Log.WARN, msg);
@@ -9873,8 +9811,8 @@
try {
final boolean compareCompat = isCompatSignatureUpdateNeeded(pkg);
final boolean compareRecover = isRecoverSignatureUpdateNeeded(pkg);
- final boolean compatMatch = verifySignatures(signatureCheckPs, pkg.mSignatures,
- compareCompat, compareRecover);
+ final boolean compatMatch = verifySignatures(signatureCheckPs, disabledPkgSetting,
+ pkg.mSigningDetails, compareCompat, compareRecover);
// The new KeySets will be re-added later in the scanning process.
if (compatMatch) {
synchronized (mPackages) {
@@ -9883,14 +9821,14 @@
}
// We just determined the app is signed correctly, so bring
// over the latest parsed certs.
- pkgSetting.signatures.mSignatures = pkg.mSignatures;
+ pkgSetting.signatures.mSignatures = pkg.mSigningDetails.signatures;
} catch (PackageManagerException e) {
if ((parseFlags & PackageParser.PARSE_IS_SYSTEM_DIR) == 0) {
throw e;
}
// The signature has changed, but this package is in the system
// image... let's recover!
- pkgSetting.signatures.mSignatures = pkg.mSignatures;
+ pkgSetting.signatures.mSignatures = pkg.mSigningDetails.signatures;
// However... if this package is part of a shared user, but it
// doesn't match the signature of the shared user, let's fail.
// What this means is that you can't change the signatures
@@ -9898,7 +9836,7 @@
// that unreasonable.
if (signatureCheckPs.sharedUser != null) {
if (compareSignatures(signatureCheckPs.sharedUser.signatures.mSignatures,
- pkg.mSignatures) != PackageManager.SIGNATURE_MATCH) {
+ pkg.mSigningDetails.signatures) != PackageManager.SIGNATURE_MATCH) {
throw new PackageManagerException(
INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES,
"Signature mismatch for shared user: "
@@ -9963,10 +9901,16 @@
*/
private static @Nullable String getRealPackageName(@NonNull PackageParser.Package pkg,
@Nullable String renamedPkgName) {
- if (pkg.mOriginalPackages == null || !pkg.mOriginalPackages.contains(renamedPkgName)) {
- return null;
+ if (isPackageRenamed(pkg, renamedPkgName)) {
+ return pkg.mRealPackage;
}
- return pkg.mRealPackage;
+ return null;
+ }
+
+ /** Returns {@code true} if the package has been renamed. Otherwise, {@code false}. */
+ private static boolean isPackageRenamed(@NonNull PackageParser.Package pkg,
+ @Nullable String renamedPkgName) {
+ return pkg.mOriginalPackages != null && pkg.mOriginalPackages.contains(renamedPkgName);
}
/**
@@ -9979,7 +9923,7 @@
@GuardedBy("mPackages")
private @Nullable PackageSetting getOriginalPackageLocked(@NonNull PackageParser.Package pkg,
@Nullable String renamedPkgName) {
- if (pkg.mOriginalPackages == null || pkg.mOriginalPackages.contains(renamedPkgName)) {
+ if (!isPackageRenamed(pkg, renamedPkgName)) {
return null;
}
for (int i = pkg.mOriginalPackages.size() - 1; i >= 0; --i) {
@@ -10102,7 +10046,6 @@
usesStaticLibraries = new String[pkg.usesStaticLibraries.size()];
pkg.usesStaticLibraries.toArray(usesStaticLibraries);
}
-
final boolean createNewPackage = (pkgSetting == null);
if (createNewPackage) {
final String parentPackageName = (pkg.parentPackage != null)
@@ -10126,17 +10069,17 @@
// secondaryCpuAbi are not known at this point so we always update them
// to null here, only to reset them at a later point.
Settings.updatePackageSetting(pkgSetting, disabledPkgSetting, sharedUserSetting,
- destCodeFile, pkg.applicationInfo.nativeLibraryDir,
+ destCodeFile, destResourceFile, pkg.applicationInfo.nativeLibraryDir,
pkg.applicationInfo.primaryCpuAbi, pkg.applicationInfo.secondaryCpuAbi,
pkg.applicationInfo.flags, pkg.applicationInfo.privateFlags,
pkg.getChildPackageNames(), UserManagerService.getInstance(),
usesStaticLibraries, pkg.usesStaticLibrariesVersions);
}
if (createNewPackage && originalPkgSetting != null) {
- // If we are first transitioning from an original package,
- // fix up the new package's name now. We need to do this after
- // looking up the package under its new name, so getPackageLP
- // can take care of fiddling things correctly.
+ // This is the initial transition from the original package, so,
+ // fix up the new package's name now. We must do this after looking
+ // up the package under its new name, so getPackageLP takes care of
+ // fiddling things correctly.
pkg.setPackageName(originalPkgSetting.name);
// File a report about this.
@@ -10223,7 +10166,7 @@
// would've already compiled the app without taking the package setting into
// account.
if ((scanFlags & SCAN_NO_DEX) == 0 && (scanFlags & SCAN_NEW_INSTALL) != 0) {
- if (cpuAbiOverride == null && pkgSetting.cpuAbiOverrideString != null) {
+ if (cpuAbiOverride == null && pkg.packageName != null) {
Slog.w(TAG, "Ignoring persisted ABI override " + cpuAbiOverride +
" for package " + pkg.packageName);
}
@@ -10238,7 +10181,7 @@
pkg.cpuAbiOverride = cpuAbiOverride;
if (DEBUG_ABI_SELECTION) {
- Slog.d(TAG, "Resolved nativeLibraryRoot for " + pkg.applicationInfo.packageName
+ Slog.d(TAG, "Resolved nativeLibraryRoot for " + pkg.packageName
+ " to root=" + pkg.applicationInfo.nativeLibraryRootDir + ", isa="
+ pkg.applicationInfo.nativeLibraryRootRequiresIsa);
}
@@ -10293,6 +10236,22 @@
}
pkgSetting.setTimeStamp(scanFileTime);
+ pkgSetting.pkg = pkg;
+ pkgSetting.pkgFlags = pkg.applicationInfo.flags;
+ if (pkg.getLongVersionCode() != pkgSetting.versionCode) {
+ pkgSetting.versionCode = pkg.getLongVersionCode();
+ }
+ // Update volume if needed
+ final String volumeUuid = pkg.applicationInfo.volumeUuid;
+ if (!Objects.equals(volumeUuid, pkgSetting.volumeUuid)) {
+ Slog.i(PackageManagerService.TAG,
+ "Update" + (pkgSetting.isSystem() ? " system" : "")
+ + " package " + pkg.packageName
+ + " volume from " + pkgSetting.volumeUuid
+ + " to " + volumeUuid);
+ pkgSetting.volumeUuid = volumeUuid;
+ }
+
return new ScanResult(true, pkgSetting, changedAbiCodePath);
}
@@ -13204,7 +13163,7 @@
void installStage(String packageName, File stagedDir,
IPackageInstallObserver2 observer, PackageInstaller.SessionParams sessionParams,
String installerPackageName, int installerUid, UserHandle user,
- Certificate[][] certificates) {
+ PackageParser.SigningDetails signingDetails) {
if (DEBUG_EPHEMERAL) {
if ((sessionParams.installFlags & PackageManager.INSTALL_INSTANT_APP) != 0) {
Slog.d(TAG, "Ephemeral install of " + packageName);
@@ -13222,7 +13181,7 @@
final InstallParams params = new InstallParams(origin, null, observer,
sessionParams.installFlags, installerPackageName, sessionParams.volumeUuid,
verificationInfo, user, sessionParams.abiOverride,
- sessionParams.grantedRuntimePermissions, certificates, installReason);
+ sessionParams.grantedRuntimePermissions, signingDetails, installReason);
params.setTraceMethod("installStage").setTraceCookie(System.identityHashCode(params));
msg.obj = params;
@@ -13826,7 +13785,7 @@
final PackageParser.Package pkg = mPackages.get(verifierInfo.packageName);
if (pkg == null) {
return -1;
- } else if (pkg.mSignatures.length != 1) {
+ } else if (pkg.mSigningDetails.signatures.length != 1) {
Slog.i(TAG, "Verifier package " + verifierInfo.packageName
+ " has more than one signature; ignoring");
return -1;
@@ -13840,7 +13799,7 @@
final byte[] expectedPublicKey;
try {
- final Signature verifierSig = pkg.mSignatures[0];
+ final Signature verifierSig = pkg.mSigningDetails.signatures[0];
final PublicKey publicKey = verifierSig.getPublicKey();
expectedPublicKey = publicKey.getEncoded();
} catch (CertificateException e) {
@@ -14532,13 +14491,13 @@
final String packageAbiOverride;
final String[] grantedRuntimePermissions;
final VerificationInfo verificationInfo;
- final Certificate[][] certificates;
+ final PackageParser.SigningDetails signingDetails;
final int installReason;
InstallParams(OriginInfo origin, MoveInfo move, IPackageInstallObserver2 observer,
int installFlags, String installerPackageName, String volumeUuid,
VerificationInfo verificationInfo, UserHandle user, String packageAbiOverride,
- String[] grantedPermissions, Certificate[][] certificates, int installReason) {
+ String[] grantedPermissions, PackageParser.SigningDetails signingDetails, int installReason) {
super(user);
this.origin = origin;
this.move = move;
@@ -14549,7 +14508,7 @@
this.verificationInfo = verificationInfo;
this.packageAbiOverride = packageAbiOverride;
this.grantedRuntimePermissions = grantedPermissions;
- this.certificates = certificates;
+ this.signingDetails = signingDetails;
this.installReason = installReason;
}
@@ -14980,7 +14939,7 @@
/** If non-null, drop an async trace when the install completes */
final String traceMethod;
final int traceCookie;
- final Certificate[][] certificates;
+ final PackageParser.SigningDetails signingDetails;
final int installReason;
// The list of instruction sets supported by this app. This is currently
@@ -14992,7 +14951,7 @@
int installFlags, String installerPackageName, String volumeUuid,
UserHandle user, String[] instructionSets,
String abiOverride, String[] installGrantPermissions,
- String traceMethod, int traceCookie, Certificate[][] certificates,
+ String traceMethod, int traceCookie, PackageParser.SigningDetails signingDetails,
int installReason) {
this.origin = origin;
this.move = move;
@@ -15006,7 +14965,7 @@
this.installGrantPermissions = installGrantPermissions;
this.traceMethod = traceMethod;
this.traceCookie = traceCookie;
- this.certificates = certificates;
+ this.signingDetails = signingDetails;
this.installReason = installReason;
}
@@ -15102,7 +15061,7 @@
params.installerPackageName, params.volumeUuid,
params.getUser(), null /*instructionSets*/, params.packageAbiOverride,
params.grantedRuntimePermissions,
- params.traceMethod, params.traceCookie, params.certificates,
+ params.traceMethod, params.traceCookie, params.signingDetails,
params.installReason);
if (isFwdLocked()) {
throw new IllegalArgumentException("Forward locking only supported in ASEC");
@@ -15112,7 +15071,7 @@
/** Existing install */
FileInstallArgs(String codePath, String resourcePath, String[] instructionSets) {
super(OriginInfo.fromNothing(), null, null, 0, null, null, null, instructionSets,
- null, null, null, 0, null /*certificates*/,
+ null, null, null, 0, PackageParser.SigningDetails.UNKNOWN,
PackageManager.INSTALL_REASON_UNKNOWN);
this.codeFile = (codePath != null) ? new File(codePath) : null;
this.resourceFile = (resourcePath != null) ? new File(resourcePath) : null;
@@ -15333,7 +15292,7 @@
params.installerPackageName, params.volumeUuid,
params.getUser(), null /* instruction sets */, params.packageAbiOverride,
params.grantedRuntimePermissions,
- params.traceMethod, params.traceCookie, params.certificates,
+ params.traceMethod, params.traceCookie, params.signingDetails,
params.installReason);
}
@@ -15672,7 +15631,8 @@
}
} else {
// default to original signature matching
- if (compareSignatures(oldPackage.mSignatures, pkg.mSignatures)
+ if (compareSignatures(oldPackage.mSigningDetails.signatures,
+ pkg.mSigningDetails.signatures)
!= PackageManager.SIGNATURE_MATCH) {
res.setError(INSTALL_FAILED_UPDATE_INCOMPATIBLE,
"New package has a different signature: " + pkgName);
@@ -16384,7 +16344,6 @@
| PackageParser.PARSE_ENFORCE_CODE
| (forwardLocked ? PackageParser.PARSE_FORWARD_LOCK : 0)
| (onExternal ? PackageParser.PARSE_EXTERNAL_STORAGE : 0)
- | (instantApp ? PackageParser.PARSE_IS_EPHEMERAL : 0)
| (forceSdk ? PackageParser.PARSE_FORCE_SDK : 0);
PackageParser pp = new PackageParser();
pp.setSeparateProcesses(mSeparateProcesses);
@@ -16412,19 +16371,29 @@
return;
}
- // Instant apps must have target SDK >= O and have targetSanboxVersion >= 2
- if (instantApp && pkg.applicationInfo.targetSdkVersion <= Build.VERSION_CODES.N_MR1) {
- Slog.w(TAG, "Instant app package " + pkg.packageName + " does not target O");
- res.setError(INSTALL_FAILED_SANDBOX_VERSION_DOWNGRADE,
- "Instant app package must target O");
- return;
- }
- if (instantApp && pkg.applicationInfo.targetSandboxVersion != 2) {
- Slog.w(TAG, "Instant app package " + pkg.packageName
- + " does not target targetSandboxVersion 2");
- res.setError(INSTALL_FAILED_SANDBOX_VERSION_DOWNGRADE,
- "Instant app package must use targetSanboxVersion 2");
- return;
+ // Instant apps have several additional install-time checks.
+ if (instantApp) {
+ if (pkg.applicationInfo.targetSdkVersion < Build.VERSION_CODES.O) {
+ Slog.w(TAG,
+ "Instant app package " + pkg.packageName + " does not target at least O");
+ res.setError(INSTALL_FAILED_INSTANT_APP_INVALID,
+ "Instant app package must target at least O");
+ return;
+ }
+ if (pkg.applicationInfo.targetSandboxVersion != 2) {
+ Slog.w(TAG, "Instant app package " + pkg.packageName
+ + " does not target targetSandboxVersion 2");
+ res.setError(INSTALL_FAILED_INSTANT_APP_INVALID,
+ "Instant app package must use targetSandboxVersion 2");
+ return;
+ }
+ if (pkg.mSharedUserId != null) {
+ Slog.w(TAG, "Instant app package " + pkg.packageName
+ + " may not declare sharedUserId.");
+ res.setError(INSTALL_FAILED_INSTANT_APP_INVALID,
+ "Instant app package may not declare a sharedUserId");
+ return;
+ }
}
if (pkg.applicationInfo.isStaticSharedLibrary()) {
@@ -16484,14 +16453,8 @@
try {
// either use what we've been given or parse directly from the APK
- if (args.certificates != null) {
- try {
- PackageParser.populateCertificates(pkg, args.certificates);
- } catch (PackageParserException e) {
- // there was something wrong with the certificates we were given;
- // try to pull them from the APK
- PackageParser.collectCertificates(pkg, parseFlags);
- }
+ if (args.signingDetails != PackageParser.SigningDetails.UNKNOWN) {
+ pkg.setSigningDetails(args.signingDetails);
} else {
PackageParser.collectCertificates(pkg, parseFlags);
}
@@ -16500,6 +16463,15 @@
return;
}
+ if (instantApp && pkg.mSigningDetails.signatureSchemeVersion
+ < SignatureSchemeVersion.SIGNING_BLOCK_V2) {
+ Slog.w(TAG, "Instant app package " + pkg.packageName
+ + " is not signed with at least APK Signature Scheme v2");
+ res.setError(INSTALL_FAILED_INSTANT_APP_INVALID,
+ "Instant app package must be signed with APK Signature Scheme v2 or greater");
+ return;
+ }
+
// Get rid of all references to package scan path via parser.
pp = null;
String oldCodePath = null;
@@ -16609,8 +16581,10 @@
try {
final boolean compareCompat = isCompatSignatureUpdateNeeded(pkg);
final boolean compareRecover = isRecoverSignatureUpdateNeeded(pkg);
+ // We don't care about disabledPkgSetting on install for now.
final boolean compatMatch = verifySignatures(
- signatureCheckPs, pkg.mSignatures, compareCompat, compareRecover);
+ signatureCheckPs, null, pkg.mSigningDetails, compareCompat,
+ compareRecover);
// The new KeySets will be re-added later in the scanning process.
if (compatMatch) {
synchronized (mPackages) {
@@ -16661,7 +16635,7 @@
sigsOk = ksms.checkUpgradeKeySetLocked(sourcePackageSetting, pkg);
} else {
sigsOk = compareSignatures(sourcePackageSetting.signatures.mSignatures,
- pkg.mSignatures) == PackageManager.SIGNATURE_MATCH;
+ pkg.mSigningDetails.signatures) == PackageManager.SIGNATURE_MATCH;
}
if (!sigsOk) {
// If the owning package is the system itself, we log but allow
@@ -16937,7 +16911,8 @@
for (ActivityIntentInfo filter : a.intents) {
if (filter.needsVerification() && needsNetworkVerificationLPr(filter)) {
if (DEBUG_DOMAIN_VERIFICATION) {
- Slog.d(TAG, "Intent filter needs verification, so processing all filters");
+ Slog.d(TAG,
+ "Intent filter needs verification, so processing all filters");
}
needToVerify = true;
break;
@@ -18267,7 +18242,8 @@
null /*enabledComponents*/,
null /*disabledComponents*/,
ps.readUserState(nextUserId).domainVerificationStatus,
- 0, PackageManager.INSTALL_REASON_UNKNOWN);
+ 0, PackageManager.INSTALL_REASON_UNKNOWN,
+ null /*harmfulAppWarning*/);
}
mSettings.writeKernelMappingLPr(ps);
}
@@ -22245,8 +22221,8 @@
final OriginInfo origin = OriginInfo.fromExistingFile(codeFile);
final InstallParams params = new InstallParams(origin, move, installObserver, installFlags,
installerPackageName, volumeUuid, null /*verificationInfo*/, user,
- packageAbiOverride, null /*grantedPermissions*/, null /*certificates*/,
- PackageManager.INSTALL_REASON_UNKNOWN);
+ packageAbiOverride, null /*grantedPermissions*/,
+ PackageParser.SigningDetails.UNKNOWN, PackageManager.INSTALL_REASON_UNKNOWN);
params.setTraceMethod("movePackage").setTraceCookie(System.identityHashCode(params));
msg.obj = params;
@@ -23579,6 +23555,47 @@
}
return unusedPackages;
}
+
+ @Override
+ public void setHarmfulAppWarning(@NonNull String packageName, @Nullable CharSequence warning,
+ int userId) {
+ final int callingUid = Binder.getCallingUid();
+ final int callingAppId = UserHandle.getAppId(callingUid);
+
+ mPermissionManager.enforceCrossUserPermission(callingUid, userId,
+ true /*requireFullPermission*/, true /*checkShell*/, "setHarmfulAppInfo");
+
+ if (callingAppId != Process.SYSTEM_UID && callingAppId != Process.ROOT_UID &&
+ checkUidPermission(SET_HARMFUL_APP_WARNINGS, callingUid) != PERMISSION_GRANTED) {
+ throw new SecurityException("Caller must have the "
+ + SET_HARMFUL_APP_WARNINGS + " permission.");
+ }
+
+ synchronized(mPackages) {
+ mSettings.setHarmfulAppWarningLPw(packageName, warning, userId);
+ scheduleWritePackageRestrictionsLocked(userId);
+ }
+ }
+
+ @Nullable
+ @Override
+ public CharSequence getHarmfulAppWarning(@NonNull String packageName, int userId) {
+ final int callingUid = Binder.getCallingUid();
+ final int callingAppId = UserHandle.getAppId(callingUid);
+
+ mPermissionManager.enforceCrossUserPermission(callingUid, userId,
+ true /*requireFullPermission*/, true /*checkShell*/, "getHarmfulAppInfo");
+
+ if (callingAppId != Process.SYSTEM_UID && callingAppId != Process.ROOT_UID &&
+ checkUidPermission(SET_HARMFUL_APP_WARNINGS, callingUid) != PERMISSION_GRANTED) {
+ throw new SecurityException("Caller must have the "
+ + SET_HARMFUL_APP_WARNINGS + " permission.");
+ }
+
+ synchronized(mPackages) {
+ return mSettings.getHarmfulAppWarningLPr(packageName, userId);
+ }
+ }
}
interface PackageSender {
diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
index 20ec9b5..7b96ca6 100644
--- a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
+++ b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
@@ -33,10 +33,12 @@
import com.android.server.pm.dex.PackageDexUsage;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.app.AppGlobals;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.PackageParser;
+import android.content.pm.PackageParser.PackageParserException;
import android.content.pm.ResolveInfo;
import android.content.pm.Signature;
import android.os.Build;
@@ -45,6 +47,7 @@
import android.os.FileUtils;
import android.os.Process;
import android.os.RemoteException;
+import android.os.SystemProperties;
import android.os.UserHandle;
import android.service.pm.PackageServiceDumpProto;
import android.system.ErrnoException;
@@ -504,13 +507,13 @@
* system upgrade) and {@code scannedSigs} will be in the newer format.
*/
private static boolean matchSignaturesCompat(String packageName,
- PackageSignatures packageSignatures, Signature[] parsedSignatures) {
+ PackageSignatures packageSignatures, PackageParser.SigningDetails parsedSignatures) {
ArraySet<Signature> existingSet = new ArraySet<Signature>();
for (Signature sig : packageSignatures.mSignatures) {
existingSet.add(sig);
}
ArraySet<Signature> scannedCompatSet = new ArraySet<Signature>();
- for (Signature sig : parsedSignatures) {
+ for (Signature sig : parsedSignatures.signatures) {
try {
Signature[] chainSignatures = sig.getChainSignatures();
for (Signature chainSig : chainSignatures) {
@@ -547,27 +550,69 @@
}
/**
+ * Make sure the updated priv app is signed with the same key as the original APK file on the
+ * /system partition.
+ *
+ * <p>The rationale is that {@code disabledPkg} is a PackageSetting backed by xml files in /data
+ * and is not tamperproof.
+ */
+ private static boolean matchSignatureInSystem(PackageSetting pkgSetting,
+ PackageSetting disabledPkgSetting) {
+ try {
+ PackageParser.collectCertificates(disabledPkgSetting.pkg,
+ PackageParser.PARSE_IS_SYSTEM_DIR);
+ if (compareSignatures(pkgSetting.signatures.mSignatures,
+ disabledPkgSetting.signatures.mSignatures)
+ != PackageManager.SIGNATURE_MATCH) {
+ logCriticalInfo(Log.ERROR, "Updated system app mismatches cert on /system: " +
+ pkgSetting.name);
+ return false;
+ }
+ } catch (PackageParserException e) {
+ logCriticalInfo(Log.ERROR, "Failed to collect cert for " + pkgSetting.name + ": " +
+ e.getMessage());
+ return false;
+ }
+ return true;
+ }
+
+ /** Returns true to force apk verification if the updated package (in /data) is a priv app. */
+ static boolean isApkVerificationForced(@Nullable PackageSetting disabledPs) {
+ return disabledPs != null && disabledPs.isPrivileged() &&
+ SystemProperties.getInt("ro.apk_verity.mode", 0) != 0;
+ }
+
+ /**
* Verifies that signatures match.
* @returns {@code true} if the compat signatures were matched; otherwise, {@code false}.
* @throws PackageManagerException if the signatures did not match.
*/
public static boolean verifySignatures(PackageSetting pkgSetting,
- Signature[] parsedSignatures, boolean compareCompat, boolean compareRecover)
+ PackageSetting disabledPkgSetting, PackageParser.SigningDetails parsedSignatures,
+ boolean compareCompat, boolean compareRecover)
throws PackageManagerException {
final String packageName = pkgSetting.name;
boolean compatMatch = false;
if (pkgSetting.signatures.mSignatures != null) {
// Already existing package. Make sure signatures match
- boolean match = compareSignatures(pkgSetting.signatures.mSignatures, parsedSignatures)
+ boolean match = compareSignatures(pkgSetting.signatures.mSignatures,
+ parsedSignatures.signatures)
== PackageManager.SIGNATURE_MATCH;
if (!match && compareCompat) {
- match = matchSignaturesCompat(packageName, pkgSetting.signatures, parsedSignatures);
+ match = matchSignaturesCompat(packageName, pkgSetting.signatures,
+ parsedSignatures);
compatMatch = match;
}
if (!match && compareRecover) {
match = matchSignaturesRecover(
- packageName, pkgSetting.signatures.mSignatures, parsedSignatures);
+ packageName, pkgSetting.signatures.mSignatures,
+ parsedSignatures.signatures);
}
+
+ if (!match && isApkVerificationForced(disabledPkgSetting)) {
+ match = matchSignatureInSystem(pkgSetting, disabledPkgSetting);
+ }
+
if (!match) {
throw new PackageManagerException(INSTALL_FAILED_UPDATE_INCOMPATIBLE,
"Package " + packageName +
@@ -578,14 +623,14 @@
if (pkgSetting.sharedUser != null && pkgSetting.sharedUser.signatures.mSignatures != null) {
// Already existing package. Make sure signatures match
boolean match = compareSignatures(pkgSetting.sharedUser.signatures.mSignatures,
- parsedSignatures) == PackageManager.SIGNATURE_MATCH;
+ parsedSignatures.signatures) == PackageManager.SIGNATURE_MATCH;
if (!match && compareCompat) {
match = matchSignaturesCompat(
packageName, pkgSetting.sharedUser.signatures, parsedSignatures);
}
if (!match && compareRecover) {
- match = matchSignaturesRecover(
- packageName, pkgSetting.sharedUser.signatures.mSignatures, parsedSignatures);
+ match = matchSignaturesRecover(packageName,
+ pkgSetting.sharedUser.signatures.mSignatures, parsedSignatures.signatures);
compatMatch |= match;
}
if (!match) {
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index 2d82c46..bd1cd33 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -230,6 +230,8 @@
return runGetInstantAppResolver();
case "has-feature":
return runHasFeature();
+ case "set-harmful-app-warning":
+ return runSetHarmfulAppWarning();
default: {
String nextArg = getNextArg();
if (nextArg == null) {
@@ -1277,7 +1279,7 @@
return runRemoveSplit(packageName, splitName);
}
- userId = translateUserId(userId, "runUninstall");
+ userId = translateUserId(userId, true /*allowAll*/, "runUninstall");
if (userId == UserHandle.USER_ALL) {
userId = UserHandle.USER_SYSTEM;
flags |= PackageManager.DELETE_ALL_USERS;
@@ -2088,6 +2090,29 @@
return 0;
}
+ private int runSetHarmfulAppWarning() throws RemoteException {
+ int userId = UserHandle.USER_CURRENT;
+
+ String opt;
+ while ((opt = getNextOption()) != null) {
+ if (opt.equals("--user")) {
+ userId = UserHandle.parseUserArg(getNextArgRequired());
+ } else {
+ getErrPrintWriter().println("Error: Unknown option: " + opt);
+ return -1;
+ }
+ }
+
+ userId = translateUserId(userId, false /*allowAll*/, "runSetHarmfulAppWarning");
+
+ final String packageName = getNextArgRequired();
+ final String warning = getNextArg();
+
+ mInterface.setHarmfulAppWarning(packageName, warning, userId);
+
+ return 0;
+ }
+
private static String checkAbiArgument(String abi) {
if (TextUtils.isEmpty(abi)) {
throw new IllegalArgumentException("Missing ABI argument");
@@ -2107,14 +2132,14 @@
throw new IllegalArgumentException("ABI " + abi + " not supported on this device");
}
- private int translateUserId(int userId, String logContext) {
+ private int translateUserId(int userId, boolean allowAll, String logContext) {
return ActivityManager.handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(),
- userId, true, true, logContext, "pm command");
+ userId, allowAll, true, logContext, "pm command");
}
private int doCreateSession(SessionParams params, String installerPackageName, int userId)
throws RemoteException {
- userId = translateUserId(userId, "runInstallCreate");
+ userId = translateUserId(userId, true /*allowAll*/, "runInstallCreate");
if (userId == UserHandle.USER_ALL) {
userId = UserHandle.USER_SYSTEM;
params.installFlags |= PackageManager.INSTALL_ALL_USERS;
@@ -2634,6 +2659,9 @@
pw.println("");
pw.println(" get-instantapp-resolver");
pw.println(" Return the name of the component that is the current instant app installer.");
+ pw.println("");
+ pw.println(" set-harmful-app-warning [--user <USER_ID>] <PACKAGE> [<WARNING>]");
+ pw.println(" Mark the app as harmful with the given warning message.");
pw.println();
Intent.printIntentArgsHelp(pw , "");
}
diff --git a/services/core/java/com/android/server/pm/PackageSettingBase.java b/services/core/java/com/android/server/pm/PackageSettingBase.java
index 809e16c..e3c4c43 100644
--- a/services/core/java/com/android/server/pm/PackageSettingBase.java
+++ b/services/core/java/com/android/server/pm/PackageSettingBase.java
@@ -437,7 +437,8 @@
boolean notLaunched, boolean hidden, boolean suspended, boolean instantApp,
boolean virtualPreload, String lastDisableAppCaller,
ArraySet<String> enabledComponents, ArraySet<String> disabledComponents,
- int domainVerifState, int linkGeneration, int installReason) {
+ int domainVerifState, int linkGeneration, int installReason,
+ String harmfulAppWarning) {
PackageUserState state = modifyUserState(userId);
state.ceDataInode = ceDataInode;
state.enabled = enabled;
@@ -454,6 +455,7 @@
state.installReason = installReason;
state.instantApp = instantApp;
state.virtualPreload = virtualPreload;
+ state.harmfulAppWarning = harmfulAppWarning;
}
ArraySet<String> getEnabledComponents(int userId) {
@@ -620,4 +622,14 @@
proto.end(userToken);
}
}
+
+ void setHarmfulAppWarning(int userId, String harmfulAppWarning) {
+ PackageUserState userState = modifyUserState(userId);
+ userState.harmfulAppWarning = harmfulAppWarning;
+ }
+
+ String getHarmfulAppWarning(int userId) {
+ PackageUserState userState = readUserState(userId);
+ return userState.harmfulAppWarning;
+ }
}
diff --git a/services/core/java/com/android/server/pm/PackageSignatures.java b/services/core/java/com/android/server/pm/PackageSignatures.java
index f5c81e4..d567d5c 100644
--- a/services/core/java/com/android/server/pm/PackageSignatures.java
+++ b/services/core/java/com/android/server/pm/PackageSignatures.java
@@ -22,6 +22,8 @@
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlSerializer;
+import android.content.pm.PackageParser;
+import android.content.pm.PackageParser.SigningDetails.SignatureSchemeVersion;
import android.content.pm.Signature;
import android.util.Log;
@@ -30,15 +32,17 @@
class PackageSignatures {
Signature[] mSignatures;
+ @SignatureSchemeVersion int mSignatureSchemeVersion;
PackageSignatures(PackageSignatures orig) {
if (orig != null && orig.mSignatures != null) {
mSignatures = orig.mSignatures.clone();
+ mSignatureSchemeVersion = orig.mSignatureSchemeVersion;
}
}
- PackageSignatures(Signature[] sigs) {
- assignSignatures(sigs);
+ PackageSignatures(PackageParser.SigningDetails signingDetails) {
+ assignSignatures(signingDetails);
}
PackageSignatures() {
@@ -52,6 +56,7 @@
serializer.startTag(null, tagName);
serializer.attribute(null, "count",
Integer.toString(mSignatures.length));
+ serializer.attribute(null, "schemeVersion", Integer.toString(mSignatureSchemeVersion));
for (int i=0; i<mSignatures.length; i++) {
serializer.startTag(null, "cert");
final Signature sig = mSignatures[i];
@@ -84,6 +89,15 @@
+ " no count at " + parser.getPositionDescription());
XmlUtils.skipCurrentTag(parser);
}
+ String schemeVersionStr = parser.getAttributeValue(null, "schemeVersion");
+ if (schemeVersionStr == null) {
+ PackageManagerService.reportSettingsProblem(Log.WARN,
+ "Error in package manager settings: <signatures> has no schemeVersion at "
+ + parser.getPositionDescription());
+ mSignatureSchemeVersion = SignatureSchemeVersion.UNKNOWN;
+ } else {
+ mSignatureSchemeVersion = Integer.parseInt(countStr);
+ }
final int count = Integer.parseInt(countStr);
mSignatures = new Signature[count];
int pos = 0;
@@ -174,14 +188,15 @@
}
}
- void assignSignatures(Signature[] sigs) {
- if (sigs == null) {
+ void assignSignatures(PackageParser.SigningDetails signingDetails) {
+ mSignatureSchemeVersion = signingDetails.signatureSchemeVersion;
+ if (!signingDetails.hasSignatures()) {
mSignatures = null;
return;
}
- mSignatures = new Signature[sigs.length];
- for (int i=0; i<sigs.length; i++) {
- mSignatures[i] = sigs[i];
+ mSignatures = new Signature[signingDetails.signatures.length];
+ for (int i=0; i<signingDetails.signatures.length; i++) {
+ mSignatures[i] = signingDetails.signatures[i];
}
}
@@ -190,7 +205,9 @@
StringBuffer buf = new StringBuffer(128);
buf.append("PackageSignatures{");
buf.append(Integer.toHexString(System.identityHashCode(this)));
- buf.append(" [");
+ buf.append(" version:");
+ buf.append(mSignatureSchemeVersion);
+ buf.append(", signatures:[");
if (mSignatures != null) {
for (int i=0; i<mSignatures.length; i++) {
if (i > 0) buf.append(", ");
diff --git a/services/core/java/com/android/server/pm/SELinuxMMAC.java b/services/core/java/com/android/server/pm/SELinuxMMAC.java
index fbf3d82..2552643 100644
--- a/services/core/java/com/android/server/pm/SELinuxMMAC.java
+++ b/services/core/java/com/android/server/pm/SELinuxMMAC.java
@@ -17,9 +17,8 @@
package com.android.server.pm;
import android.content.pm.PackageParser;
-import android.content.pm.PackageUserState;
-import android.content.pm.SELinuxUtil;
import android.content.pm.Signature;
+import android.content.pm.PackageParser.SigningDetails;
import android.os.Environment;
import android.util.Slog;
import android.util.Xml;
@@ -453,7 +452,8 @@
public String getMatchedSeInfo(PackageParser.Package pkg) {
// Check for exact signature matches across all certs.
Signature[] certs = mCerts.toArray(new Signature[0]);
- if (!Signature.areExactMatch(certs, pkg.mSignatures)) {
+ if (pkg.mSigningDetails != SigningDetails.UNKNOWN
+ && !Signature.areExactMatch(certs, pkg.mSigningDetails.signatures)) {
return null;
}
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index 4cf1814..ecbc452 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -227,6 +227,7 @@
private static final String ATTR_INSTALL_REASON = "install-reason";
private static final String ATTR_INSTANT_APP = "instant-app";
private static final String ATTR_VIRTUAL_PRELOAD = "virtual-preload";
+ private static final String ATTR_HARMFUL_APP_WARNING = "harmful-app-warning";
private static final String ATTR_PACKAGE_NAME = "packageName";
private static final String ATTR_FINGERPRINT = "fingerprint";
@@ -742,7 +743,8 @@
null /*enabledComponents*/,
null /*disabledComponents*/,
INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED,
- 0, PackageManager.INSTALL_REASON_UNKNOWN);
+ 0, PackageManager.INSTALL_REASON_UNKNOWN,
+ null /*harmfulAppWarning*/);
}
}
}
@@ -783,11 +785,12 @@
*/
static void updatePackageSetting(@NonNull PackageSetting pkgSetting,
@Nullable PackageSetting disabledPkg, @Nullable SharedUserSetting sharedUser,
- @NonNull File codePath, @Nullable String legacyNativeLibraryPath,
- @Nullable String primaryCpuAbi, @Nullable String secondaryCpuAbi,
- int pkgFlags, int pkgPrivateFlags, @Nullable List<String> childPkgNames,
- @NonNull UserManagerService userManager, @Nullable String[] usesStaticLibraries,
- @Nullable long[] usesStaticLibrariesVersions) throws PackageManagerException {
+ @NonNull File codePath, File resourcePath,
+ @Nullable String legacyNativeLibraryPath, @Nullable String primaryCpuAbi,
+ @Nullable String secondaryCpuAbi, int pkgFlags, int pkgPrivateFlags,
+ @Nullable List<String> childPkgNames, @NonNull UserManagerService userManager,
+ @Nullable String[] usesStaticLibraries, @Nullable long[] usesStaticLibrariesVersions)
+ throws PackageManagerException {
final String pkgName = pkgSetting.name;
if (pkgSetting.sharedUser != sharedUser) {
PackageManagerService.reportSettingsProblem(Log.WARN,
@@ -799,29 +802,19 @@
}
if (!pkgSetting.codePath.equals(codePath)) {
- // Check to see if its a disabled system app
- if ((pkgSetting.pkgFlags & ApplicationInfo.FLAG_SYSTEM) != 0) {
- // This is an updated system app with versions in both system
- // and data partition. Just let the most recent version
- // take precedence.
- Slog.w(PackageManagerService.TAG,
- "Trying to update system app code path from "
- + pkgSetting.codePathString + " to " + codePath.toString());
- } else {
- // Just a change in the code path is not an issue, but
- // let's log a message about it.
- Slog.i(PackageManagerService.TAG,
- "Package " + pkgName + " codePath changed from "
- + pkgSetting.codePath + " to " + codePath
- + "; Retaining data and using new");
-
- // The owner user's installed flag is set false
- // when the application was installed by other user
- // and the installed flag is not updated
- // when the application is appended as system app later.
- if ((pkgFlags & ApplicationInfo.FLAG_SYSTEM) != 0
- && disabledPkg == null) {
- List<UserInfo> allUserInfos = getAllUsers(userManager);
+ final boolean isSystem = pkgSetting.isSystem();
+ Slog.i(PackageManagerService.TAG,
+ "Update" + (isSystem ? " system" : "")
+ + " package " + pkgName
+ + " code path from " + pkgSetting.codePathString
+ + " to " + codePath.toString()
+ + "; Retain data and using new");
+ if (!isSystem) {
+ // The package isn't considered as installed if the application was
+ // first installed by another user. Update the installed flag when the
+ // application ever becomes part of the system.
+ if ((pkgFlags & ApplicationInfo.FLAG_SYSTEM) != 0 && disabledPkg == null) {
+ final List<UserInfo> allUserInfos = getAllUsers(userManager);
if (allUserInfos != null) {
for (UserInfo userInfo : allUserInfos) {
pkgSetting.setInstalled(true, userInfo.id);
@@ -829,14 +822,24 @@
}
}
- /*
- * Since we've changed paths, we need to prefer the new
- * native library path over the one stored in the
- * package settings since we might have moved from
- * internal to external storage or vice versa.
- */
+ // Since we've changed paths, prefer the new native library path over
+ // the one stored in the package settings since we might have moved from
+ // internal to external storage or vice versa.
pkgSetting.legacyNativeLibraryPathString = legacyNativeLibraryPath;
}
+ pkgSetting.codePath = codePath;
+ pkgSetting.codePathString = codePath.toString();
+ }
+ if (!pkgSetting.resourcePath.equals(resourcePath)) {
+ final boolean isSystem = pkgSetting.isSystem();
+ Slog.i(PackageManagerService.TAG,
+ "Update" + (isSystem ? " system" : "")
+ + " package " + pkgName
+ + " resource path from " + pkgSetting.resourcePathString
+ + " to " + resourcePath.toString()
+ + "; Retain data and using new");
+ pkgSetting.resourcePath = resourcePath;
+ pkgSetting.resourcePathString = resourcePath.toString();
}
// If what we are scanning is a system (and possibly privileged) package,
// then make it so, regardless of whether it was previously installed only
@@ -853,13 +856,14 @@
if (childPkgNames != null) {
pkgSetting.childPackageNames = new ArrayList<>(childPkgNames);
}
- if (usesStaticLibraries != null) {
- pkgSetting.usesStaticLibraries = Arrays.copyOf(usesStaticLibraries,
- usesStaticLibraries.length);
- }
- if (usesStaticLibrariesVersions != null) {
- pkgSetting.usesStaticLibrariesVersions = Arrays.copyOf(usesStaticLibrariesVersions,
- usesStaticLibrariesVersions.length);
+ // Update static shared library dependencies if needed
+ if (usesStaticLibraries != null && usesStaticLibrariesVersions != null
+ && usesStaticLibraries.length == usesStaticLibrariesVersions.length) {
+ pkgSetting.usesStaticLibraries = usesStaticLibraries;
+ pkgSetting.usesStaticLibrariesVersions = usesStaticLibrariesVersions;
+ } else {
+ pkgSetting.usesStaticLibraries = null;
+ pkgSetting.usesStaticLibrariesVersions = null;
}
}
@@ -912,69 +916,17 @@
userId);
}
+ // TODO: Move to scanPackageOnlyLI() after verifying signatures are setup correctly
+ // by that time.
void insertPackageSettingLPw(PackageSetting p, PackageParser.Package pkg) {
- p.pkg = pkg;
- // pkg.mSetEnabled = p.getEnabled(userId);
- // pkg.mSetStopped = p.getStopped(userId);
- final String volumeUuid = pkg.applicationInfo.volumeUuid;
- final String codePath = pkg.applicationInfo.getCodePath();
- final String resourcePath = pkg.applicationInfo.getResourcePath();
- final String legacyNativeLibraryPath = pkg.applicationInfo.nativeLibraryRootDir;
- // Update volume if needed
- if (!Objects.equals(volumeUuid, p.volumeUuid)) {
- Slog.w(PackageManagerService.TAG, "Volume for " + p.pkg.packageName +
- " changing from " + p.volumeUuid + " to " + volumeUuid);
- p.volumeUuid = volumeUuid;
- }
- // Update code path if needed
- if (!Objects.equals(codePath, p.codePathString)) {
- Slog.w(PackageManagerService.TAG, "Code path for " + p.pkg.packageName +
- " changing from " + p.codePathString + " to " + codePath);
- p.codePath = new File(codePath);
- p.codePathString = codePath;
- }
- //Update resource path if needed
- if (!Objects.equals(resourcePath, p.resourcePathString)) {
- Slog.w(PackageManagerService.TAG, "Resource path for " + p.pkg.packageName +
- " changing from " + p.resourcePathString + " to " + resourcePath);
- p.resourcePath = new File(resourcePath);
- p.resourcePathString = resourcePath;
- }
- // Update the native library paths if needed
- if (!Objects.equals(legacyNativeLibraryPath, p.legacyNativeLibraryPathString)) {
- p.legacyNativeLibraryPathString = legacyNativeLibraryPath;
- }
-
- // Update the required Cpu Abi
- p.primaryCpuAbiString = pkg.applicationInfo.primaryCpuAbi;
- p.secondaryCpuAbiString = pkg.applicationInfo.secondaryCpuAbi;
- p.cpuAbiOverrideString = pkg.cpuAbiOverride;
- // Update version code if needed
- if (pkg.getLongVersionCode() != p.versionCode) {
- p.versionCode = pkg.getLongVersionCode();
- }
// Update signatures if needed.
if (p.signatures.mSignatures == null) {
- p.signatures.assignSignatures(pkg.mSignatures);
- }
- // Update flags if needed.
- if (pkg.applicationInfo.flags != p.pkgFlags) {
- p.pkgFlags = pkg.applicationInfo.flags;
+ p.signatures.assignSignatures(pkg.mSigningDetails);
}
// If this app defines a shared user id initialize
// the shared user signatures as well.
if (p.sharedUser != null && p.sharedUser.signatures.mSignatures == null) {
- p.sharedUser.signatures.assignSignatures(pkg.mSignatures);
- }
- // Update static shared library dependencies if needed
- if (pkg.usesStaticLibraries != null && pkg.usesStaticLibrariesVersions != null
- && pkg.usesStaticLibraries.size() == pkg.usesStaticLibrariesVersions.length) {
- p.usesStaticLibraries = new String[pkg.usesStaticLibraries.size()];
- pkg.usesStaticLibraries.toArray(p.usesStaticLibraries);
- p.usesStaticLibrariesVersions = pkg.usesStaticLibrariesVersions;
- } else {
- p.usesStaticLibraries = null;
- p.usesStaticLibrariesVersions = null;
+ p.sharedUser.signatures.assignSignatures(pkg.mSigningDetails);
}
addPackageSettingLPw(p, p.sharedUser);
}
@@ -1680,7 +1632,8 @@
null /*enabledComponents*/,
null /*disabledComponents*/,
INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED,
- 0, PackageManager.INSTALL_REASON_UNKNOWN);
+ 0, PackageManager.INSTALL_REASON_UNKNOWN,
+ null /*harmfulAppWarning*/);
}
return;
}
@@ -1755,7 +1708,8 @@
COMPONENT_ENABLED_STATE_DEFAULT);
final String enabledCaller = parser.getAttributeValue(null,
ATTR_ENABLED_CALLER);
-
+ final String harmfulAppWarning =
+ parser.getAttributeValue(null, ATTR_HARMFUL_APP_WARNING);
final int verifState = XmlUtils.readIntAttribute(parser,
ATTR_DOMAIN_VERIFICATON_STATE,
PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED);
@@ -1792,7 +1746,7 @@
ps.setUserState(userId, ceDataInode, enabled, installed, stopped, notLaunched,
hidden, suspended, instantApp, virtualPreload, enabledCaller,
enabledComponents, disabledComponents, verifState, linkGeneration,
- installReason);
+ installReason, harmfulAppWarning);
} else if (tagName.equals("preferred-activities")) {
readPreferredActivitiesLPw(parser, userId);
} else if (tagName.equals(TAG_PERSISTENT_PREFERRED_ACTIVITIES)) {
@@ -2125,6 +2079,10 @@
serializer.attribute(null, ATTR_INSTALL_REASON,
Integer.toString(ustate.installReason));
}
+ if (ustate.harmfulAppWarning != null) {
+ serializer.attribute(null, ATTR_HARMFUL_APP_WARNING,
+ ustate.harmfulAppWarning);
+ }
if (!ArrayUtils.isEmpty(ustate.enabledComponents)) {
serializer.startTag(null, TAG_ENABLED_COMPONENTS);
for (final String name : ustate.enabledComponents) {
@@ -4347,6 +4305,22 @@
return false;
}
+ void setHarmfulAppWarningLPw(String packageName, CharSequence warning, int userId) {
+ final PackageSetting pkgSetting = mPackages.get(packageName);
+ if (pkgSetting == null) {
+ throw new IllegalArgumentException("Unknown package: " + packageName);
+ }
+ pkgSetting.setHarmfulAppWarning(userId, warning == null ? null : warning.toString());
+ }
+
+ String getHarmfulAppWarningLPr(String packageName, int userId) {
+ final PackageSetting pkgSetting = mPackages.get(packageName);
+ if (pkgSetting == null) {
+ throw new IllegalArgumentException("Unknown package: " + packageName);
+ }
+ return pkgSetting.getHarmfulAppWarning(userId);
+ }
+
private static List<UserInfo> getAllUsers(UserManagerService userManager) {
long id = Binder.clearCallingIdentity();
try {
@@ -4493,11 +4467,14 @@
pw.print(ps.getNotLaunched(user.id) ? "l" : "L");
pw.print(ps.getInstantApp(user.id) ? "IA" : "ia");
pw.print(ps.getVirtulalPreload(user.id) ? "VPI" : "vpi");
+ String harmfulAppWarning = ps.getHarmfulAppWarning(user.id);
+ pw.print(harmfulAppWarning != null ? "HA" : "ha");
pw.print(",");
pw.print(ps.getEnabled(user.id));
String lastDisabledAppCaller = ps.getLastDisabledAppCaller(user.id);
pw.print(",");
pw.print(lastDisabledAppCaller != null ? lastDisabledAppCaller : "?");
+ pw.print(",");
pw.println();
}
return;
@@ -4565,10 +4542,8 @@
}
pw.print(prefix); pw.print(" versionName="); pw.println(ps.pkg.mVersionName);
pw.print(prefix); pw.print(" splits="); dumpSplitNames(pw, ps.pkg); pw.println();
- final int apkSigningVersion = PackageParser.getApkSigningVersion(ps.pkg);
- if (apkSigningVersion != PackageParser.APK_SIGNING_UNKNOWN) {
- pw.print(prefix); pw.print(" apkSigningVersion="); pw.println(apkSigningVersion);
- }
+ final int apkSigningVersion = ps.pkg.mSigningDetails.signatureSchemeVersion;
+ pw.print(prefix); pw.print(" apkSigningVersion="); pw.println(apkSigningVersion);
pw.print(prefix); pw.print(" applicationInfo=");
pw.println(ps.pkg.applicationInfo.toString());
pw.print(prefix); pw.print(" flags="); printFlags(pw, ps.pkg.applicationInfo.flags,
@@ -4772,6 +4747,12 @@
.getRuntimePermissionStates(user.id), dumpAll);
}
+ String harmfulAppWarning = ps.getHarmfulAppWarning(user.id);
+ if (harmfulAppWarning != null) {
+ pw.print(prefix); pw.print(" harmfulAppWarning: ");
+ pw.println(harmfulAppWarning);
+ }
+
if (permissionNames == null) {
ArraySet<String> cmp = ps.getDisabledComponents(user.id);
if (cmp != null && cmp.size() > 0) {
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 7d57566..92fd904 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -394,7 +394,7 @@
/**
* Start an {@link IntentSender} when user is unlocked after disabling quiet mode.
*
- * @see {@link #trySetQuietModeEnabled(String, boolean, int, IntentSender)}
+ * @see {@link #requestQuietModeEnabled(String, boolean, int, IntentSender)}
*/
private class DisableQuietModeUserUnlockedCallback extends IProgressListener.Stub {
private final IntentSender mTarget;
@@ -823,7 +823,7 @@
}
@Override
- public boolean trySetQuietModeEnabled(@NonNull String callingPackage, boolean enableQuietMode,
+ public boolean requestQuietModeEnabled(@NonNull String callingPackage, boolean enableQuietMode,
int userHandle, @Nullable IntentSender target) {
Preconditions.checkNotNull(callingPackage);
diff --git a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
index bfba700..50690cb 100644
--- a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
+++ b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
@@ -116,7 +116,10 @@
UserManager.DISALLOW_USER_SWITCH,
UserManager.DISALLOW_UNIFIED_PASSWORD,
UserManager.DISALLOW_CONFIG_LOCATION_MODE,
- UserManager.DISALLOW_AIRPLANE_MODE
+ UserManager.DISALLOW_AIRPLANE_MODE,
+ UserManager.DISALLOW_CONFIG_BRIGHTNESS,
+ UserManager.DISALLOW_SHARE_INTO_MANAGED_PROFILE,
+ UserManager.DISALLOW_AMBIENT_DISPLAY
});
/**
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 34c3ce3..6e07eaa 100644
--- a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
+++ b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
@@ -27,7 +27,6 @@
import android.companion.CompanionDeviceManager;
import android.content.Context;
import android.content.Intent;
-import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.IPackageManager;
import android.content.pm.PackageList;
@@ -62,8 +61,6 @@
import android.util.Xml;
import com.android.internal.util.XmlUtils;
import com.android.server.LocalServices;
-import com.android.server.pm.PackageManagerService;
-import com.android.server.pm.PackageSetting;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
@@ -1169,7 +1166,8 @@
final String systemPackageName = mServiceInternal.getKnownPackageName(
PackageManagerInternal.PACKAGE_SYSTEM, UserHandle.USER_SYSTEM);
final PackageParser.Package systemPackage = getPackage(systemPackageName);
- return compareSignatures(systemPackage.mSignatures, pkg.mSignatures)
+ return compareSignatures(systemPackage.mSigningDetails.signatures,
+ pkg.mSigningDetails.signatures)
== PackageManager.SIGNATURE_MATCH;
}
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 90ac4ab..786b998 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
@@ -29,7 +29,6 @@
import android.Manifest;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.app.AppOpsManager;
import android.content.Context;
import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
@@ -56,21 +55,17 @@
import android.util.Slog;
import android.util.SparseArray;
-import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.internal.os.RoSystemProperties;
import com.android.internal.util.ArrayUtils;
-import com.android.server.FgThread;
import com.android.server.LocalServices;
import com.android.server.ServiceThread;
import com.android.server.SystemConfig;
import com.android.server.Watchdog;
-import com.android.server.pm.PackageManagerService;
import com.android.server.pm.PackageManagerServiceUtils;
import com.android.server.pm.PackageSetting;
-import com.android.server.pm.ProcessLoggingHandler;
import com.android.server.pm.SharedUserSetting;
import com.android.server.pm.UserManagerService;
import com.android.server.pm.permission.DefaultPermissionGrantPolicy.DefaultPermissionGrantedCallback;
@@ -1015,10 +1010,10 @@
final PackageParser.Package systemPackage =
mPackageManagerInt.getPackage(systemPackageName);
boolean allowed = (PackageManagerServiceUtils.compareSignatures(
- bp.getSourceSignatures(), pkg.mSignatures)
+ bp.getSourceSignatures(), pkg.mSigningDetails.signatures)
== PackageManager.SIGNATURE_MATCH)
|| (PackageManagerServiceUtils.compareSignatures(
- systemPackage.mSignatures, pkg.mSignatures)
+ systemPackage.mSigningDetails.signatures, pkg.mSigningDetails.signatures)
== PackageManager.SIGNATURE_MATCH);
if (!allowed && (privilegedPermission || oemPermission)) {
if (pkg.isSystem()) {
diff --git a/services/core/java/com/android/server/policy/BarController.java b/services/core/java/com/android/server/policy/BarController.java
index 10d9565..c906705 100644
--- a/services/core/java/com/android/server/policy/BarController.java
+++ b/services/core/java/com/android/server/policy/BarController.java
@@ -16,13 +16,16 @@
package com.android.server.policy;
+import static com.android.server.wm.proto.BarControllerProto.STATE;
+import static com.android.server.wm.proto.BarControllerProto.TRANSIENT_STATE;
+
import android.app.StatusBarManager;
import android.os.Handler;
import android.os.Message;
import android.os.SystemClock;
import android.util.Slog;
+import android.util.proto.ProtoOutputStream;
import android.view.View;
-import android.view.ViewGroup;
import android.view.WindowManager;
import com.android.server.LocalServices;
@@ -311,6 +314,13 @@
throw new IllegalArgumentException("Unknown state " + state);
}
+ public void writeToProto(ProtoOutputStream proto, long fieldId) {
+ final long token = proto.start(fieldId);
+ proto.write(STATE, mState);
+ proto.write(TRANSIENT_STATE, mTransientBarState);
+ proto.end(token);
+ }
+
public void dump(PrintWriter pw, String prefix) {
if (mWin != null) {
pw.print(prefix); pw.println(mTag);
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 75d211b..d352559 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -127,6 +127,26 @@
import static com.android.server.policy.WindowManagerPolicy.WindowManagerFuncs.LID_ABSENT;
import static com.android.server.policy.WindowManagerPolicy.WindowManagerFuncs.LID_CLOSED;
import static com.android.server.policy.WindowManagerPolicy.WindowManagerFuncs.LID_OPEN;
+import static com.android.server.wm.proto.WindowManagerPolicyProto.FOCUSED_APP_TOKEN;
+import static com.android.server.wm.proto.WindowManagerPolicyProto.FOCUSED_WINDOW;
+import static com.android.server.wm.proto.WindowManagerPolicyProto.FORCE_STATUS_BAR;
+import static com.android.server.wm.proto.WindowManagerPolicyProto.FORCE_STATUS_BAR_FROM_KEYGUARD;
+import static com.android.server.wm.proto.WindowManagerPolicyProto.KEYGUARD_DELEGATE;
+import static com.android.server.wm.proto.WindowManagerPolicyProto.KEYGUARD_DRAW_COMPLETE;
+import static com.android.server.wm.proto.WindowManagerPolicyProto.KEYGUARD_OCCLUDED;
+import static com.android.server.wm.proto.WindowManagerPolicyProto.KEYGUARD_OCCLUDED_CHANGED;
+import static com.android.server.wm.proto.WindowManagerPolicyProto.KEYGUARD_OCCLUDED_PENDING;
+import static com.android.server.wm.proto.WindowManagerPolicyProto.LAST_SYSTEM_UI_FLAGS;
+import static com.android.server.wm.proto.WindowManagerPolicyProto.NAVIGATION_BAR;
+import static com.android.server.wm.proto.WindowManagerPolicyProto.ORIENTATION;
+import static com.android.server.wm.proto.WindowManagerPolicyProto.ORIENTATION_LISTENER;
+import static com.android.server.wm.proto.WindowManagerPolicyProto.ROTATION;
+import static com.android.server.wm.proto.WindowManagerPolicyProto.ROTATION_MODE;
+import static com.android.server.wm.proto.WindowManagerPolicyProto.SCREEN_ON_FULLY;
+import static com.android.server.wm.proto.WindowManagerPolicyProto.STATUS_BAR;
+import static com.android.server.wm.proto.WindowManagerPolicyProto.TOP_FULLSCREEN_OPAQUE_OR_DIMMING_WINDOW;
+import static com.android.server.wm.proto.WindowManagerPolicyProto.TOP_FULLSCREEN_OPAQUE_WINDOW;
+import static com.android.server.wm.proto.WindowManagerPolicyProto.WINDOW_MANAGER_DRAW_COMPLETE;
import android.annotation.Nullable;
import android.app.ActivityManager;
@@ -141,6 +161,7 @@
import android.app.UiModeManager;
import android.content.ActivityNotFoundException;
import android.content.BroadcastReceiver;
+import android.content.ComponentCallbacks;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
@@ -172,7 +193,6 @@
import android.media.IAudioService;
import android.media.session.MediaSessionLegacyHelper;
import android.os.Binder;
-import android.os.Build;
import android.os.Bundle;
import android.os.FactoryTest;
import android.os.Handler;
@@ -250,6 +270,7 @@
import com.android.internal.policy.PhoneWindow;
import com.android.internal.statusbar.IStatusBarService;
import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.ScreenshotHelper;
import com.android.internal.util.ScreenShapeHelper;
import com.android.internal.widget.PointerLocationView;
import com.android.server.GestureLauncherService;
@@ -441,6 +462,7 @@
AccessibilityManager mAccessibilityManager;
BurnInProtectionHelper mBurnInProtectionHelper;
AppOpsManager mAppOpsManager;
+ private ScreenshotHelper mScreenshotHelper;
private boolean mHasFeatureWatch;
private boolean mHasFeatureLeanback;
@@ -602,8 +624,6 @@
PointerLocationView mPointerLocationView;
- boolean mEmulateDisplayCutout = false;
-
// During layout, the layer at which the doc window is placed.
int mDockLayer;
// During layout, this is the layer of the status bar.
@@ -699,6 +719,9 @@
// Behavior of Back button while in-call and screen on
int mIncallBackBehavior;
+ // Behavior of rotation suggestions. (See Settings.Secure.SHOW_ROTATION_SUGGESTION)
+ int mShowRotationSuggestions;
+
Display mDisplay;
int mLandscapeRotation = 0; // default landscape rotation
@@ -953,11 +976,11 @@
resolver.registerContentObserver(Settings.Secure.getUriFor(
Settings.Secure.IMMERSIVE_MODE_CONFIRMATIONS), false, this,
UserHandle.USER_ALL);
- resolver.registerContentObserver(Settings.Global.getUriFor(
- Settings.Global.POLICY_CONTROL), false, this,
+ resolver.registerContentObserver(Settings.Secure.getUriFor(
+ Settings.Secure.SHOW_ROTATION_SUGGESTIONS), false, this,
UserHandle.USER_ALL);
resolver.registerContentObserver(Settings.Global.getUriFor(
- Settings.Global.EMULATE_DISPLAY_CUTOUT), false, this,
+ Settings.Global.POLICY_CONTROL), false, this,
UserHandle.USER_ALL);
updateSettings();
}
@@ -986,23 +1009,41 @@
}
class MyOrientationListener extends WindowOrientationListener {
- private final Runnable mUpdateRotationRunnable = new Runnable() {
+
+ private SparseArray<Runnable> mRunnableCache;
+
+ MyOrientationListener(Context context, Handler handler) {
+ super(context, handler);
+ mRunnableCache = new SparseArray<>(5);
+ }
+
+ private class UpdateRunnable implements Runnable {
+ private final int mRotation;
+ UpdateRunnable(int rotation) {
+ mRotation = rotation;
+ }
+
@Override
public void run() {
// send interaction hint to improve redraw performance
mPowerManagerInternal.powerHint(PowerHint.INTERACTION, 0);
- updateRotation(false);
+ if (showRotationChoice(mCurrentAppOrientation, mRotation)) {
+ sendProposedRotationChangeToStatusBarInternal(mRotation);
+ } else {
+ updateRotation(false);
+ }
}
- };
-
- MyOrientationListener(Context context, Handler handler) {
- super(context, handler);
}
@Override
public void onProposedRotationChanged(int rotation) {
if (localLOGV) Slog.v(TAG, "onProposedRotationChanged, rotation=" + rotation);
- mHandler.post(mUpdateRotationRunnable);
+ Runnable r = mRunnableCache.get(rotation, null);
+ if (r == null){
+ r = new UpdateRunnable(rotation);
+ mRunnableCache.put(rotation, r);
+ }
+ mHandler.post(r);
}
}
MyOrientationListener mOrientationListener;
@@ -1107,7 +1148,11 @@
// orientation for a little bit, which can cause orientation
// changes to lag, so we'd like to keep it always on. (It will
// still be turned off when the screen is off.)
- return false;
+
+ // When locked we can provide rotation suggestions users can approve to change the
+ // current screen rotation. To do this the sensor needs to be running.
+ return mSupportAutoRotation &&
+ mShowRotationSuggestions == Settings.Secure.SHOW_ROTATION_SUGGESTIONS_ENABLED;
}
return mSupportAutoRotation;
}
@@ -1666,7 +1711,9 @@
@Override
public void run() {
- takeScreenshot(mScreenshotType);
+ mScreenshotHelper.takeScreenshot(mScreenshotType,
+ mStatusBar != null && mStatusBar.isVisibleLw(),
+ mNavigationBar != null && mNavigationBar.isVisibleLw(), mHandler);
}
}
@@ -2144,6 +2191,7 @@
mWindowManagerFuncs.notifyKeyguardTrustedChanged();
}
});
+ mScreenshotHelper = new ScreenshotHelper(mContext);
}
/**
@@ -2256,9 +2304,13 @@
// http://developer.android.com/guide/practices/screens_support.html#range
// For car, ignore the dp limitation. It's physically impossible to rotate the car's screen
// so if the orientation is forced, we need to respect that no matter what.
- boolean isCar = mContext.getPackageManager().hasSystemFeature(
+ final boolean isCar = mContext.getPackageManager().hasSystemFeature(
PackageManager.FEATURE_AUTOMOTIVE);
- mForceDefaultOrientation = ((longSizeDp >= 960 && shortSizeDp >= 720) || isCar) &&
+ // For TV, it's usually 960dp x 540dp, ignore the size limitation.
+ // so if the orientation is forced, we need to respect that no matter what.
+ final boolean isTv = mContext.getPackageManager().hasSystemFeature(
+ PackageManager.FEATURE_LEANBACK);
+ mForceDefaultOrientation = ((longSizeDp >= 960 && shortSizeDp >= 720) || isCar || isTv) &&
res.getBoolean(com.android.internal.R.bool.config_forceDefaultOrientation) &&
// For debug purposes the next line turns this feature off with:
// $ adb shell setprop config.override_forced_orient true
@@ -2296,6 +2348,16 @@
Settings.Secure.INCALL_BACK_BUTTON_BEHAVIOR_DEFAULT,
UserHandle.USER_CURRENT);
+ // Configure rotation suggestions.
+ int showRotationSuggestions = Settings.Secure.getIntForUser(resolver,
+ Settings.Secure.SHOW_ROTATION_SUGGESTIONS,
+ Settings.Secure.SHOW_ROTATION_SUGGESTIONS_DEFAULT,
+ UserHandle.USER_CURRENT);
+ if (mShowRotationSuggestions != showRotationSuggestions) {
+ mShowRotationSuggestions = showRotationSuggestions;
+ updateOrientationListenerLp(); // Enable, disable the orientation listener
+ }
+
// Configure wake gesture.
boolean wakeGestureEnabledSetting = Settings.Secure.getIntForUser(resolver,
Settings.Secure.WAKE_GESTURE_ENABLED, 0,
@@ -2345,10 +2407,6 @@
if (mImmersiveModeConfirmation != null) {
mImmersiveModeConfirmation.loadSetting(mCurrentUserId);
}
- mEmulateDisplayCutout = Settings.Global.getInt(resolver,
- Settings.Global.EMULATE_DISPLAY_CUTOUT,
- Settings.Global.EMULATE_DISPLAY_CUTOUT_OFF)
- != Settings.Global.EMULATE_DISPLAY_CUTOUT_OFF;
}
synchronized (mWindowManagerFuncs.getWindowManagerLock()) {
PolicyControl.reloadFromSetting(mContext);
@@ -2684,6 +2742,11 @@
}
@Override
+ public void onOverlayChangedLw() {
+ onConfigurationChanged();
+ }
+
+ @Override
public void onConfigurationChanged() {
// TODO(multi-display): Define policy for secondary displays.
Context uiContext = getSystemUiContext();
@@ -4383,7 +4446,7 @@
/** {@inheritDoc} */
@Override
public void beginLayoutLw(DisplayFrames displayFrames, int uiMode) {
- displayFrames.onBeginLayout(mEmulateDisplayCutout, mStatusBarHeight);
+ displayFrames.onBeginLayout();
// TODO(multi-display): This doesn't seem right...Maybe only apply to default display?
mSystemGestures.screenWidth = displayFrames.mUnrestricted.width();
mSystemGestures.screenHeight = displayFrames.mUnrestricted.height();
@@ -4826,8 +4889,7 @@
final int adjust = sim & SOFT_INPUT_MASK_ADJUST;
final boolean requestedFullscreen = (fl & FLAG_FULLSCREEN) != 0
- || (requestedSysUiFl & View.SYSTEM_UI_FLAG_FULLSCREEN) != 0
- || (requestedSysUiFl & View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN) != 0;
+ || (requestedSysUiFl & View.SYSTEM_UI_FLAG_FULLSCREEN) != 0;
final boolean layoutInScreen = (fl & FLAG_LAYOUT_IN_SCREEN) == FLAG_LAYOUT_IN_SCREEN;
final boolean layoutInsetDecor = (fl & FLAG_LAYOUT_INSET_DECOR) == FLAG_LAYOUT_INSET_DECOR;
@@ -5709,100 +5771,6 @@
setHdmiPlugged(!mHdmiPlugged);
}
- final Object mScreenshotLock = new Object();
- ServiceConnection mScreenshotConnection = null;
-
- final Runnable mScreenshotTimeout = new Runnable() {
- @Override public void run() {
- synchronized (mScreenshotLock) {
- if (mScreenshotConnection != null) {
- mContext.unbindService(mScreenshotConnection);
- mScreenshotConnection = null;
- notifyScreenshotError();
- }
- }
- }
- };
-
- // Assume this is called from the Handler thread.
- private void takeScreenshot(final int screenshotType) {
- synchronized (mScreenshotLock) {
- if (mScreenshotConnection != null) {
- return;
- }
- final ComponentName serviceComponent = new ComponentName(SYSUI_PACKAGE,
- SYSUI_SCREENSHOT_SERVICE);
- final Intent serviceIntent = new Intent();
- serviceIntent.setComponent(serviceComponent);
- ServiceConnection conn = new ServiceConnection() {
- @Override
- public void onServiceConnected(ComponentName name, IBinder service) {
- synchronized (mScreenshotLock) {
- if (mScreenshotConnection != this) {
- return;
- }
- Messenger messenger = new Messenger(service);
- Message msg = Message.obtain(null, screenshotType);
- final ServiceConnection myConn = this;
- Handler h = new Handler(mHandler.getLooper()) {
- @Override
- public void handleMessage(Message msg) {
- synchronized (mScreenshotLock) {
- if (mScreenshotConnection == myConn) {
- mContext.unbindService(mScreenshotConnection);
- mScreenshotConnection = null;
- mHandler.removeCallbacks(mScreenshotTimeout);
- }
- }
- }
- };
- msg.replyTo = new Messenger(h);
- msg.arg1 = msg.arg2 = 0;
- if (mStatusBar != null && mStatusBar.isVisibleLw())
- msg.arg1 = 1;
- if (mNavigationBar != null && mNavigationBar.isVisibleLw())
- msg.arg2 = 1;
- try {
- messenger.send(msg);
- } catch (RemoteException e) {
- }
- }
- }
-
- @Override
- public void onServiceDisconnected(ComponentName name) {
- synchronized (mScreenshotLock) {
- if (mScreenshotConnection != null) {
- mContext.unbindService(mScreenshotConnection);
- mScreenshotConnection = null;
- mHandler.removeCallbacks(mScreenshotTimeout);
- notifyScreenshotError();
- }
- }
- }
- };
- if (mContext.bindServiceAsUser(serviceIntent, conn,
- Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE,
- UserHandle.CURRENT)) {
- mScreenshotConnection = conn;
- mHandler.postDelayed(mScreenshotTimeout, 10000);
- }
- }
- }
-
- /**
- * Notifies the screenshot service to show an error.
- */
- private void notifyScreenshotError() {
- // If the service process is killed, then ask it to clean up after itself
- final ComponentName errorComponent = new ComponentName(SYSUI_PACKAGE,
- SYSUI_SCREENSHOT_ERROR_RECEIVER);
- Intent errorIntent = new Intent(Intent.ACTION_USER_PRESENT);
- errorIntent.setComponent(errorComponent);
- errorIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT |
- Intent.FLAG_RECEIVER_FOREGROUND);
- mContext.sendBroadcastAsUser(errorIntent, UserHandle.CURRENT);
- }
/** {@inheritDoc} */
@Override
@@ -6227,6 +6195,16 @@
}
/**
+ * Notify the StatusBar that system rotation suggestion has changed.
+ */
+ private void sendProposedRotationChangeToStatusBarInternal(int rotation) {
+ StatusBarManagerInternal statusBar = getStatusBarManagerInternal();
+ if (statusBar != null) {
+ statusBar.onProposedRotationChanged(rotation);
+ }
+ }
+
+ /**
* Returns true if the key can have global actions attached to it.
* We reserve all power management keys for the system since they require
* very careful handling.
@@ -6881,12 +6859,12 @@
}
@Override
- public void dismissKeyguardLw(IKeyguardDismissCallback callback) {
+ public void dismissKeyguardLw(IKeyguardDismissCallback callback, CharSequence message) {
if (mKeyguardDelegate != null && mKeyguardDelegate.isShowing()) {
if (DEBUG_KEYGUARD) Slog.d(TAG, "PWM.dismissKeyguardLw");
// ask the keyguard to prompt the user to authenticate if necessary
- mKeyguardDelegate.dismiss(callback);
+ mKeyguardDelegate.dismiss(callback, message);
} else if (callback != null) {
try {
callback.onDismissError();
@@ -7168,6 +7146,86 @@
mOrientationListener.setCurrentRotation(rotation);
}
+ public boolean showRotationChoice(int orientation, final int preferredRotation) {
+ // Rotation choice is only shown when the user is in locked mode.
+ if (mUserRotationMode != WindowManagerPolicy.USER_ROTATION_LOCKED) return false;
+
+ // We should only show a rotation choice if:
+ // 1. The rotation isn't forced by the lid, dock, demo, hdmi, vr, etc mode
+ // 2. The user choice won't be ignored due to screen orientation settings
+
+ // Determine if the rotation currently forced
+ if (mForceDefaultOrientation) {
+ return false; // Rotation is forced to default orientation
+
+ } else if (mLidState == LID_OPEN && mLidOpenRotation >= 0) {
+ return false; // Rotation is forced mLidOpenRotation
+
+ } else if (mDockMode == Intent.EXTRA_DOCK_STATE_CAR && !mCarDockEnablesAccelerometer) {
+ return false; // Rotation forced to mCarDockRotation
+
+ } else if ((mDockMode == Intent.EXTRA_DOCK_STATE_DESK
+ || mDockMode == Intent.EXTRA_DOCK_STATE_LE_DESK
+ || mDockMode == Intent.EXTRA_DOCK_STATE_HE_DESK)
+ && !mDeskDockEnablesAccelerometer) {
+ return false; // Rotation forced to mDeskDockRotation
+
+ } else if (mHdmiPlugged && mDemoHdmiRotationLock) {
+ return false; // Rotation forced to mDemoHdmiRotation
+
+ } else if (mHdmiPlugged && mDockMode == Intent.EXTRA_DOCK_STATE_UNDOCKED
+ && mUndockedHdmiRotation >= 0) {
+ return false; // Rotation forced to mUndockedHdmiRotation
+
+ } else if (mDemoRotationLock) {
+ return false; // Rotation forced to mDemoRotation
+
+ } else if (mPersistentVrModeEnabled) {
+ return false; // Rotation forced to mPortraitRotation
+
+ } else if (!mSupportAutoRotation) {
+ return false;
+ }
+
+ // Determine if the orientation will ignore user 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
+ return (preferredRotation >= 0) && (preferredRotation != mUpsideDownRotation);
+ }
+ }
+
private boolean isLandscapeOrSeascape(int rotation) {
return rotation == mLandscapeRotation || rotation == mSeascapeRotation;
}
@@ -7845,8 +7903,11 @@
// If the top fullscreen-or-dimming window is also the top fullscreen, respect
// its light flag.
vis &= ~View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR;
- vis |= PolicyControl.getSystemUiVisibility(statusColorWin, null)
- & View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR;
+ if (!statusColorWin.isLetterboxedForDisplayCutoutLw()) {
+ // Only allow white status bar if the window was not letterboxed.
+ vis |= PolicyControl.getSystemUiVisibility(statusColorWin, null)
+ & View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR;
+ }
} else if (statusColorWin != null && statusColorWin.isDimming()) {
// Otherwise if it's dimming, clear the light flag.
vis &= ~View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR;
@@ -8218,6 +8279,40 @@
@Override
public void writeToProto(ProtoOutputStream proto, long fieldId) {
final long token = proto.start(fieldId);
+ proto.write(LAST_SYSTEM_UI_FLAGS, mLastSystemUiFlags);
+ proto.write(ROTATION_MODE, mUserRotationMode);
+ proto.write(ROTATION, mUserRotation);
+ proto.write(ORIENTATION, mCurrentAppOrientation);
+ proto.write(SCREEN_ON_FULLY, mScreenOnFully);
+ proto.write(KEYGUARD_DRAW_COMPLETE, mKeyguardDrawComplete);
+ proto.write(WINDOW_MANAGER_DRAW_COMPLETE, mWindowManagerDrawComplete);
+ if (mFocusedApp != null) {
+ proto.write(FOCUSED_APP_TOKEN, mFocusedApp.toString());
+ }
+ if (mFocusedWindow != null) {
+ mFocusedWindow.writeIdentifierToProto(proto, FOCUSED_WINDOW);
+ }
+ if (mTopFullscreenOpaqueWindowState != null) {
+ mTopFullscreenOpaqueWindowState.writeIdentifierToProto(
+ proto, TOP_FULLSCREEN_OPAQUE_WINDOW);
+ }
+ if (mTopFullscreenOpaqueOrDimmingWindowState != null) {
+ mTopFullscreenOpaqueOrDimmingWindowState.writeIdentifierToProto(
+ proto, TOP_FULLSCREEN_OPAQUE_OR_DIMMING_WINDOW);
+ }
+ proto.write(KEYGUARD_OCCLUDED, mKeyguardOccluded);
+ proto.write(KEYGUARD_OCCLUDED_CHANGED, mKeyguardOccludedChanged);
+ proto.write(KEYGUARD_OCCLUDED_PENDING, mPendingKeyguardOccluded);
+ proto.write(FORCE_STATUS_BAR, mForceStatusBar);
+ proto.write(FORCE_STATUS_BAR_FROM_KEYGUARD, mForceStatusBarFromKeyguard);
+ mStatusBarController.writeToProto(proto, STATUS_BAR);
+ mNavigationBarController.writeToProto(proto, NAVIGATION_BAR);
+ if (mOrientationListener != null) {
+ mOrientationListener.writeToProto(proto, ORIENTATION_LISTENER);
+ }
+ if (mKeyguardDelegate != null) {
+ mKeyguardDelegate.writeToProto(proto, KEYGUARD_DELEGATE);
+ }
proto.end(token);
}
diff --git a/services/core/java/com/android/server/policy/WindowManagerPolicy.java b/services/core/java/com/android/server/policy/WindowManagerPolicy.java
index 40b656d..64a280c 100644
--- a/services/core/java/com/android/server/policy/WindowManagerPolicy.java
+++ b/services/core/java/com/android/server/policy/WindowManagerPolicy.java
@@ -176,6 +176,11 @@
void onKeyguardOccludedChangedLw(boolean occluded);
/**
+ * Called when the resource overlays change.
+ */
+ default void onOverlayChangedLw() {}
+
+ /**
* Interface to the Window Manager state associated with a particular
* window. You can hold on to an instance of this interface from the call
* to prepareAddWindow() until removeWindow().
@@ -446,6 +451,13 @@
*/
public boolean isDimming();
+ /**
+ * Returns true if the window is letterboxed for the display cutout.
+ */
+ default boolean isLetterboxedForDisplayCutoutLw() {
+ return false;
+ }
+
/** @return the current windowing mode of this window. */
int getWindowingMode();
@@ -474,6 +486,11 @@
* visible. That is, they have the permission {@link Manifest.permission#DEVICE_POWER}.
*/
boolean canAcquireSleepToken();
+
+ /**
+ * Writes {@link com.android.server.wm.proto.IdentifierProto} to stream.
+ */
+ void writeIdentifierToProto(ProtoOutputStream proto, long fieldId);
}
/**
@@ -1380,8 +1397,10 @@
* Ask the policy to dismiss the keyguard, if it is currently shown.
*
* @param callback Callback to be informed about the result.
+ * @param message A message that should be displayed in the keyguard.
*/
- public void dismissKeyguardLw(@Nullable IKeyguardDismissCallback callback);
+ public void dismissKeyguardLw(@Nullable IKeyguardDismissCallback callback,
+ CharSequence message);
/**
* Ask the policy whether the Keyguard has drawn. If the Keyguard is disabled, this method
diff --git a/services/core/java/com/android/server/policy/WindowOrientationListener.java b/services/core/java/com/android/server/policy/WindowOrientationListener.java
index 169fd27..1b5a521 100644
--- a/services/core/java/com/android/server/policy/WindowOrientationListener.java
+++ b/services/core/java/com/android/server/policy/WindowOrientationListener.java
@@ -16,6 +16,9 @@
package com.android.server.policy;
+import static com.android.server.wm.proto.WindowOrientationListenerProto.ENABLED;
+import static com.android.server.wm.proto.WindowOrientationListenerProto.ROTATION;
+
import android.content.Context;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
@@ -24,13 +27,11 @@
import android.os.Handler;
import android.os.SystemClock;
import android.os.SystemProperties;
-import android.text.TextUtils;
import android.util.Slog;
+import android.util.proto.ProtoOutputStream;
import android.view.Surface;
import java.io.PrintWriter;
-import java.util.Arrays;
-import java.util.List;
/**
* A special helper class used by the WindowManager
@@ -65,7 +66,7 @@
/**
* Creates a new WindowOrientationListener.
- *
+ *
* @param context for the WindowOrientationListener.
* @param handler Provides the Looper for receiving sensor updates.
*/
@@ -75,12 +76,12 @@
/**
* Creates a new WindowOrientationListener.
- *
+ *
* @param context for the WindowOrientationListener.
* @param handler Provides the Looper for receiving sensor updates.
* @param rate at which sensor events are processed (see also
* {@link android.hardware.SensorManager SensorManager}). Use the default
- * value of {@link android.hardware.SensorManager#SENSOR_DELAY_NORMAL
+ * value of {@link android.hardware.SensorManager#SENSOR_DELAY_NORMAL
* SENSOR_DELAY_NORMAL} for simple screen orientation change detection.
*
* This constructor is private since no one uses it.
@@ -232,6 +233,15 @@
*/
public abstract void onProposedRotationChanged(int rotation);
+ public void writeToProto(ProtoOutputStream proto, long fieldId) {
+ final long token = proto.start(fieldId);
+ synchronized (mLock) {
+ proto.write(ENABLED, mEnabled);
+ proto.write(ROTATION, mCurrentRotation);
+ }
+ proto.end(token);
+ }
+
public void dump(PrintWriter pw, String prefix) {
synchronized (mLock) {
pw.println(prefix + TAG);
diff --git a/services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java b/services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java
index 58002bc..18f4a3c 100644
--- a/services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java
+++ b/services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java
@@ -1,6 +1,11 @@
package com.android.server.policy.keyguard;
import static android.view.Display.INVALID_DISPLAY;
+import static com.android.server.wm.proto.KeyguardServiceDelegateProto.INTERACTIVE_STATE;
+import static com.android.server.wm.proto.KeyguardServiceDelegateProto.OCCLUDED;
+import static com.android.server.wm.proto.KeyguardServiceDelegateProto.SCREEN_STATE;
+import static com.android.server.wm.proto.KeyguardServiceDelegateProto.SECURE;
+import static com.android.server.wm.proto.KeyguardServiceDelegateProto.SHOWING;
import android.app.ActivityManager;
import android.content.ComponentName;
@@ -15,6 +20,7 @@
import android.os.UserHandle;
import android.util.Log;
import android.util.Slog;
+import android.util.proto.ProtoOutputStream;
import android.view.WindowManagerPolicyConstants;
import com.android.internal.policy.IKeyguardDismissCallback;
@@ -257,9 +263,9 @@
mKeyguardState.occluded = isOccluded;
}
- public void dismiss(IKeyguardDismissCallback callback) {
+ public void dismiss(IKeyguardDismissCallback callback, CharSequence message) {
if (mKeyguardService != null) {
- mKeyguardService.dismiss(callback);
+ mKeyguardService.dismiss(callback, message);
}
}
@@ -406,6 +412,16 @@
}
}
+ public void writeToProto(ProtoOutputStream proto, long fieldId) {
+ final long token = proto.start(fieldId);
+ proto.write(SHOWING, mKeyguardState.showing);
+ proto.write(OCCLUDED, mKeyguardState.occluded);
+ proto.write(SECURE, mKeyguardState.secure);
+ proto.write(SCREEN_STATE, mKeyguardState.screenState);
+ proto.write(INTERACTIVE_STATE, mKeyguardState.interactiveState);
+ proto.end(token);
+ }
+
public void dump(String prefix, PrintWriter pw) {
pw.println(prefix + TAG);
prefix += " ";
diff --git a/services/core/java/com/android/server/policy/keyguard/KeyguardServiceWrapper.java b/services/core/java/com/android/server/policy/keyguard/KeyguardServiceWrapper.java
index 952e0b0..4e84868 100644
--- a/services/core/java/com/android/server/policy/keyguard/KeyguardServiceWrapper.java
+++ b/services/core/java/com/android/server/policy/keyguard/KeyguardServiceWrapper.java
@@ -74,9 +74,9 @@
}
@Override // Binder interface
- public void dismiss(IKeyguardDismissCallback callback) {
+ public void dismiss(IKeyguardDismissCallback callback, CharSequence message) {
try {
- mService.dismiss(callback);
+ mService.dismiss(callback, message);
} catch (RemoteException e) {
Slog.w(TAG , "Remote Exception", e);
}
diff --git a/services/core/java/com/android/server/power/BatterySaverPolicy.java b/services/core/java/com/android/server/power/BatterySaverPolicy.java
index 6f005a3..a538967 100644
--- a/services/core/java/com/android/server/power/BatterySaverPolicy.java
+++ b/services/core/java/com/android/server/power/BatterySaverPolicy.java
@@ -20,6 +20,7 @@
import android.database.ContentObserver;
import android.net.Uri;
import android.os.Handler;
+import android.os.PowerManager;
import android.os.PowerManager.ServiceType;
import android.os.PowerSaveState;
import android.provider.Settings;
@@ -49,21 +50,6 @@
public static final boolean DEBUG = false; // DO NOT SUBMIT WITH TRUE.
- /** Value of batterySaverGpsMode such that GPS isn't affected by battery saver mode. */
- public static final int GPS_MODE_NO_CHANGE = 0;
-
- /**
- * Value of batterySaverGpsMode such that GPS is disabled when battery saver mode
- * is enabled and the screen is off.
- */
- public static final int GPS_MODE_DISABLED_WHEN_SCREEN_OFF = 1;
-
- /**
- * Value of batterySaverGpsMode such that location should be disabled altogether
- * when battery saver mode is enabled and the screen is off.
- */
- public static final int GPS_MODE_ALL_DISABLED_WHEN_SCREEN_OFF = 2;
-
// Secure setting for GPS behavior when battery saver mode is on.
public static final String SECURE_KEY_GPS_MODE = "batterySaverGpsMode";
@@ -354,7 +340,7 @@
// Get default value from Settings.Secure
final int defaultGpsMode = Settings.Secure.getInt(mContentResolver, SECURE_KEY_GPS_MODE,
- GPS_MODE_ALL_DISABLED_WHEN_SCREEN_OFF);
+ PowerManager.LOCATION_MODE_ALL_DISABLED_WHEN_SCREEN_OFF);
mGpsMode = parser.getInt(KEY_GPS_MODE, defaultGpsMode);
// Non-device-specific parameters.
diff --git a/services/core/java/com/android/server/power/batterysaver/BatterySaverLocationPlugin.java b/services/core/java/com/android/server/power/batterysaver/BatterySaverLocationPlugin.java
index 0af19b6..bd8baeb 100644
--- a/services/core/java/com/android/server/power/batterysaver/BatterySaverLocationPlugin.java
+++ b/services/core/java/com/android/server/power/batterysaver/BatterySaverLocationPlugin.java
@@ -16,11 +16,11 @@
package com.android.server.power.batterysaver;
import android.content.Context;
+import android.os.PowerManager;
import android.provider.Settings;
import android.provider.Settings.Global;
import android.util.Slog;
-import com.android.server.power.BatterySaverPolicy;
import com.android.server.power.batterysaver.BatterySaverController.Plugin;
public class BatterySaverLocationPlugin implements Plugin {
@@ -53,7 +53,7 @@
private void updateLocationState(BatterySaverController caller) {
final boolean kill =
(caller.getBatterySaverPolicy().getGpsMode()
- == BatterySaverPolicy.GPS_MODE_ALL_DISABLED_WHEN_SCREEN_OFF) &&
+ == PowerManager.LOCATION_MODE_ALL_DISABLED_WHEN_SCREEN_OFF) &&
caller.isEnabled() && !caller.isInteractive();
if (DEBUG) {
diff --git a/services/core/java/com/android/server/stats/StatsCompanionService.java b/services/core/java/com/android/server/stats/StatsCompanionService.java
index b31f4b3..2f5e2f8 100644
--- a/services/core/java/com/android/server/stats/StatsCompanionService.java
+++ b/services/core/java/com/android/server/stats/StatsCompanionService.java
@@ -555,7 +555,7 @@
case StatsLog.CPU_TIME_PER_FREQ: {
List<StatsLogEventWrapper> ret = new ArrayList();
for (int cluster = 0; cluster < mKernelCpuSpeedReaders.length; cluster++) {
- long[] clusterTimeMs = mKernelCpuSpeedReaders[cluster].readDelta();
+ long[] clusterTimeMs = mKernelCpuSpeedReaders[cluster].readAbsolute();
if (clusterTimeMs != null) {
for (int speed = clusterTimeMs.length - 1; speed >= 0; --speed) {
StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, 3);
@@ -743,9 +743,13 @@
filter.addAction(Intent.ACTION_SHUTDOWN);
mContext.registerReceiverAsUser(
mShutdownEventReceiver, UserHandle.ALL, filter, null, null);
-
- // Pull the latest state of UID->app name, version mapping when statsd starts.
- informAllUidsLocked(mContext);
+ final long token = Binder.clearCallingIdentity();
+ try {
+ // Pull the latest state of UID->app name, version mapping when statsd starts.
+ informAllUidsLocked(mContext);
+ } finally {
+ restoreCallingIdentity(token);
+ }
} catch (RemoteException e) {
Slog.e(TAG, "Failed to inform statsd that statscompanion is ready", e);
forgetEverything();
diff --git a/services/core/java/com/android/server/wm/AppWindowToken.java b/services/core/java/com/android/server/wm/AppWindowToken.java
index f2098dc..a254ba2 100644
--- a/services/core/java/com/android/server/wm/AppWindowToken.java
+++ b/services/core/java/com/android/server/wm/AppWindowToken.java
@@ -24,6 +24,7 @@
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET;
import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.SurfaceControl.HIDDEN;
import static android.view.WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD;
import static android.view.WindowManager.LayoutParams.FLAG_SECURE;
import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED;
@@ -245,6 +246,7 @@
/** Whether this token should be boosted at the top of all app window tokens. */
private boolean mNeedsZBoost;
+ private Letterbox mLetterbox;
private final Point mTmpPoint = new Point();
private final Rect mTmpRect = new Rect();
@@ -678,6 +680,7 @@
if (destroyedSomething) {
final DisplayContent dc = getDisplayContent();
dc.assignWindowLayers(true /*setLayoutNeeded*/);
+ updateLetterbox(null);
}
}
@@ -944,6 +947,7 @@
void removeChild(WindowState child) {
super.removeChild(child);
checkKeyguardFlagsChanged();
+ updateLetterbox(child);
}
private boolean waitingForReplacement() {
@@ -1409,6 +1413,33 @@
return isInterestingAndDrawn;
}
+ void updateLetterbox(WindowState winHint) {
+ final WindowState w = findMainWindow();
+ if (w != winHint && winHint != null && w != null) {
+ return;
+ }
+ final boolean needsLetterbox = w != null && w.isLetterboxedAppWindow()
+ && fillsParent() && w.hasDrawnLw();
+ if (needsLetterbox) {
+ if (mLetterbox == null) {
+ mLetterbox = new Letterbox(() -> makeChildSurface(null));
+ }
+ mLetterbox.setDimensions(mPendingTransaction, getParent().getBounds(), w.mFrame);
+ } else if (mLetterbox != null) {
+ final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
+ // Make sure we have a transaction here, in case we're called outside of a transaction.
+ // This does not use mPendingTransaction, because SurfaceAnimator uses a
+ // global transaction in onAnimationEnd.
+ SurfaceControl.openTransaction();
+ try {
+ mLetterbox.hide(t);
+ } finally {
+ SurfaceControl.mergeToGlobalTransaction(t);
+ SurfaceControl.closeTransaction();
+ }
+ }
+ }
+
@Override
boolean forAllWindows(ToBooleanFunction<WindowState> callback, boolean traverseTopToBottom) {
// For legacy reasons we process the TaskStack.mExitingAppTokens first in DisplayContent
@@ -1635,6 +1666,8 @@
// the status bar). In that case we need to use the final frame.
if (freeform) {
frame.set(win.mFrame);
+ } else if (win.isLetterboxedAppWindow()) {
+ frame.set(getTask().getBounds());
} else {
frame.set(win.mContainingFrame);
}
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index df468ac..a8e00dd 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -342,9 +342,6 @@
new TaskForResizePointSearchResult();
private final ApplySurfaceChangesTransactionState mTmpApplySurfaceChangesTransactionState =
new ApplySurfaceChangesTransactionState();
- private final ScreenshotApplicationState mScreenshotApplicationState =
- new ScreenshotApplicationState();
- private final Transaction mTmpTransaction = new Transaction();
// True if this display is in the process of being removed. Used to determine if the removal of
// the display's direct children should be allowed.
@@ -661,10 +658,7 @@
mWallpaperController.updateWallpaperVisibility();
}
- // Use mTmpTransaction instead of mPendingTransaction because we don't want to commit
- // other changes in mPendingTransaction at this point.
- w.handleWindowMovedIfNeeded(mTmpTransaction);
- SurfaceControl.mergeToGlobalTransaction(mTmpTransaction);
+ w.handleWindowMovedIfNeeded(mPendingTransaction);
final WindowStateAnimator winAnimator = w.mWinAnimator;
@@ -699,37 +693,11 @@
}
}
}
- final TaskStack stack = w.getStack();
- if (!winAnimator.isWaitingForOpening()
- || (stack != null && stack.isAnimatingBounds())) {
- // Updates the shown frame before we set up the surface. This is needed
- // because the resizing could change the top-left position (in addition to
- // size) of the window. setSurfaceBoundariesLocked uses mShownPosition to
- // position the surface.
- //
- // If an animation is being started, we can't call this method because the
- // animation hasn't processed its initial transformation yet, but in general
- // we do want to update the position if the window is animating. We make an exception
- // for the bounds animating state, where an application may have been waiting
- // for an exit animation to start, but instead enters PiP. We need to ensure
- // we always recompute the top-left in this case.
- winAnimator.computeShownFrameLocked();
- }
- winAnimator.setSurfaceBoundariesLocked(mTmpRecoveringMemory /* recoveringMemory */);
-
- // Since setSurfaceBoundariesLocked applies the clipping, we need to apply the position
- // to the surface of the window container and also the position of the stack window
- // container as well. Use mTmpTransaction instead of mPendingTransaction to avoid
- // committing any existing changes in there.
- w.updateSurfacePosition(mTmpTransaction);
- if (stack != null) {
- stack.updateSurfaceBounds(mTmpTransaction);
- }
- SurfaceControl.mergeToGlobalTransaction(mTmpTransaction);
}
final AppWindowToken atoken = w.mAppToken;
if (atoken != null) {
+ atoken.updateLetterbox(w);
final boolean updateAllDrawn = atoken.updateDrawnWindowStates(w);
if (updateAllDrawn && !mTmpUpdateAllDrawn.contains(atoken)) {
mTmpUpdateAllDrawn.add(atoken);
@@ -2845,6 +2813,7 @@
mTmpRecoveringMemory = recoveringMemory;
forAllWindows(mApplySurfaceChangesTransaction, true /* traverseTopToBottom */);
+ prepareSurfaces();
mService.mDisplayManagerInternal.setDisplayProperties(mDisplayId,
mTmpApplySurfaceChangesTransactionState.displayHasContent,
diff --git a/services/core/java/com/android/server/wm/DisplayFrames.java b/services/core/java/com/android/server/wm/DisplayFrames.java
index bd06192..13d0c86 100644
--- a/services/core/java/com/android/server/wm/DisplayFrames.java
+++ b/services/core/java/com/android/server/wm/DisplayFrames.java
@@ -101,7 +101,7 @@
/** During layout, the current screen borders along which input method windows are placed. */
public final Rect mDock = new Rect();
- /** The display cutout used for layout (after rotation and emulation) */
+ /** The display cutout used for layout (after rotation) */
@NonNull public DisplayCutout mDisplayCutout = DisplayCutout.NO_CUTOUT;
/** The cutout as supplied by display info */
@@ -134,7 +134,7 @@
? info.displayCutout : DisplayCutout.NO_CUTOUT;
}
- public void onBeginLayout(boolean emulateDisplayCutout, int statusBarHeight) {
+ public void onBeginLayout() {
switch (mRotation) {
case ROTATION_90:
mRotatedDisplayInfoOverscan.left = mDisplayInfoOverscan.top;
@@ -172,12 +172,8 @@
mStable.set(mUnrestricted);
mStableFullscreen.set(mUnrestricted);
mCurrent.set(mUnrestricted);
- mDisplayCutout = mDisplayInfoCutout;
- if (emulateDisplayCutout) {
- setEmulatedDisplayCutout((int) (statusBarHeight * 0.8));
- }
- mDisplayCutout = mDisplayCutout.calculateRelativeTo(mOverscan);
+ mDisplayCutout = mDisplayInfoCutout.calculateRelativeTo(mOverscan);
mDisplayCutoutSafe.set(Integer.MIN_VALUE, Integer.MIN_VALUE,
Integer.MAX_VALUE, Integer.MAX_VALUE);
if (!mDisplayCutout.isEmpty()) {
@@ -201,51 +197,6 @@
return mDock.bottom - mCurrent.bottom;
}
- private void setEmulatedDisplayCutout(int height) {
- final boolean swappedDimensions = mRotation == ROTATION_90 || mRotation == ROTATION_270;
-
- final int screenWidth = swappedDimensions ? mDisplayHeight : mDisplayWidth;
- final int screenHeight = swappedDimensions ? mDisplayWidth : mDisplayHeight;
-
- final int widthTop = (int) (screenWidth * 0.3);
- final int widthBottom = widthTop - height;
-
- switch (mRotation) {
- case ROTATION_90:
- mDisplayCutout = DisplayCutout.fromBoundingPolygon(Arrays.asList(
- new Point(0, (screenWidth - widthTop) / 2),
- new Point(height, (screenWidth - widthBottom) / 2),
- new Point(height, (screenWidth + widthBottom) / 2),
- new Point(0, (screenWidth + widthTop) / 2)
- ));
- break;
- case ROTATION_180:
- mDisplayCutout = DisplayCutout.fromBoundingPolygon(Arrays.asList(
- new Point((screenWidth - widthTop) / 2, screenHeight),
- new Point((screenWidth - widthBottom) / 2, screenHeight - height),
- new Point((screenWidth + widthBottom) / 2, screenHeight - height),
- new Point((screenWidth + widthTop) / 2, screenHeight)
- ));
- break;
- case ROTATION_270:
- mDisplayCutout = DisplayCutout.fromBoundingPolygon(Arrays.asList(
- new Point(screenHeight, (screenWidth - widthTop) / 2),
- new Point(screenHeight - height, (screenWidth - widthBottom) / 2),
- new Point(screenHeight - height, (screenWidth + widthBottom) / 2),
- new Point(screenHeight, (screenWidth + widthTop) / 2)
- ));
- break;
- default:
- mDisplayCutout = DisplayCutout.fromBoundingPolygon(Arrays.asList(
- new Point((screenWidth - widthTop) / 2, 0),
- new Point((screenWidth - widthBottom) / 2, height),
- new Point((screenWidth + widthBottom) / 2, height),
- new Point((screenWidth + widthTop) / 2, 0)
- ));
- break;
- }
- }
-
public void writeToProto(ProtoOutputStream proto, long fieldId) {
final long token = proto.start(fieldId);
mStable.writeToProto(proto, STABLE_BOUNDS);
diff --git a/services/core/java/com/android/server/wm/Letterbox.java b/services/core/java/com/android/server/wm/Letterbox.java
new file mode 100644
index 0000000..8fa79ab
--- /dev/null
+++ b/services/core/java/com/android/server/wm/Letterbox.java
@@ -0,0 +1,145 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import static android.view.SurfaceControl.HIDDEN;
+
+import android.graphics.Rect;
+import android.view.SurfaceControl;
+
+import java.util.function.Supplier;
+
+/**
+ * Manages a set of {@link SurfaceControl}s to draw a black letterbox between an
+ * outer rect and an inner rect.
+ */
+public class Letterbox {
+
+ private static final Rect EMPTY_RECT = new Rect();
+
+ private final Supplier<SurfaceControl.Builder> mFactory;
+ private final Rect mOuter = new Rect();
+ private final Rect mInner = new Rect();
+ private final LetterboxSurface mTop = new LetterboxSurface("top");
+ private final LetterboxSurface mLeft = new LetterboxSurface("left");
+ private final LetterboxSurface mBottom = new LetterboxSurface("bottom");
+ private final LetterboxSurface mRight = new LetterboxSurface("right");
+
+ /**
+ * Constructs a Letterbox.
+ *
+ * @param surfaceControlFactory a factory for creating the managed {@link SurfaceControl}s
+ */
+ public Letterbox(Supplier<SurfaceControl.Builder> surfaceControlFactory) {
+ mFactory = surfaceControlFactory;
+ }
+
+ /**
+ * Sets the dimensions of the the letterbox, such that the area between the outer and inner
+ * frames will be covered by black color surfaces.
+ *
+ * @param t a transaction in which to set the dimensions
+ * @param outer the outer frame of the letterbox (this frame will be black, except the area
+ * that intersects with the {code inner} frame).
+ * @param inner the inner frame of the letterbox (this frame will be clear)
+ */
+ public void setDimensions(SurfaceControl.Transaction t, Rect outer, Rect inner) {
+ mOuter.set(outer);
+ mInner.set(inner);
+
+ mTop.setRect(t, outer.left, outer.top, inner.right, inner.top);
+ mLeft.setRect(t, outer.left, inner.top, inner.left, outer.bottom);
+ mBottom.setRect(t, inner.left, inner.bottom, outer.right, outer.bottom);
+ mRight.setRect(t, inner.right, outer.top, outer.right, inner.bottom);
+ }
+
+ /**
+ * Hides the letterbox.
+ *
+ * @param t a transaction in which to hide the letterbox
+ */
+ public void hide(SurfaceControl.Transaction t) {
+ setDimensions(t, EMPTY_RECT, EMPTY_RECT);
+ }
+
+ /**
+ * Destroys the managed {@link SurfaceControl}s.
+ */
+ public void destroy() {
+ mOuter.setEmpty();
+ mInner.setEmpty();
+
+ mTop.destroy();
+ mLeft.destroy();
+ mBottom.destroy();
+ mRight.destroy();
+ }
+
+ private class LetterboxSurface {
+
+ private final String mType;
+ private SurfaceControl mSurface;
+
+ private int mLastLeft = 0;
+ private int mLastTop = 0;
+ private int mLastRight = 0;
+ private int mLastBottom = 0;
+
+ public LetterboxSurface(String type) {
+ mType = type;
+ }
+
+ public void setRect(SurfaceControl.Transaction t,
+ int left, int top, int right, int bottom) {
+ if (mLastLeft == left && mLastTop == top
+ && mLastRight == right && mLastBottom == bottom) {
+ // Nothing changed.
+ return;
+ }
+
+ if (left < right && top < bottom) {
+ if (mSurface == null) {
+ createSurface();
+ }
+ t.setPosition(mSurface, left, top);
+ t.setSize(mSurface, right - left, bottom - top);
+ t.show(mSurface);
+ } else if (mSurface != null) {
+ t.hide(mSurface);
+ }
+
+ mLastLeft = left;
+ mLastTop = top;
+ mLastRight = right;
+ mLastBottom = bottom;
+ }
+
+ private void createSurface() {
+ mSurface = mFactory.get().setName("Letterbox - " + mType)
+ .setFlags(HIDDEN).setColorLayer(true).build();
+ mSurface.setLayer(-1);
+ mSurface.setColor(new float[]{0, 0, 0});
+ }
+
+ public void destroy() {
+ if (mSurface != null) {
+ mSurface.destroy();
+ mSurface = null;
+ }
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/wm/RemoteSurfaceTrace.java b/services/core/java/com/android/server/wm/RemoteSurfaceTrace.java
index d2cbf88..33e560f 100644
--- a/services/core/java/com/android/server/wm/RemoteSurfaceTrace.java
+++ b/services/core/java/com/android/server/wm/RemoteSurfaceTrace.java
@@ -33,7 +33,7 @@
// the surface control.
//
// See cts/hostsidetests/../../SurfaceTraceReceiver.java for parsing side.
-class RemoteSurfaceTrace extends SurfaceControlWithBackground {
+class RemoteSurfaceTrace extends SurfaceControl {
static final String TAG = "RemoteSurfaceTrace";
final FileDescriptor mWriteFd;
@@ -42,7 +42,7 @@
final WindowManagerService mService;
final WindowState mWindow;
- RemoteSurfaceTrace(FileDescriptor fd, SurfaceControlWithBackground wrapped,
+ RemoteSurfaceTrace(FileDescriptor fd, SurfaceControl wrapped,
WindowState window) {
super(wrapped);
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 2a77c92..2cc96c9 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -86,7 +86,6 @@
import static com.android.server.wm.WindowManagerService.logSurface;
import static com.android.server.wm.WindowSurfacePlacer.SET_FORCE_HIDING_CHANGED;
import static com.android.server.wm.WindowSurfacePlacer.SET_ORIENTATION_CHANGE_COMPLETE;
-import static com.android.server.wm.WindowSurfacePlacer.SET_TURN_ON_SCREEN;
import static com.android.server.wm.WindowSurfacePlacer.SET_UPDATE_ROTATION;
import static com.android.server.wm.WindowSurfacePlacer.SET_WALLPAPER_ACTION_PENDING;
import static com.android.server.wm.WindowSurfacePlacer.SET_WALLPAPER_MAY_CHANGE;
@@ -968,9 +967,7 @@
doRequest = true;
}
}
- if ((bulkUpdateParams & SET_TURN_ON_SCREEN) != 0) {
- mService.mTurnOnScreen = true;
- }
+
if ((bulkUpdateParams & SET_WALLPAPER_ACTION_PENDING) != 0) {
mWallpaperActionPending = true;
}
diff --git a/services/core/java/com/android/server/wm/SurfaceControlWithBackground.java b/services/core/java/com/android/server/wm/SurfaceControlWithBackground.java
deleted file mode 100644
index 7c5bd43..0000000
--- a/services/core/java/com/android/server/wm/SurfaceControlWithBackground.java
+++ /dev/null
@@ -1,334 +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.server.wm;
-
-import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
-import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
-
-import static com.android.server.policy.WindowManagerPolicy.NAV_BAR_BOTTOM;
-import static com.android.server.policy.WindowManagerPolicy.NAV_BAR_LEFT;
-import static com.android.server.policy.WindowManagerPolicy.NAV_BAR_RIGHT;
-
-import android.graphics.Rect;
-import android.graphics.Region;
-import android.os.IBinder;
-import android.os.Parcel;
-import android.view.Surface;
-import android.view.Surface.OutOfResourcesException;
-import android.view.SurfaceControl;
-
-/**
- * SurfaceControl extension that has black background behind navigation bar area for fullscreen
- * letterboxed apps.
- */
-class SurfaceControlWithBackground extends SurfaceControl {
- // SurfaceControl that holds the background.
- private SurfaceControl mBackgroundControl;
-
- // Flag that defines whether the background should be shown.
- private boolean mVisible;
-
- // Way to communicate with corresponding window.
- private WindowSurfaceController mWindowSurfaceController;
-
- // Rect to hold task bounds when computing metrics for background.
- private Rect mTmpContainerRect = new Rect();
-
- // Last metrics applied to the main SurfaceControl.
- private float mLastWidth, mLastHeight;
- private float mLastDsDx = 1, mLastDsDy = 1;
- private float mLastX, mLastY;
-
- // SurfaceFlinger doesn't support crop rectangles where width or height is non-positive.
- // If we just set an empty crop it will behave as if there is no crop at all.
- // To fix this we explicitly hide the surface and won't let it to be shown.
- private boolean mHiddenForCrop = false;
-
- public SurfaceControlWithBackground(SurfaceControlWithBackground other) {
- super(other);
- mBackgroundControl = other.mBackgroundControl;
- mVisible = other.mVisible;
- mWindowSurfaceController = other.mWindowSurfaceController;
- }
-
- public SurfaceControlWithBackground(String name, SurfaceControl.Builder b,
- int windowType, int w, int h,
- WindowSurfaceController windowSurfaceController) throws OutOfResourcesException {
- super(b.build());
-
- // We should only show background behind app windows that are letterboxed in a task.
- if ((windowType != TYPE_BASE_APPLICATION && windowType != TYPE_APPLICATION_STARTING)
- || !windowSurfaceController.mAnimator.mWin.isLetterboxedAppWindow()) {
- return;
- }
- mWindowSurfaceController = windowSurfaceController;
- mLastWidth = w;
- mLastHeight = h;
- mWindowSurfaceController.getContainerRect(mTmpContainerRect);
- mBackgroundControl = b.setName("Background for - " + name)
- .setSize(mTmpContainerRect.width(), mTmpContainerRect.height())
- .setFormat(OPAQUE)
- .setColorLayer(true)
- .build();
- }
-
- @Override
- public void setAlpha(float alpha) {
- super.setAlpha(alpha);
-
- if (mBackgroundControl == null) {
- return;
- }
- mBackgroundControl.setAlpha(alpha);
- }
-
- @Override
- public void setLayer(int zorder) {
- super.setLayer(zorder);
-
- if (mBackgroundControl == null) {
- return;
- }
- // TODO: Use setRelativeLayer(Integer.MIN_VALUE) when it's fixed.
- mBackgroundControl.setLayer(zorder - 1);
- }
-
- @Override
- public void setPosition(float x, float y) {
- super.setPosition(x, y);
-
- if (mBackgroundControl == null) {
- return;
- }
- mLastX = x;
- mLastY = y;
- updateBgPosition();
- }
-
- private void updateBgPosition() {
- mWindowSurfaceController.getContainerRect(mTmpContainerRect);
- final Rect winFrame = mWindowSurfaceController.mAnimator.mWin.mFrame;
- final float offsetX = (mTmpContainerRect.left - winFrame.left) * mLastDsDx;
- final float offsetY = (mTmpContainerRect.top - winFrame.top) * mLastDsDy;
- mBackgroundControl.setPosition(mLastX + offsetX, mLastY + offsetY);
- }
-
- @Override
- public void setSize(int w, int h) {
- super.setSize(w, h);
-
- if (mBackgroundControl == null) {
- return;
- }
- mLastWidth = w;
- mLastHeight = h;
- mWindowSurfaceController.getContainerRect(mTmpContainerRect);
- mBackgroundControl.setSize(mTmpContainerRect.width(), mTmpContainerRect.height());
- }
-
- @Override
- public void setWindowCrop(Rect crop) {
- super.setWindowCrop(crop);
-
- if (mBackgroundControl == null) {
- return;
- }
- calculateBgCrop(crop);
- mBackgroundControl.setWindowCrop(mTmpContainerRect);
- mHiddenForCrop = mTmpContainerRect.isEmpty();
- updateBackgroundVisibility();
- }
-
- @Override
- public void setFinalCrop(Rect crop) {
- super.setFinalCrop(crop);
-
- if (mBackgroundControl == null) {
- return;
- }
- mWindowSurfaceController.getContainerRect(mTmpContainerRect);
- mBackgroundControl.setFinalCrop(mTmpContainerRect);
- }
-
- /**
- * Compute background crop based on current animation progress for main surface control and
- * update {@link #mTmpContainerRect} with new values.
- */
- private void calculateBgCrop(Rect crop) {
- // Track overall progress of animation by computing cropped portion of status bar.
- final Rect contentInsets = mWindowSurfaceController.mAnimator.mWin.mContentInsets;
- float d = contentInsets.top == 0 ? 0 : (float) crop.top / contentInsets.top;
- if (d > 1.f) {
- // We're running expand animation from launcher, won't compute custom bg crop here.
- mTmpContainerRect.setEmpty();
- return;
- }
-
- // Compute new scaled width and height for background that will depend on current animation
- // progress. Those consist of current crop rect for the main surface + scaled areas outside
- // of letterboxed area.
- // TODO: Because the progress is computed with low precision we're getting smaller values
- // for background width/height then screen size at the end of the animation. Will round when
- // the value is smaller then some empiric epsilon. However, this should be fixed by
- // computing correct frames for letterboxed windows in WindowState.
- d = d < 0.025f ? 0 : d;
- mWindowSurfaceController.getContainerRect(mTmpContainerRect);
- int backgroundWidth = 0, backgroundHeight = 0;
- // Compute additional offset for the background when app window is positioned not at (0,0).
- // E.g. landscape with navigation bar on the left.
- final Rect winFrame = mWindowSurfaceController.mAnimator.mWin.mFrame;
- int offsetX = (int)((winFrame.left - mTmpContainerRect.left) * mLastDsDx),
- offsetY = (int) ((winFrame.top - mTmpContainerRect.top) * mLastDsDy);
-
- // Position and size background.
- final int bgPosition = mWindowSurfaceController.mAnimator.mService.getNavBarPosition();
-
- switch (bgPosition) {
- case NAV_BAR_LEFT:
- backgroundWidth = (int) ((mTmpContainerRect.width() - mLastWidth) * (1 - d) + 0.5);
- backgroundHeight = crop.height();
- offsetX += crop.left - backgroundWidth;
- offsetY += crop.top;
- break;
- case NAV_BAR_RIGHT:
- backgroundWidth = (int) ((mTmpContainerRect.width() - mLastWidth) * (1 - d) + 0.5);
- backgroundHeight = crop.height();
- offsetX += crop.right;
- offsetY += crop.top;
- break;
- case NAV_BAR_BOTTOM:
- backgroundWidth = crop.width();
- backgroundHeight = (int) ((mTmpContainerRect.height() - mLastHeight) * (1 - d)
- + 0.5);
- offsetX += crop.left;
- offsetY += crop.bottom;
- break;
- }
- mTmpContainerRect.set(offsetX, offsetY, offsetX + backgroundWidth,
- offsetY + backgroundHeight);
- }
-
- @Override
- public void setLayerStack(int layerStack) {
- super.setLayerStack(layerStack);
-
- if (mBackgroundControl == null) {
- return;
- }
- mBackgroundControl.setLayerStack(layerStack);
- }
-
- @Override
- public void setOpaque(boolean isOpaque) {
- super.setOpaque(isOpaque);
- updateBackgroundVisibility();
- }
-
- @Override
- public void setSecure(boolean isSecure) {
- super.setSecure(isSecure);
- }
-
- @Override
- public void setMatrix(float dsdx, float dtdx, float dtdy, float dsdy) {
- super.setMatrix(dsdx, dtdx, dtdy, dsdy);
-
- if (mBackgroundControl == null) {
- return;
- }
- mBackgroundControl.setMatrix(dsdx, dtdx, dtdy, dsdy);
- mLastDsDx = dsdx;
- mLastDsDy = dsdy;
- updateBgPosition();
- }
-
- @Override
- public void hide() {
- super.hide();
- mVisible = false;
- updateBackgroundVisibility();
- }
-
- @Override
- public void show() {
- super.show();
- mVisible = true;
- updateBackgroundVisibility();
- }
-
- @Override
- public void destroy() {
- super.destroy();
-
- if (mBackgroundControl == null) {
- return;
- }
- mBackgroundControl.destroy();
- }
-
- @Override
- public void release() {
- super.release();
-
- if (mBackgroundControl == null) {
- return;
- }
- mBackgroundControl.release();
- }
-
- @Override
- public void setTransparentRegionHint(Region region) {
- super.setTransparentRegionHint(region);
-
- if (mBackgroundControl == null) {
- return;
- }
- mBackgroundControl.setTransparentRegionHint(region);
- }
-
- @Override
- public void deferTransactionUntil(IBinder handle, long frame) {
- super.deferTransactionUntil(handle, frame);
-
- if (mBackgroundControl == null) {
- return;
- }
- mBackgroundControl.deferTransactionUntil(handle, frame);
- }
-
- @Override
- public void deferTransactionUntil(Surface barrier, long frame) {
- super.deferTransactionUntil(barrier, frame);
-
- if (mBackgroundControl == null) {
- return;
- }
- mBackgroundControl.deferTransactionUntil(barrier, frame);
- }
-
- private void updateBackgroundVisibility() {
- if (mBackgroundControl == null) {
- return;
- }
- final AppWindowToken appWindowToken = mWindowSurfaceController.mAnimator.mWin.mAppToken;
- if (!mHiddenForCrop && mVisible && appWindowToken != null && appWindowToken.fillsParent()) {
- mBackgroundControl.show();
- } else {
- mBackgroundControl.hide();
- }
- }
-}
diff --git a/services/core/java/com/android/server/wm/TaskStack.java b/services/core/java/com/android/server/wm/TaskStack.java
index 7024fb1..8a36226 100644
--- a/services/core/java/com/android/server/wm/TaskStack.java
+++ b/services/core/java/com/android/server/wm/TaskStack.java
@@ -49,6 +49,7 @@
import android.annotation.CallSuper;
import android.content.res.Configuration;
+import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.Region;
import android.os.RemoteException;
@@ -150,6 +151,7 @@
* For {@link #prepareSurfaces}.
*/
final Rect mTmpDimBoundsRect = new Rect();
+ private final Point mLastSurfaceSize = new Point();
TaskStack(WindowManagerService service, int stackId, StackWindowController controller) {
super(service);
@@ -749,7 +751,13 @@
}
final Rect stackBounds = getBounds();
- transaction.setSize(mSurfaceControl, stackBounds.width(), stackBounds.height());
+ final int width = stackBounds.width();
+ final int height = stackBounds.height();
+ if (width == mLastSurfaceSize.x && height == mLastSurfaceSize.y) {
+ return;
+ }
+ transaction.setSize(mSurfaceControl, width, height);
+ mLastSurfaceSize.set(width, height);
}
@Override
diff --git a/services/core/java/com/android/server/wm/WindowAnimator.java b/services/core/java/com/android/server/wm/WindowAnimator.java
index 3efd6ac..cec13ab 100644
--- a/services/core/java/com/android/server/wm/WindowAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowAnimator.java
@@ -292,9 +292,6 @@
if ((bulkUpdateParams & WindowSurfacePlacer.SET_ORIENTATION_CHANGE_COMPLETE) != 0) {
builder.append(" ORIENTATION_CHANGE_COMPLETE");
}
- if ((bulkUpdateParams & WindowSurfacePlacer.SET_TURN_ON_SCREEN) != 0) {
- builder.append(" TURN_ON_SCREEN");
- }
return builder.toString();
}
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index c267c70..42c6ec2 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -95,6 +95,7 @@
protected final WindowManagerService mService;
private final Point mTmpPos = new Point();
+ protected final Point mLastSurfacePosition = new Point();
/** Total number of elements in this subtree, including our own hierarchy element. */
private int mTreeWeight = 1;
@@ -1179,7 +1180,12 @@
}
getRelativePosition(mTmpPos);
+ if (mTmpPos.equals(mLastSurfacePosition)) {
+ return;
+ }
+
transaction.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);
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index fcc9988..7b33533 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -2941,10 +2941,10 @@
}
@Override
- public void dismissKeyguard(IKeyguardDismissCallback callback) {
+ public void dismissKeyguard(IKeyguardDismissCallback callback, CharSequence message) {
checkCallingPermission(permission.CONTROL_KEYGUARD, "dismissKeyguard");
synchronized(mWindowMap) {
- mPolicy.dismissKeyguardLw(callback);
+ mPolicy.dismissKeyguardLw(callback, message);
}
}
@@ -6596,6 +6596,13 @@
}
}
+ public void onOverlayChanged() {
+ synchronized (mWindowMap) {
+ mPolicy.onOverlayChangedLw();
+ requestTraversal();
+ }
+ }
+
public void onDisplayChanged(int displayId) {
synchronized (mWindowMap) {
final DisplayContent displayContent = mRoot.getDisplayContentOrCreate(displayId);
diff --git a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
index b9dc9db..e24c393 100644
--- a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
+++ b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
@@ -231,7 +231,7 @@
}
private int runDismissKeyguard(PrintWriter pw) throws RemoteException {
- mInterface.dismissKeyguard(null /* callback */);
+ mInterface.dismissKeyguard(null /* callback */, null /* message */);
return 0;
}
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index b3809dd..d5a1680 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -20,16 +20,20 @@
import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.SurfaceControl.Transaction;
+import static android.view.View.SYSTEM_UI_FLAG_FULLSCREEN;
+import static android.view.View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN;
import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_CONTENT;
import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_FRAME;
import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION;
import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_VISIBLE;
import static android.view.WindowManager.LayoutParams.FIRST_SUB_WINDOW;
import static android.view.WindowManager.LayoutParams.FIRST_SYSTEM_WINDOW;
+import static android.view.WindowManager.LayoutParams.FLAG2_LAYOUT_IN_DISPLAY_CUTOUT_AREA;
import static android.view.WindowManager.LayoutParams.FLAG_ALLOW_LOCK_WHILE_SCREEN_ON;
import static android.view.WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
import static android.view.WindowManager.LayoutParams.FLAG_DIM_BEHIND;
import static android.view.WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD;
+import static android.view.WindowManager.LayoutParams.FLAG_FULLSCREEN;
import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS;
import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
@@ -198,7 +202,6 @@
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Comparator;
-import java.util.LinkedList;
import java.util.function.Predicate;
/** A window in the window manager. */
@@ -2977,7 +2980,29 @@
/** @return true when the window is in fullscreen task, but has non-fullscreen bounds set. */
boolean isLetterboxedAppWindow() {
- return !isInMultiWindowMode() && mAppToken != null && !mAppToken.matchParentBounds();
+ return !isInMultiWindowMode() && mAppToken != null && !mAppToken.matchParentBounds()
+ || isLetterboxedForDisplayCutoutLw();
+ }
+
+ @Override
+ public boolean isLetterboxedForDisplayCutoutLw() {
+ if (mAppToken == null) {
+ // Only windows with an AppWindowToken are letterboxed.
+ return false;
+ }
+ if (getDisplayContent().getDisplayInfo().displayCutout == null) {
+ // No cutout, no letterbox.
+ return false;
+ }
+ if ((mAttrs.flags2 & FLAG2_LAYOUT_IN_DISPLAY_CUTOUT_AREA) != 0) {
+ // Layout in cutout, no letterbox.
+ return false;
+ }
+ // TODO: handle dialogs and other non-filling windows
+ // Letterbox if any fullscreen mode is set.
+ final int fl = mAttrs.flags;
+ final int sysui = mSystemUiVisibility;
+ return (fl & FLAG_FULLSCREEN) != 0 || (sysui & (SYSTEM_UI_FLAG_FULLSCREEN)) != 0;
}
boolean isDragResizeChanged() {
@@ -3136,7 +3161,8 @@
proto.end(token);
}
- void writeIdentifierToProto(ProtoOutputStream proto, long fieldId) {
+ @Override
+ public void writeIdentifierToProto(ProtoOutputStream proto, long fieldId) {
final long token = proto.start(fieldId);
proto.write(HASH_CODE, System.identityHashCode(this));
proto.write(USER_ID, UserHandle.getUserId(mOwnerUid));
@@ -4487,6 +4513,7 @@
// Leash is now responsible for position, so set our position to 0.
t.setPosition(mSurfaceControl, 0, 0);
+ mLastSurfacePosition.set(0, 0);
}
@Override
@@ -4502,8 +4529,9 @@
}
transformFrameToSurfacePosition(mFrame.left, mFrame.top, mSurfacePosition);
- if (!mSurfaceAnimator.hasLeash()) {
+ if (!mSurfaceAnimator.hasLeash() && !mLastSurfacePosition.equals(mSurfacePosition)) {
t.setPosition(mSurfaceControl, mSurfacePosition.x, mSurfacePosition.y);
+ mLastSurfacePosition.set(mSurfacePosition.x, mSurfacePosition.y);
}
}
diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index 96b0bd5..ba5156b 100644
--- a/services/core/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java
@@ -41,7 +41,6 @@
import static com.android.server.wm.WindowManagerService.TYPE_LAYER_MULTIPLIER;
import static com.android.server.wm.WindowManagerService.logWithStack;
import static com.android.server.wm.WindowSurfacePlacer.SET_ORIENTATION_CHANGE_COMPLETE;
-import static com.android.server.wm.WindowSurfacePlacer.SET_TURN_ON_SCREEN;
import static com.android.server.wm.proto.WindowStateAnimatorProto.DRAW_STATE;
import static com.android.server.wm.proto.WindowStateAnimatorProto.LAST_CLIP_RECT;
import static com.android.server.wm.proto.WindowStateAnimatorProto.SURFACE;
@@ -1165,7 +1164,7 @@
// potentially occurring while turning off the screen. This would lead to the
// screen incorrectly turning back on.
if (!mService.mPowerManager.isInteractive()) {
- mAnimator.mBulkUpdateParams |= SET_TURN_ON_SCREEN;
+ mService.mTurnOnScreen = true;
}
}
}
diff --git a/services/core/java/com/android/server/wm/WindowSurfaceController.java b/services/core/java/com/android/server/wm/WindowSurfaceController.java
index e26a362..2f38556 100644
--- a/services/core/java/com/android/server/wm/WindowSurfaceController.java
+++ b/services/core/java/com/android/server/wm/WindowSurfaceController.java
@@ -53,7 +53,7 @@
final WindowStateAnimator mAnimator;
- SurfaceControlWithBackground mSurfaceControl;
+ SurfaceControl mSurfaceControl;
// Should only be set from within setShown().
private boolean mSurfaceShown = false;
@@ -108,13 +108,11 @@
.setFormat(format)
.setFlags(flags)
.setMetadata(windowType, ownerUid);
- mSurfaceControl = new SurfaceControlWithBackground(
- name, b, windowType, w, h, this);
+ mSurfaceControl = b.build();
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
if (mService.mRoot.mSurfaceTraceEnabled) {
- mSurfaceControl = new RemoteSurfaceTrace(
- mService.mRoot.mSurfaceTraceFd.getFileDescriptor(), mSurfaceControl, win);
+ installRemoteTrace(mService.mRoot.mSurfaceTraceFd.getFileDescriptor());
}
}
@@ -123,7 +121,7 @@
}
void removeRemoteTrace() {
- mSurfaceControl = new SurfaceControlWithBackground(mSurfaceControl);
+ mSurfaceControl = new SurfaceControl(mSurfaceControl);
}
diff --git a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
index a512fdf..017b325 100644
--- a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
+++ b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
@@ -86,8 +86,7 @@
static final int SET_WALLPAPER_MAY_CHANGE = 1 << 1;
static final int SET_FORCE_HIDING_CHANGED = 1 << 2;
static final int SET_ORIENTATION_CHANGE_COMPLETE = 1 << 3;
- static final int SET_TURN_ON_SCREEN = 1 << 4;
- static final int SET_WALLPAPER_ACTION_PENDING = 1 << 5;
+ static final int SET_WALLPAPER_ACTION_PENDING = 1 << 4;
private boolean mTraversalScheduled;
private int mDeferDepth = 0;
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java b/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java
index 29ac4ce..36de3d1 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java
@@ -19,6 +19,7 @@
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;
@@ -38,6 +39,12 @@
*/
abstract class BaseIDevicePolicyManager extends IDevicePolicyManager.Stub {
/**
+ * To be called by {@link DevicePolicyManagerService#Lifecycle} when the service is started.
+ *
+ * @see {@link SystemService#onStart}.
+ */
+ abstract void handleStart();
+ /**
* To be called by {@link DevicePolicyManagerService#Lifecycle} during the various boot phases.
*
* @see {@link SystemService#onBootPhase}.
@@ -97,4 +104,9 @@
byte[] cert, byte[] chain, boolean isUserSelectable) {
return false;
}
+
+ @Override
+ public boolean startUserInBackground(ComponentName who, UserHandle userHandle) {
+ 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 3592fc0..bf2b137 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -54,7 +54,6 @@
import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_COMPLEX;
import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED;
import static android.app.admin.DevicePolicyManager.PROFILE_KEYGUARD_FEATURES_AFFECT_OWNER;
-import static android.app.admin.DevicePolicyManager.START_USER_IN_BACKGROUND;
import static android.app.admin.DevicePolicyManager.WIPE_EUICC;
import static android.app.admin.DevicePolicyManager.WIPE_EXTERNAL_STORAGE;
import static android.app.admin.DevicePolicyManager.WIPE_RESET_PROTECTION_DATA;
@@ -96,6 +95,7 @@
import android.app.admin.SystemUpdatePolicy;
import android.app.backup.IBackupManager;
import android.app.trust.TrustManager;
+import android.app.usage.UsageStatsManagerInternal;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
@@ -150,6 +150,7 @@
import android.os.UserHandle;
import android.os.UserManager;
import android.os.UserManagerInternal;
+import android.os.UserManagerInternal.UserRestrictionsListener;
import android.os.storage.StorageManager;
import android.provider.ContactsContract.QuickContact;
import android.provider.ContactsInternal;
@@ -401,6 +402,7 @@
final IPackageManager mIPackageManager;
final UserManager mUserManager;
final UserManagerInternal mUserManagerInternal;
+ final UsageStatsManagerInternal mUsageStatsManagerInternal;
final TelephonyManager mTelephonyManager;
private final LockPatternUtils mLockPatternUtils;
private final DevicePolicyConstants mConstants;
@@ -504,6 +506,7 @@
@Override
public void onStart() {
publishBinderService(Context.DEVICE_POLICY_SERVICE, mService);
+ mService.handleStart();
}
@Override
@@ -700,6 +703,33 @@
}
};
+ protected static class RestrictionsListener implements UserRestrictionsListener {
+ private Context mContext;
+
+ public RestrictionsListener(Context context) {
+ mContext = context;
+ }
+
+ public void onUserRestrictionsChanged(int userId, Bundle newRestrictions,
+ Bundle prevRestrictions) {
+ final boolean newlyDisallowed =
+ newRestrictions.getBoolean(UserManager.DISALLOW_SHARE_INTO_MANAGED_PROFILE);
+ final boolean previouslyDisallowed =
+ prevRestrictions.getBoolean(UserManager.DISALLOW_SHARE_INTO_MANAGED_PROFILE);
+ final boolean restrictionChanged = (newlyDisallowed != previouslyDisallowed);
+
+ if (restrictionChanged) {
+ // Notify ManagedProvisioning to update the built-in cross profile intent filters.
+ Intent intent = new Intent(
+ DevicePolicyManager.ACTION_DATA_SHARING_RESTRICTION_CHANGED);
+ intent.setPackage(MANAGED_PROVISIONING_PKG);
+ intent.putExtra(Intent.EXTRA_USER_ID, userId);
+ intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+ mContext.sendBroadcastAsUser(intent, UserHandle.SYSTEM);
+ }
+ }
+ }
+
static class ActiveAdmin {
private static final String TAG_DISABLE_KEYGUARD_FEATURES = "disable-keyguard-features";
private static final String TAG_TEST_ONLY_ADMIN = "test-only-admin";
@@ -1560,6 +1590,7 @@
removedAdmin = true;
policy.mAdminList.remove(i);
policy.mAdminMap.remove(aa.info.getComponent());
+ pushActiveAdminPackagesLocked(userHandle);
}
}
} catch (RemoteException re) {
@@ -1653,6 +1684,10 @@
return LocalServices.getService(PackageManagerInternal.class);
}
+ UsageStatsManagerInternal getUsageStatsManagerInternal() {
+ return LocalServices.getService(UsageStatsManagerInternal.class);
+ }
+
NotificationManager getNotificationManager() {
return mContext.getSystemService(NotificationManager.class);
}
@@ -1691,6 +1726,10 @@
return ActivityManager.getService();
}
+ ActivityManagerInternal getActivityManagerInternal() {
+ return LocalServices.getService(ActivityManagerInternal.class);
+ }
+
IPackageManager getIPackageManager() {
return AppGlobals.getPackageManager();
}
@@ -1923,6 +1962,8 @@
mUserManager = Preconditions.checkNotNull(injector.getUserManager());
mUserManagerInternal = Preconditions.checkNotNull(injector.getUserManagerInternal());
+ mUsageStatsManagerInternal = Preconditions.checkNotNull(
+ injector.getUsageStatsManagerInternal());
mIPackageManager = Preconditions.checkNotNull(injector.getIPackageManager());
mTelephonyManager = Preconditions.checkNotNull(injector.getTelephonyManager());
@@ -1972,6 +2013,8 @@
LocalServices.addService(DevicePolicyManagerInternal.class, mLocalService);
mSetupContentObserver = new SetupContentObserver(mHandler);
+
+ mUserManagerInternal.addUserRestrictionsListener(new RestrictionsListener(mContext));
}
/**
@@ -3190,6 +3233,11 @@
}
@Override
+ void handleStart() {
+ pushActiveAdminPackages();
+ }
+
+ @Override
void handleStartUser(int userId) {
updateScreenCaptureDisabledInWindowManager(userId,
getScreenCaptureDisabled(null, userId));
@@ -3357,6 +3405,8 @@
if (replaceIndex == -1) {
policy.mAdminList.add(newAdmin);
enableIfNecessary(info.getPackageName(), userHandle);
+ mUsageStatsManagerInternal.onActiveAdminAdded(
+ adminReceiver.getPackageName(), userHandle);
} else {
policy.mAdminList.set(replaceIndex, newAdmin);
}
@@ -3369,6 +3419,35 @@
}
}
+ private void pushActiveAdminPackages() {
+ synchronized (this) {
+ final List<UserInfo> users = mUserManager.getUsers();
+ for (int i = users.size() - 1; i >= 0; --i) {
+ final int userId = users.get(i).id;
+ mUsageStatsManagerInternal.setActiveAdminApps(
+ getActiveAdminPackagesLocked(userId), userId);
+ }
+ }
+ }
+
+ private void pushActiveAdminPackagesLocked(int userId) {
+ mUsageStatsManagerInternal.setActiveAdminApps(
+ getActiveAdminPackagesLocked(userId), userId);
+ }
+
+ private Set<String> getActiveAdminPackagesLocked(int userId) {
+ final DevicePolicyData policy = getUserData(userId);
+ Set<String> adminPkgs = null;
+ for (int i = policy.mAdminList.size() - 1; i >= 0; --i) {
+ final String pkgName = policy.mAdminList.get(i).info.getPackageName();
+ if (adminPkgs == null) {
+ adminPkgs = new ArraySet<>();
+ }
+ adminPkgs.add(pkgName);
+ }
+ return adminPkgs;
+ }
+
private void transferActiveAdminUncheckedLocked(ComponentName incomingReceiver,
ComponentName outgoingReceiver, int userHandle) {
final DevicePolicyData policy = getUserData(userHandle);
@@ -3487,6 +3566,7 @@
}
}
+ @Override
public void forceRemoveActiveAdmin(ComponentName adminReceiver, int userHandle) {
if (!mHasFeature) {
return;
@@ -3540,7 +3620,7 @@
private boolean isPackageTestOnly(String packageName, int userHandle) {
final ApplicationInfo ai;
try {
- ai = mIPackageManager.getApplicationInfo(packageName,
+ ai = mInjector.getIPackageManager().getApplicationInfo(packageName,
(PackageManager.MATCH_DIRECT_BOOT_AWARE
| PackageManager.MATCH_DIRECT_BOOT_UNAWARE), userHandle);
} catch (RemoteException e) {
@@ -3562,7 +3642,7 @@
}
private void enforceShell(String method) {
- final int callingUid = Binder.getCallingUid();
+ final int callingUid = mInjector.binderGetCallingUid();
if (callingUid != Process.SHELL_UID && callingUid != Process.ROOT_UID) {
throw new SecurityException("Non-shell user attempted to call " + method);
}
@@ -8564,14 +8644,6 @@
Settings.Secure.USER_SETUP_COMPLETE, 1, userHandle);
}
- if ((flags & START_USER_IN_BACKGROUND) != 0) {
- try {
- mInjector.getIActivityManager().startUserInBackground(userHandle);
- } catch (RemoteException re) {
- // Does not happen, same process
- }
- }
-
return user;
} catch (Throwable re) {
mUserManager.removeUser(userHandle);
@@ -8584,6 +8656,8 @@
@Override
public boolean removeUser(ComponentName who, UserHandle userHandle) {
Preconditions.checkNotNull(who, "ComponentName is null");
+ Preconditions.checkNotNull(userHandle, "UserHandle is null");
+
synchronized (this) {
getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER);
}
@@ -8622,6 +8696,7 @@
@Override
public boolean switchUser(ComponentName who, UserHandle userHandle) {
Preconditions.checkNotNull(who, "ComponentName is null");
+
synchronized (this) {
getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER);
@@ -8642,8 +8717,40 @@
}
@Override
+ public boolean startUserInBackground(ComponentName who, UserHandle userHandle) {
+ Preconditions.checkNotNull(who, "ComponentName is null");
+ Preconditions.checkNotNull(userHandle, "UserHandle is null");
+
+ synchronized (this) {
+ getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER);
+ }
+
+ final int userId = userHandle.getIdentifier();
+ if (isManagedProfile(userId)) {
+ Log.w(LOG_TAG, "Managed profile cannot be started in background");
+ return false;
+ }
+
+ final long id = mInjector.binderClearCallingIdentity();
+ try {
+ if (!mInjector.getActivityManagerInternal().canStartMoreUsers()) {
+ Log.w(LOG_TAG, "Cannot start more users in background");
+ return false;
+ }
+
+ return mInjector.getIActivityManager().startUserInBackground(userId);
+ } catch (RemoteException e) {
+ // Same process, should not happen.
+ return false;
+ } finally {
+ mInjector.binderRestoreCallingIdentity(id);
+ }
+ }
+
+ @Override
public boolean stopUser(ComponentName who, UserHandle userHandle) {
Preconditions.checkNotNull(who, "ComponentName is null");
+ Preconditions.checkNotNull(userHandle, "UserHandle is null");
synchronized (this) {
getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER);
@@ -9692,7 +9799,7 @@
return false;
}
mLockPatternUtils.setLockScreenDisabled(disabled, userId);
- mInjector.getIWindowManager().dismissKeyguard(null);
+ mInjector.getIWindowManager().dismissKeyguard(null /* callback */, null /* message */);
} catch (RemoteException e) {
// Same process, does not happen.
} finally {
@@ -11163,6 +11270,7 @@
if (doProxyCleanup) {
resetGlobalProxyLocked(policy);
}
+ pushActiveAdminPackagesLocked(userHandle);
saveSettingsLocked(userHandle);
updateMaximumTimeToLockLocked(userHandle);
policy.mRemovingAdmins.remove(adminReceiver);
diff --git a/services/robotests/src/com/android/server/backup/BackupManagerServiceRoboTest.java b/services/robotests/src/com/android/server/backup/BackupManagerServiceRoboTest.java
index f9ebd28..bf58224 100644
--- a/services/robotests/src/com/android/server/backup/BackupManagerServiceRoboTest.java
+++ b/services/robotests/src/com/android/server/backup/BackupManagerServiceRoboTest.java
@@ -16,7 +16,11 @@
package com.android.server.backup;
-import static com.android.server.backup.testing.TransportTestUtils.TRANSPORT_NAMES;
+import static com.android.server.backup.testing.TransportData.backupTransport;
+import static com.android.server.backup.testing.TransportData.d2dTransport;
+import static com.android.server.backup.testing.TransportData.localTransport;
+import static com.android.server.backup.testing.TransportTestUtils.setUpCurrentTransport;
+import static com.android.server.backup.testing.TransportTestUtils.setUpTransports;
import static com.google.common.truth.Truth.assertThat;
@@ -24,6 +28,7 @@
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.robolectric.Shadows.shadowOf;
@@ -39,8 +44,8 @@
import android.provider.Settings;
import com.android.server.backup.testing.ShadowAppBackupUtils;
-import com.android.server.backup.testing.TransportTestUtils;
-import com.android.server.backup.testing.TransportTestUtils.TransportData;
+import com.android.server.backup.testing.TransportData;
+import com.android.server.backup.testing.TransportTestUtils.TransportMock;
import com.android.server.backup.transport.TransportNotRegisteredException;
import com.android.server.testing.FrameworkRobolectricTestRunner;
import com.android.server.testing.SystemLoaderClasses;
@@ -57,10 +62,10 @@
import org.robolectric.shadows.ShadowLog;
import org.robolectric.shadows.ShadowLooper;
import org.robolectric.shadows.ShadowSettings;
+import org.robolectric.shadows.ShadowSystemClock;
import java.io.File;
import java.util.HashMap;
-import java.util.List;
import java.util.Map;
@RunWith(FrameworkRobolectricTestRunner.class)
@@ -73,22 +78,24 @@
@Presubmit
public class BackupManagerServiceRoboTest {
private static final String TAG = "BMSTest";
- private static final String TRANSPORT_NAME =
- "com.google.android.gms/.backup.BackupTransportService";
@Mock private TransportManager mTransportManager;
private HandlerThread mBackupThread;
private ShadowLooper mShadowBackupLooper;
private File mBaseStateDir;
private File mDataDir;
- private RefactoredBackupManagerService mBackupManagerService;
private ShadowContextWrapper mShadowContext;
private Context mContext;
+ private TransportData mTransport;
+ private String mTransportName;
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
+ mTransport = backupTransport();
+ mTransportName = mTransport.transportName;
+
mBackupThread = new HandlerThread("backup-test");
mBackupThread.setUncaughtExceptionHandler(
(t, e) -> ShadowLog.e(TAG, "Uncaught exception in test thread " + t.getName(), e));
@@ -102,15 +109,6 @@
File cacheDir = mContext.getCacheDir();
mBaseStateDir = new File(cacheDir, "base_state_dir");
mDataDir = new File(cacheDir, "data_dir");
-
- mBackupManagerService =
- new RefactoredBackupManagerService(
- mContext,
- new Trampoline(mContext),
- mBackupThread,
- mBaseStateDir,
- mDataDir,
- mTransportManager);
}
@After
@@ -124,10 +122,12 @@
@Test
public void testDestinationString() throws Exception {
mShadowContext.grantPermissions(android.Manifest.permission.BACKUP);
- when(mTransportManager.getTransportCurrentDestinationString(eq(TRANSPORT_NAME)))
+ when(mTransportManager.getTransportCurrentDestinationString(eq(mTransportName)))
.thenReturn("destinationString");
+ RefactoredBackupManagerService backupManagerService =
+ createInitializedBackupManagerService();
- String destination = mBackupManagerService.getDestinationString(TRANSPORT_NAME);
+ String destination = backupManagerService.getDestinationString(mTransportName);
assertThat(destination).isEqualTo("destinationString");
}
@@ -135,10 +135,12 @@
@Test
public void testDestinationString_whenTransportNotRegistered() throws Exception {
mShadowContext.grantPermissions(android.Manifest.permission.BACKUP);
- when(mTransportManager.getTransportCurrentDestinationString(eq(TRANSPORT_NAME)))
+ when(mTransportManager.getTransportCurrentDestinationString(eq(mTransportName)))
.thenThrow(TransportNotRegisteredException.class);
+ RefactoredBackupManagerService backupManagerService =
+ createInitializedBackupManagerService();
- String destination = mBackupManagerService.getDestinationString(TRANSPORT_NAME);
+ String destination = backupManagerService.getDestinationString(mTransportName);
assertThat(destination).isNull();
}
@@ -146,12 +148,14 @@
@Test
public void testDestinationString_withoutPermission() throws Exception {
mShadowContext.denyPermissions(android.Manifest.permission.BACKUP);
- when(mTransportManager.getTransportCurrentDestinationString(eq(TRANSPORT_NAME)))
+ when(mTransportManager.getTransportCurrentDestinationString(eq(mTransportName)))
.thenThrow(TransportNotRegisteredException.class);
+ RefactoredBackupManagerService backupManagerService =
+ createInitializedBackupManagerService();
expectThrows(
SecurityException.class,
- () -> mBackupManagerService.getDestinationString(TRANSPORT_NAME));
+ () -> backupManagerService.getDestinationString(mTransportName));
}
/* Tests for app eligibility */
@@ -159,24 +163,28 @@
@Test
public void testIsAppEligibleForBackup_whenAppEligible() throws Exception {
mShadowContext.grantPermissions(android.Manifest.permission.BACKUP);
- TransportData transport =
- TransportTestUtils.setUpCurrentTransport(mTransportManager, TRANSPORT_NAME);
+ TransportMock transportMock = setUpCurrentTransport(mTransportManager, backupTransport());
ShadowAppBackupUtils.sAppIsRunningAndEligibleForBackupWithTransport = p -> true;
+ RefactoredBackupManagerService backupManagerService =
+ createInitializedBackupManagerService();
- boolean result = mBackupManagerService.isAppEligibleForBackup("app.package");
+ boolean result = backupManagerService.isAppEligibleForBackup("app.package");
assertThat(result).isTrue();
+
verify(mTransportManager)
- .disposeOfTransportClient(eq(transport.transportClientMock), any());
+ .disposeOfTransportClient(eq(transportMock.transportClient), any());
}
@Test
public void testIsAppEligibleForBackup_whenAppNotEligible() throws Exception {
mShadowContext.grantPermissions(android.Manifest.permission.BACKUP);
- TransportTestUtils.setUpCurrentTransport(mTransportManager, TRANSPORT_NAME);
+ setUpCurrentTransport(mTransportManager, mTransport);
ShadowAppBackupUtils.sAppIsRunningAndEligibleForBackupWithTransport = p -> false;
+ RefactoredBackupManagerService backupManagerService =
+ createInitializedBackupManagerService();
- boolean result = mBackupManagerService.isAppEligibleForBackup("app.package");
+ boolean result = backupManagerService.isAppEligibleForBackup("app.package");
assertThat(result).isFalse();
}
@@ -184,38 +192,43 @@
@Test
public void testIsAppEligibleForBackup_withoutPermission() throws Exception {
mShadowContext.denyPermissions(android.Manifest.permission.BACKUP);
- TransportTestUtils.setUpCurrentTransport(mTransportManager, TRANSPORT_NAME);
+ setUpCurrentTransport(mTransportManager, mTransport);
+ RefactoredBackupManagerService backupManagerService =
+ createInitializedBackupManagerService();
expectThrows(
SecurityException.class,
- () -> mBackupManagerService.isAppEligibleForBackup("app.package"));
+ () -> backupManagerService.isAppEligibleForBackup("app.package"));
}
@Test
public void testFilterAppsEligibleForBackup() throws Exception {
mShadowContext.grantPermissions(android.Manifest.permission.BACKUP);
- TransportData transport =
- TransportTestUtils.setUpCurrentTransport(mTransportManager, TRANSPORT_NAME);
+ TransportMock transportMock = setUpCurrentTransport(mTransportManager, mTransport);
Map<String, Boolean> packagesMap = new HashMap<>();
packagesMap.put("package.a", true);
packagesMap.put("package.b", false);
ShadowAppBackupUtils.sAppIsRunningAndEligibleForBackupWithTransport = packagesMap::get;
+ RefactoredBackupManagerService backupManagerService =
+ createInitializedBackupManagerService();
String[] packages = packagesMap.keySet().toArray(new String[packagesMap.size()]);
- String[] filtered = mBackupManagerService.filterAppsEligibleForBackup(packages);
+ String[] filtered = backupManagerService.filterAppsEligibleForBackup(packages);
assertThat(filtered).asList().containsExactly("package.a");
verify(mTransportManager)
- .disposeOfTransportClient(eq(transport.transportClientMock), any());
+ .disposeOfTransportClient(eq(transportMock.transportClient), any());
}
@Test
public void testFilterAppsEligibleForBackup_whenNoneIsEligible() throws Exception {
mShadowContext.grantPermissions(android.Manifest.permission.BACKUP);
ShadowAppBackupUtils.sAppIsRunningAndEligibleForBackupWithTransport = p -> false;
+ RefactoredBackupManagerService backupManagerService =
+ createInitializedBackupManagerService();
String[] filtered =
- mBackupManagerService.filterAppsEligibleForBackup(
+ backupManagerService.filterAppsEligibleForBackup(
new String[] {"package.a", "package.b"});
assertThat(filtered).isEmpty();
@@ -224,12 +237,14 @@
@Test
public void testFilterAppsEligibleForBackup_withoutPermission() throws Exception {
mShadowContext.denyPermissions(android.Manifest.permission.BACKUP);
- TransportTestUtils.setUpCurrentTransport(mTransportManager, TRANSPORT_NAME);
+ setUpCurrentTransport(mTransportManager, mTransport);
+ RefactoredBackupManagerService backupManagerService =
+ createInitializedBackupManagerService();
expectThrows(
SecurityException.class,
() ->
- mBackupManagerService.filterAppsEligibleForBackup(
+ backupManagerService.filterAppsEligibleForBackup(
new String[] {"package.a", "package.b"}));
}
@@ -238,14 +253,12 @@
private TransportData mNewTransport;
private TransportData mOldTransport;
private ComponentName mNewTransportComponent;
- private ISelectBackupTransportCallback mCallback;
private void setUpForSelectTransport() throws Exception {
- List<TransportData> transports =
- TransportTestUtils.setUpTransports(mTransportManager, TRANSPORT_NAMES);
- mNewTransport = transports.get(0);
- mNewTransportComponent = mNewTransport.transportClientMock.getTransportComponent();
- mOldTransport = transports.get(1);
+ mNewTransport = backupTransport();
+ mNewTransportComponent = mNewTransport.getTransportComponent();
+ mOldTransport = d2dTransport();
+ setUpTransports(mTransportManager, mNewTransport, mOldTransport, localTransport());
when(mTransportManager.selectTransport(eq(mNewTransport.transportName)))
.thenReturn(mOldTransport.transportName);
}
@@ -254,9 +267,11 @@
public void testSelectBackupTransport() throws Exception {
setUpForSelectTransport();
mShadowContext.grantPermissions(android.Manifest.permission.BACKUP);
+ RefactoredBackupManagerService backupManagerService =
+ createInitializedBackupManagerService();
String oldTransport =
- mBackupManagerService.selectBackupTransport(mNewTransport.transportName);
+ backupManagerService.selectBackupTransport(mNewTransport.transportName);
assertThat(getSettingsTransport()).isEqualTo(mNewTransport.transportName);
assertThat(oldTransport).isEqualTo(mOldTransport.transportName);
@@ -266,10 +281,12 @@
public void testSelectBackupTransport_withoutPermission() throws Exception {
setUpForSelectTransport();
mShadowContext.denyPermissions(android.Manifest.permission.BACKUP);
+ RefactoredBackupManagerService backupManagerService =
+ createInitializedBackupManagerService();
expectThrows(
SecurityException.class,
- () -> mBackupManagerService.selectBackupTransport(mNewTransport.transportName));
+ () -> backupManagerService.selectBackupTransport(mNewTransport.transportName));
}
@Test
@@ -278,9 +295,11 @@
mShadowContext.grantPermissions(android.Manifest.permission.BACKUP);
when(mTransportManager.registerAndSelectTransport(eq(mNewTransportComponent)))
.thenReturn(BackupManager.SUCCESS);
+ RefactoredBackupManagerService backupManagerService =
+ createInitializedBackupManagerService();
ISelectBackupTransportCallback callback = mock(ISelectBackupTransportCallback.class);
- mBackupManagerService.selectBackupTransportAsync(mNewTransportComponent, callback);
+ backupManagerService.selectBackupTransportAsync(mNewTransportComponent, callback);
mShadowBackupLooper.runToEndOfTasks();
assertThat(getSettingsTransport()).isEqualTo(mNewTransport.transportName);
@@ -293,9 +312,11 @@
mShadowContext.grantPermissions(android.Manifest.permission.BACKUP);
when(mTransportManager.registerAndSelectTransport(eq(mNewTransportComponent)))
.thenReturn(BackupManager.ERROR_TRANSPORT_UNAVAILABLE);
+ RefactoredBackupManagerService backupManagerService =
+ createInitializedBackupManagerService();
ISelectBackupTransportCallback callback = mock(ISelectBackupTransportCallback.class);
- mBackupManagerService.selectBackupTransportAsync(mNewTransportComponent, callback);
+ backupManagerService.selectBackupTransportAsync(mNewTransportComponent, callback);
mShadowBackupLooper.runToEndOfTasks();
assertThat(getSettingsTransport()).isNotEqualTo(mNewTransport.transportName);
@@ -304,19 +325,19 @@
@Test
public void testSelectBackupTransportAsync_whenTransportGetsUnregistered() throws Exception {
- TransportTestUtils.setUpTransports(
- mTransportManager, new TransportData(TRANSPORT_NAME, null, null));
- ComponentName newTransportComponent =
- TransportTestUtils.transportComponentName(TRANSPORT_NAME);
+ setUpTransports(mTransportManager, mTransport.unregistered());
+ ComponentName newTransportComponent = mTransport.getTransportComponent();
mShadowContext.grantPermissions(android.Manifest.permission.BACKUP);
when(mTransportManager.registerAndSelectTransport(eq(newTransportComponent)))
.thenReturn(BackupManager.SUCCESS);
+ RefactoredBackupManagerService backupManagerService =
+ createInitializedBackupManagerService();
ISelectBackupTransportCallback callback = mock(ISelectBackupTransportCallback.class);
- mBackupManagerService.selectBackupTransportAsync(newTransportComponent, callback);
+ backupManagerService.selectBackupTransportAsync(newTransportComponent, callback);
mShadowBackupLooper.runToEndOfTasks();
- assertThat(getSettingsTransport()).isNotEqualTo(TRANSPORT_NAME);
+ assertThat(getSettingsTransport()).isNotEqualTo(mTransportName);
verify(callback).onFailure(anyInt());
}
@@ -324,13 +345,14 @@
public void testSelectBackupTransportAsync_withoutPermission() throws Exception {
setUpForSelectTransport();
mShadowContext.denyPermissions(android.Manifest.permission.BACKUP);
- ComponentName newTransportComponent =
- mNewTransport.transportClientMock.getTransportComponent();
+ RefactoredBackupManagerService backupManagerService =
+ createInitializedBackupManagerService();
+ ComponentName newTransportComponent = mNewTransport.getTransportComponent();
expectThrows(
SecurityException.class,
() ->
- mBackupManagerService.selectBackupTransportAsync(
+ backupManagerService.selectBackupTransportAsync(
newTransportComponent, mock(ISelectBackupTransportCallback.class)));
}
@@ -338,4 +360,55 @@
return ShadowSettings.ShadowSecure.getString(
mContext.getContentResolver(), Settings.Secure.BACKUP_TRANSPORT);
}
+
+ /* Miscellaneous tests */
+
+ @Test
+ public void testConstructor_postRegisterTransports() {
+ mShadowContext.grantPermissions(android.Manifest.permission.BACKUP);
+
+ createBackupManagerService();
+
+ mShadowBackupLooper.runToEndOfTasks();
+ verify(mTransportManager).registerTransports();
+ }
+
+ @Test
+ public void testConstructor_doesNotRegisterTransportsSynchronously() {
+ mShadowContext.grantPermissions(android.Manifest.permission.BACKUP);
+
+ createBackupManagerService();
+
+ // Operations posted to mBackupThread only run with mShadowBackupLooper.runToEndOfTasks()
+ verify(mTransportManager, never()).registerTransports();
+ }
+
+ private RefactoredBackupManagerService createBackupManagerService() {
+ return new RefactoredBackupManagerService(
+ mContext,
+ new Trampoline(mContext),
+ mBackupThread,
+ mBaseStateDir,
+ mDataDir,
+ mTransportManager);
+ }
+
+ private RefactoredBackupManagerService createInitializedBackupManagerService() {
+ RefactoredBackupManagerService backupManagerService =
+ new RefactoredBackupManagerService(
+ mContext,
+ new Trampoline(mContext),
+ mBackupThread,
+ mBaseStateDir,
+ mDataDir,
+ mTransportManager);
+ mShadowBackupLooper.runToEndOfTasks();
+ // Handler instances have their own clock, so advancing looper (with runToEndOfTasks())
+ // above does NOT advance the handlers' clock, hence whenever a handler post messages with
+ // specific time to the looper the time of those messages will be before the looper's time.
+ // To fix this we advance SystemClock as well since that is from where the handlers read
+ // time.
+ ShadowSystemClock.setCurrentTimeMillis(mShadowBackupLooper.getScheduler().getCurrentTime());
+ return backupManagerService;
+ }
}
diff --git a/services/robotests/src/com/android/server/backup/TransportManagerTest.java b/services/robotests/src/com/android/server/backup/TransportManagerTest.java
index acd670f..cf0bc23 100644
--- a/services/robotests/src/com/android/server/backup/TransportManagerTest.java
+++ b/services/robotests/src/com/android/server/backup/TransportManagerTest.java
@@ -16,108 +16,97 @@
package com.android.server.backup;
+import static com.android.server.backup.testing.TransportData.genericTransport;
+import static com.android.server.backup.testing.TransportTestUtils.mockTransport;
+import static com.android.server.backup.testing.TransportTestUtils.setUpTransportsForTransportManager;
+
import static com.google.common.truth.Truth.assertThat;
-import static org.mockito.Mockito.mock;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.robolectric.shadow.api.Shadow.extract;
import static org.testng.Assert.expectThrows;
+import static java.util.Arrays.asList;
+import static java.util.Collections.emptyList;
+import static java.util.Collections.singletonList;
+import static java.util.stream.Collectors.toList;
+import static java.util.stream.Collectors.toSet;
+import static java.util.stream.Stream.concat;
+
import android.annotation.Nullable;
import android.content.ComponentName;
+import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
-import android.content.pm.ResolveInfo;
-import android.content.pm.ServiceInfo;
-import android.os.IBinder;
-import android.os.RemoteException;
import android.platform.test.annotations.Presubmit;
-import com.android.internal.backup.IBackupTransport;
-import com.android.server.backup.testing.ShadowBackupTransportStub;
import com.android.server.backup.testing.ShadowContextImplForBackup;
-import com.android.server.backup.testing.ShadowPackageManagerForBackup;
-import com.android.server.backup.testing.TransportBoundListenerStub;
+import com.android.server.testing.shadows.FrameworkShadowPackageManager;
+import com.android.server.backup.testing.TransportData;
+import com.android.server.backup.testing.TransportTestUtils.TransportMock;
+import com.android.server.backup.transport.OnTransportRegisteredListener;
import com.android.server.backup.transport.TransportClient;
+import com.android.server.backup.transport.TransportClientManager;
import com.android.server.backup.transport.TransportNotRegisteredException;
import com.android.server.testing.FrameworkRobolectricTestRunner;
import com.android.server.testing.SystemLoaderClasses;
+import com.android.server.testing.shadows.FrameworkShadowContextImpl;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Config;
-import org.robolectric.shadows.ShadowLog;
-import org.robolectric.shadows.ShadowLooper;
import org.robolectric.shadows.ShadowPackageManager;
import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.HashSet;
import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+import java.util.stream.Stream;
@RunWith(FrameworkRobolectricTestRunner.class)
@Config(
- manifest = Config.NONE,
- sdk = 26,
- shadows = {
- ShadowContextImplForBackup.class,
- ShadowBackupTransportStub.class,
- ShadowPackageManagerForBackup.class
- }
+ manifest = Config.NONE,
+ sdk = 26,
+ shadows = {FrameworkShadowPackageManager.class, FrameworkShadowContextImpl.class}
)
@SystemLoaderClasses({TransportManager.class})
@Presubmit
public class TransportManagerTest {
- private static final String PACKAGE_NAME = "some.package.name";
- private static final String ANOTHER_PACKAGE_NAME = "another.package.name";
+ private static final String PACKAGE_A = "some.package.a";
+ private static final String PACKAGE_B = "some.package.b";
- private TransportInfo mTransport1;
- private TransportInfo mTransport2;
+ @Mock private OnTransportRegisteredListener mListener;
+ @Mock private TransportClientManager mTransportClientManager;
+ private TransportData mTransportA1;
+ private TransportData mTransportA2;
+ private TransportData mTransportB1;
- private ShadowPackageManager mPackageManagerShadow;
-
- private final TransportBoundListenerStub mTransportBoundListenerStub =
- new TransportBoundListenerStub(true);
+ private ShadowPackageManager mShadowPackageManager;
+ private Context mContext;
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
- ShadowLog.stream = System.out;
-
- mPackageManagerShadow =
- (ShadowPackageManagerForBackup)
+ mShadowPackageManager =
+ (FrameworkShadowPackageManager)
extract(RuntimeEnvironment.application.getPackageManager());
+ mContext = RuntimeEnvironment.application.getApplicationContext();
- mTransport1 = new TransportInfo(
- PACKAGE_NAME,
- "transport1.name",
- new Intent(),
- "currentDestinationString",
- new Intent(),
- "dataManagementLabel");
- mTransport2 = new TransportInfo(
- PACKAGE_NAME,
- "transport2.name",
- new Intent(),
- "currentDestinationString",
- new Intent(),
- "dataManagementLabel");
-
- ShadowContextImplForBackup.sComponentBinderMap.put(mTransport1.componentName,
- mTransport1.binder);
- ShadowContextImplForBackup.sComponentBinderMap.put(mTransport2.componentName,
- mTransport2.binder);
- ShadowBackupTransportStub.sBinderTransportMap.put(
- mTransport1.binder, mTransport1.binderInterface);
- ShadowBackupTransportStub.sBinderTransportMap.put(
- mTransport2.binder, mTransport2.binderInterface);
+ mTransportA1 = genericTransport(PACKAGE_A, "TransportFoo");
+ mTransportA2 = genericTransport(PACKAGE_A, "TransportBar");
+ mTransportB1 = genericTransport(PACKAGE_B, "TransportBaz");
}
@After
@@ -126,560 +115,441 @@
}
@Test
- public void onPackageAdded_bindsToAllTransports() throws Exception {
- setUpPackageWithTransports(PACKAGE_NAME, Arrays.asList(mTransport1, mTransport2),
- ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
-
- TransportManager transportManager = new TransportManager(
- RuntimeEnvironment.application.getApplicationContext(),
- new HashSet<>(Arrays.asList(
- mTransport1.componentName, mTransport2.componentName)),
- null /* defaultTransport */,
- mTransportBoundListenerStub,
- ShadowLooper.getMainLooper());
- transportManager.onPackageAdded(PACKAGE_NAME);
-
- assertThat(transportManager.getAllTransportComponents()).asList().containsExactlyElementsIn(
- Arrays.asList(mTransport1.componentName, mTransport2.componentName));
- assertThat(transportManager.getBoundTransportNames()).asList().containsExactlyElementsIn(
- Arrays.asList(mTransport1.name, mTransport2.name));
- assertThat(mTransportBoundListenerStub.isCalledForTransport(mTransport1.binderInterface))
- .isTrue();
- assertThat(mTransportBoundListenerStub.isCalledForTransport(mTransport2.binderInterface))
- .isTrue();
- }
-
- @Test
- public void onPackageAdded_oneTransportUnavailable_bindsToOnlyOneTransport() throws Exception {
- setUpPackageWithTransports(PACKAGE_NAME, Arrays.asList(mTransport1, mTransport2),
- ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
-
- ShadowContextImplForBackup.sUnbindableComponents.add(mTransport1.componentName);
-
- TransportManager transportManager = new TransportManager(
- RuntimeEnvironment.application.getApplicationContext(),
- new HashSet<>(Arrays.asList(
- mTransport1.componentName, mTransport2.componentName)),
- null /* defaultTransport */,
- mTransportBoundListenerStub,
- ShadowLooper.getMainLooper());
- transportManager.onPackageAdded(PACKAGE_NAME);
-
- assertThat(transportManager.getAllTransportComponents()).asList().containsExactlyElementsIn(
- Collections.singleton(mTransport2.componentName));
- assertThat(transportManager.getBoundTransportNames()).asList().containsExactlyElementsIn(
- Collections.singleton(mTransport2.name));
- assertThat(mTransportBoundListenerStub.isCalledForTransport(mTransport1.binderInterface))
- .isFalse();
- assertThat(mTransportBoundListenerStub.isCalledForTransport(mTransport2.binderInterface))
- .isTrue();
- }
-
- @Test
- public void onPackageAdded_whitelistIsNull_doesNotBindToTransports() throws Exception {
- setUpPackageWithTransports(PACKAGE_NAME, Arrays.asList(mTransport1, mTransport2),
- ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
-
- TransportManager transportManager = new TransportManager(
- RuntimeEnvironment.application.getApplicationContext(),
- null /* whitelist */,
- null /* defaultTransport */,
- mTransportBoundListenerStub,
- ShadowLooper.getMainLooper());
- transportManager.onPackageAdded(PACKAGE_NAME);
-
- assertThat(transportManager.getAllTransportComponents()).isEmpty();
- assertThat(transportManager.getBoundTransportNames()).isEmpty();
- assertThat(mTransportBoundListenerStub.isCalled()).isFalse();
- }
-
- @Test
- public void onPackageAdded_onlyOneTransportWhitelisted_onlyConnectsToWhitelistedTransport()
- throws Exception {
- setUpPackageWithTransports(PACKAGE_NAME, Arrays.asList(mTransport1, mTransport2),
- ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
-
- TransportManager transportManager = new TransportManager(
- RuntimeEnvironment.application.getApplicationContext(),
- new HashSet<>(Collections.singleton(mTransport2.componentName)),
- null /* defaultTransport */,
- mTransportBoundListenerStub,
- ShadowLooper.getMainLooper());
- transportManager.onPackageAdded(PACKAGE_NAME);
-
- assertThat(transportManager.getAllTransportComponents()).asList().containsExactlyElementsIn(
- Collections.singleton(mTransport2.componentName));
- assertThat(transportManager.getBoundTransportNames()).asList().containsExactlyElementsIn(
- Collections.singleton(mTransport2.name));
- assertThat(mTransportBoundListenerStub.isCalledForTransport(mTransport1.binderInterface))
- .isFalse();
- assertThat(mTransportBoundListenerStub.isCalledForTransport(mTransport2.binderInterface))
- .isTrue();
- }
-
- @Test
- public void onPackageAdded_appIsNotPrivileged_doesNotBindToTransports() throws Exception {
- setUpPackageWithTransports(PACKAGE_NAME, Arrays.asList(mTransport1, mTransport2), 0);
-
- TransportManager transportManager = new TransportManager(
- RuntimeEnvironment.application.getApplicationContext(),
- new HashSet<>(Arrays.asList(
- mTransport1.componentName, mTransport2.componentName)),
- null /* defaultTransport */,
- mTransportBoundListenerStub,
- ShadowLooper.getMainLooper());
- transportManager.onPackageAdded(PACKAGE_NAME);
-
- assertThat(transportManager.getAllTransportComponents()).isEmpty();
- assertThat(transportManager.getBoundTransportNames()).isEmpty();
- assertThat(mTransportBoundListenerStub.isCalled()).isFalse();
- }
-
- @Test
- public void onPackageRemoved_transportsUnbound() throws Exception {
- TransportManager transportManager = createTransportManagerAndSetUpTransports(
- Arrays.asList(mTransport1, mTransport2), mTransport1.name);
-
- transportManager.onPackageRemoved(PACKAGE_NAME);
-
- assertThat(transportManager.getAllTransportComponents()).isEmpty();
- assertThat(transportManager.getBoundTransportNames()).isEmpty();
- }
-
- @Test
- public void onPackageRemoved_incorrectPackageName_nothingHappens() throws Exception {
- TransportManager transportManager = createTransportManagerAndSetUpTransports(
- Arrays.asList(mTransport1, mTransport2), mTransport1.name);
-
- transportManager.onPackageRemoved(ANOTHER_PACKAGE_NAME);
-
- assertThat(transportManager.getAllTransportComponents()).asList().containsExactlyElementsIn(
- Arrays.asList(mTransport1.componentName, mTransport2.componentName));
- assertThat(transportManager.getBoundTransportNames()).asList().containsExactlyElementsIn(
- Arrays.asList(mTransport1.name, mTransport2.name));
- }
-
- @Test
- public void onPackageChanged_oneComponentChanged_onlyOneTransportRebound() throws Exception {
- TransportManager transportManager = createTransportManagerAndSetUpTransports(
- Arrays.asList(mTransport1, mTransport2), mTransport1.name);
-
- transportManager.onPackageChanged(PACKAGE_NAME, new String[]{mTransport2.name});
-
- assertThat(transportManager.getAllTransportComponents()).asList().containsExactlyElementsIn(
- Arrays.asList(mTransport1.componentName, mTransport2.componentName));
- assertThat(transportManager.getBoundTransportNames()).asList().containsExactlyElementsIn(
- Arrays.asList(mTransport1.name, mTransport2.name));
- assertThat(mTransportBoundListenerStub.isCalledForTransport(mTransport1.binderInterface))
- .isFalse();
- assertThat(mTransportBoundListenerStub.isCalledForTransport(mTransport2.binderInterface))
- .isTrue();
- }
-
- @Test
- public void onPackageChanged_nothingChanged_noTransportsRebound() throws Exception {
- TransportManager transportManager = createTransportManagerAndSetUpTransports(
- Arrays.asList(mTransport1, mTransport2), mTransport1.name);
-
- transportManager.onPackageChanged(PACKAGE_NAME, new String[0]);
-
- assertThat(transportManager.getAllTransportComponents()).asList().containsExactlyElementsIn(
- Arrays.asList(mTransport1.componentName, mTransport2.componentName));
- assertThat(transportManager.getBoundTransportNames()).asList().containsExactlyElementsIn(
- Arrays.asList(mTransport1.name, mTransport2.name));
- assertThat(mTransportBoundListenerStub.isCalledForTransport(mTransport1.binderInterface))
- .isFalse();
- assertThat(mTransportBoundListenerStub.isCalledForTransport(mTransport2.binderInterface))
- .isFalse();
- }
-
- @Test
- public void onPackageChanged_unexpectedComponentChanged_noTransportsRebound() throws Exception {
- TransportManager transportManager = createTransportManagerAndSetUpTransports(
- Arrays.asList(mTransport1, mTransport2), mTransport1.name);
-
- transportManager.onPackageChanged(PACKAGE_NAME, new String[]{"unexpected.component"});
-
- assertThat(transportManager.getAllTransportComponents()).asList().containsExactlyElementsIn(
- Arrays.asList(mTransport1.componentName, mTransport2.componentName));
- assertThat(transportManager.getBoundTransportNames()).asList().containsExactlyElementsIn(
- Arrays.asList(mTransport1.name, mTransport2.name));
- assertThat(mTransportBoundListenerStub.isCalledForTransport(mTransport1.binderInterface))
- .isFalse();
- assertThat(mTransportBoundListenerStub.isCalledForTransport(mTransport2.binderInterface))
- .isFalse();
- }
-
- @Test
- public void onPackageChanged_transportsRebound() throws Exception {
- TransportManager transportManager = createTransportManagerAndSetUpTransports(
- Arrays.asList(mTransport1, mTransport2), mTransport1.name);
-
- transportManager.onPackageChanged(PACKAGE_NAME, new String[]{mTransport2.name});
-
- assertThat(transportManager.getAllTransportComponents()).asList().containsExactlyElementsIn(
- Arrays.asList(mTransport1.componentName, mTransport2.componentName));
- assertThat(transportManager.getBoundTransportNames()).asList().containsExactlyElementsIn(
- Arrays.asList(mTransport1.name, mTransport2.name));
- assertThat(mTransportBoundListenerStub.isCalledForTransport(mTransport1.binderInterface))
- .isFalse();
- assertThat(mTransportBoundListenerStub.isCalledForTransport(mTransport2.binderInterface))
- .isTrue();
- }
-
- @Test
- public void getTransportBinder_returnsCorrectBinder() throws Exception {
- TransportManager transportManager = createTransportManagerAndSetUpTransports(
- Arrays.asList(mTransport1, mTransport2), mTransport1.name);
-
- assertThat(transportManager.getTransportBinder(mTransport1.name)).isEqualTo(
- mTransport1.binderInterface);
- assertThat(transportManager.getTransportBinder(mTransport2.name)).isEqualTo(
- mTransport2.binderInterface);
- }
-
- @Test
- public void getTransportBinder_incorrectTransportName_returnsNull() throws Exception {
- TransportManager transportManager = createTransportManagerAndSetUpTransports(
- Arrays.asList(mTransport1, mTransport2), mTransport1.name);
-
- assertThat(transportManager.getTransportBinder("incorrect.transport")).isNull();
- }
-
- @Test
- public void getTransportBinder_oneTransportUnavailable_returnsCorrectBinder() throws Exception {
+ public void testRegisterTransports() throws Exception {
+ setUpPackage(PACKAGE_A, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
+ setUpPackage(PACKAGE_B, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
+ setUpTransports(mTransportA1, mTransportA2, mTransportB1);
TransportManager transportManager =
- createTransportManagerAndSetUpTransports(Collections.singletonList(mTransport2),
- Collections.singletonList(mTransport1), mTransport1.name);
+ createTransportManager(mTransportA1, mTransportA2, mTransportB1);
- assertThat(transportManager.getTransportBinder(mTransport1.name)).isNull();
- assertThat(transportManager.getTransportBinder(mTransport2.name)).isEqualTo(
- mTransport2.binderInterface);
+ transportManager.registerTransports();
+
+ assertRegisteredTransports(
+ transportManager, asList(mTransportA1, mTransportA2, mTransportB1));
+
+ verify(mListener)
+ .onTransportRegistered(mTransportA1.transportName, mTransportA1.transportDirName);
+ verify(mListener)
+ .onTransportRegistered(mTransportA2.transportName, mTransportA2.transportDirName);
+ verify(mListener)
+ .onTransportRegistered(mTransportB1.transportName, mTransportB1.transportDirName);
}
@Test
- public void getCurrentTransport_selectTransportNotCalled_returnsDefaultTransport()
+ public void
+ testRegisterTransports_whenOneTransportUnavailable_doesNotRegisterUnavailableTransport()
+ throws Exception {
+ setUpPackage(PACKAGE_A, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
+ TransportData transport1 = mTransportA1.unavailable();
+ TransportData transport2 = mTransportA2;
+ setUpTransports(transport1, transport2);
+ TransportManager transportManager = createTransportManager(transport1, transport2);
+
+ transportManager.registerTransports();
+
+ assertRegisteredTransports(transportManager, singletonList(transport2));
+ verify(mListener, never())
+ .onTransportRegistered(transport1.transportName, transport1.transportDirName);
+ verify(mListener)
+ .onTransportRegistered(transport2.transportName, transport2.transportDirName);
+ }
+
+ @Test
+ public void testRegisterTransports_whenWhitelistIsEmpty_doesNotRegisterTransports()
throws Exception {
- TransportManager transportManager = createTransportManagerAndSetUpTransports(
- Arrays.asList(mTransport1, mTransport2), mTransport1.name);
+ setUpPackage(PACKAGE_A, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
+ setUpTransports(mTransportA1, mTransportA2);
+ TransportManager transportManager = createTransportManager(null);
- assertThat(transportManager.getCurrentTransportName()).isEqualTo(mTransport1.name);
+ transportManager.registerTransports();
+
+ assertRegisteredTransports(transportManager, emptyList());
+ verify(mListener, never()).onTransportRegistered(any(), any());
}
@Test
- public void getCurrentTransport_selectTransportCalled_returnsCorrectTransport()
+ public void
+ testRegisterTransports_whenOnlyOneTransportWhitelisted_onlyRegistersWhitelistedTransport()
+ throws Exception {
+ setUpPackage(PACKAGE_A, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
+ setUpTransports(mTransportA1, mTransportA2);
+ TransportManager transportManager = createTransportManager(null, mTransportA1);
+
+ transportManager.registerTransports();
+
+ assertRegisteredTransports(transportManager, singletonList(mTransportA1));
+ verify(mListener)
+ .onTransportRegistered(mTransportA1.transportName, mTransportA1.transportDirName);
+ verify(mListener, never())
+ .onTransportRegistered(mTransportA2.transportName, mTransportA2.transportDirName);
+ }
+
+ @Test
+ public void testRegisterTransports_whenAppIsNotPrivileged_doesNotRegisterTransports()
throws Exception {
- TransportManager transportManager = createTransportManagerAndSetUpTransports(
- Arrays.asList(mTransport1, mTransport2), mTransport1.name);
-
- assertThat(transportManager.getCurrentTransportName()).isEqualTo(mTransport1.name);
-
- transportManager.selectTransport(mTransport2.name);
-
- assertThat(transportManager.getCurrentTransportName()).isEqualTo(mTransport2.name);
- }
-
- @Test
- public void getCurrentTransportBinder_returnsCorrectBinder() throws Exception {
- TransportManager transportManager = createTransportManagerAndSetUpTransports(
- Arrays.asList(mTransport1, mTransport2), mTransport1.name);
-
- assertThat(transportManager.getCurrentTransportBinder())
- .isEqualTo(mTransport1.binderInterface);
- }
-
- @Test
- public void getCurrentTransportBinder_transportNotBound_returnsNull() throws Exception {
+ // Note ApplicationInfo.PRIVATE_FLAG_PRIVILEGED is missing from flags
+ setUpPackage(PACKAGE_A, 0);
+ setUpTransports(mTransportA1, mTransportA2);
TransportManager transportManager =
- createTransportManagerAndSetUpTransports(Collections.singletonList(mTransport2),
- Collections.singletonList(mTransport1), mTransport2.name);
+ createTransportManager(null, mTransportA1, mTransportA2);
- transportManager.selectTransport(mTransport1.name);
+ transportManager.registerTransports();
- assertThat(transportManager.getCurrentTransportBinder()).isNull();
+ assertRegisteredTransports(transportManager, emptyList());
+ verify(mListener, never()).onTransportRegistered(any(), any());
}
@Test
- public void getTransportName_returnsCorrectTransportName() throws Exception {
- TransportManager transportManager = createTransportManagerAndSetUpTransports(
- Arrays.asList(mTransport1, mTransport2), mTransport1.name);
+ public void testOnPackageAdded_registerTransports() throws Exception {
+ setUpPackage(PACKAGE_A, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
+ setUpTransports(mTransportA1);
+ TransportManager transportManager = createTransportManager(mTransportA1);
- assertThat(transportManager.getTransportName(mTransport1.binderInterface))
- .isEqualTo(mTransport1.name);
- assertThat(transportManager.getTransportName(mTransport2.binderInterface))
- .isEqualTo(mTransport2.name);
+ transportManager.onPackageAdded(PACKAGE_A);
+
+ assertRegisteredTransports(transportManager, asList(mTransportA1));
+ verify(mListener)
+ .onTransportRegistered(mTransportA1.transportName, mTransportA1.transportDirName);
}
@Test
- public void getTransportName_transportNotBound_returnsNull() throws Exception {
- TransportManager transportManager =
- createTransportManagerAndSetUpTransports(Collections.singletonList(mTransport2),
- Collections.singletonList(mTransport1), mTransport1.name);
+ public void testOnPackageRemoved_unregisterTransports() throws Exception {
+ setUpPackage(PACKAGE_A, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
+ setUpPackage(PACKAGE_B, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
+ setUpTransports(mTransportA1, mTransportB1);
+ TransportManager transportManager = createTransportManager(mTransportA1, mTransportB1);
+ transportManager.registerTransports();
- assertThat(transportManager.getTransportName(mTransport1.binderInterface)).isNull();
- assertThat(transportManager.getTransportName(mTransport2.binderInterface))
- .isEqualTo(mTransport2.name);
+ transportManager.onPackageRemoved(PACKAGE_A);
+
+ assertRegisteredTransports(transportManager, singletonList(mTransportB1));
}
@Test
- public void getTransportWhitelist_returnsCorrectWhiteList() throws Exception {
- TransportManager transportManager = new TransportManager(
- RuntimeEnvironment.application.getApplicationContext(),
- new HashSet<>(Arrays.asList(mTransport1.componentName, mTransport2.componentName)),
- mTransport1.name,
- mTransportBoundListenerStub,
- ShadowLooper.getMainLooper());
+ public void testOnPackageRemoved_whenUnknownPackage_nothingHappens() throws Exception {
+ setUpPackage(PACKAGE_A, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
+ setUpTransports(mTransportA1);
+ TransportManager transportManager = createTransportManager(mTransportA1);
+ transportManager.registerTransports();
- assertThat(transportManager.getTransportWhitelist()).containsExactlyElementsIn(
- Arrays.asList(mTransport1.componentName, mTransport2.componentName));
+ transportManager.onPackageRemoved(PACKAGE_A + "unknown");
+
+ assertRegisteredTransports(transportManager, singletonList(mTransportA1));
}
@Test
- public void getTransportWhitelist_whiteListIsNull_returnsEmptyArray() throws Exception {
- TransportManager transportManager = new TransportManager(
- RuntimeEnvironment.application.getApplicationContext(),
- null /* whitelist */,
- mTransport1.name,
- mTransportBoundListenerStub,
- ShadowLooper.getMainLooper());
-
- assertThat(transportManager.getTransportWhitelist()).isEmpty();
- }
-
- @Test
- public void selectTransport_setsTransportCorrectlyAndReturnsPreviousTransport()
+ public void testOnPackageChanged_whenOneComponentChanged_onlyOneTransportReRegistered()
throws Exception {
- TransportManager transportManager = new TransportManager(
- RuntimeEnvironment.application.getApplicationContext(),
- null /* whitelist */,
- mTransport1.name,
- mTransportBoundListenerStub,
- ShadowLooper.getMainLooper());
+ setUpPackage(PACKAGE_A, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
+ setUpTransports(mTransportA1, mTransportA2);
+ TransportManager transportManager = createTransportManager(mTransportA1, mTransportA2);
+ transportManager.registerTransports();
+ // Reset listener to verify calls after registerTransports() above
+ reset(mListener);
- assertThat(transportManager.selectTransport(mTransport2.name)).isEqualTo(mTransport1.name);
- assertThat(transportManager.selectTransport(mTransport1.name)).isEqualTo(mTransport2.name);
+ transportManager.onPackageChanged(
+ PACKAGE_A, mTransportA1.getTransportComponent().getClassName());
+
+ assertRegisteredTransports(transportManager, asList(mTransportA1, mTransportA2));
+ verify(mListener)
+ .onTransportRegistered(mTransportA1.transportName, mTransportA1.transportDirName);
+ verify(mListener, never())
+ .onTransportRegistered(mTransportA2.transportName, mTransportA2.transportDirName);
}
@Test
- public void getTransportClient_forRegisteredTransport_returnCorrectly() throws Exception {
+ public void testOnPackageChanged_whenNoComponentsChanged_doesNotRegisterTransports()
+ throws Exception {
+ setUpPackage(PACKAGE_A, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
+ setUpTransports(mTransportA1);
+ TransportManager transportManager = createTransportManager(mTransportA1);
+ transportManager.registerTransports();
+ reset(mListener);
+
+ transportManager.onPackageChanged(PACKAGE_A);
+
+ assertRegisteredTransports(transportManager, singletonList(mTransportA1));
+ verify(mListener, never()).onTransportRegistered(any(), any());
+ }
+
+ @Test
+ public void testOnPackageChanged_whenUnknownComponentChanged_noTransportsRegistered()
+ throws Exception {
+ setUpPackage(PACKAGE_A, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
+ setUpTransports(mTransportA1);
+ TransportManager transportManager = createTransportManager(mTransportA1);
+ transportManager.registerTransports();
+ reset(mListener);
+
+ transportManager.onPackageChanged(PACKAGE_A, PACKAGE_A + ".UnknownComponent");
+
+ assertRegisteredTransports(transportManager, singletonList(mTransportA1));
+ verify(mListener, never()).onTransportRegistered(any(), any());
+ }
+
+ @Test
+ public void testOnPackageChanged_reRegisterTransports() throws Exception {
+ setUpPackage(PACKAGE_A, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
+ setUpTransports(mTransportA1, mTransportA2);
+ TransportManager transportManager = createTransportManager(mTransportA1, mTransportA2);
+ transportManager.registerTransports();
+ reset(mListener);
+
+ transportManager.onPackageChanged(
+ PACKAGE_A,
+ mTransportA1.getTransportComponent().getClassName(),
+ mTransportA2.getTransportComponent().getClassName());
+
+ assertRegisteredTransports(transportManager, asList(mTransportA1, mTransportA2));
+ verify(mListener)
+ .onTransportRegistered(mTransportA1.transportName, mTransportA1.transportDirName);
+ verify(mListener)
+ .onTransportRegistered(mTransportA2.transportName, mTransportA2.transportDirName);
+ }
+
+ @Test
+ public void testGetCurrentTransportName_whenSelectTransportNotCalled_returnsDefaultTransport()
+ throws Exception {
+ setUpPackage(PACKAGE_A, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
+ setUpTransports(mTransportA1, mTransportA2);
+ TransportManager transportManager = createTransportManager(mTransportA1, mTransportA2);
+ transportManager.registerTransports();
+
+ String currentTransportName = transportManager.getCurrentTransportName();
+
+ assertThat(currentTransportName).isEqualTo(mTransportA1.transportName);
+ }
+
+ @Test
+ public void testGetCurrentTransport_whenSelectTransportCalled_returnsSelectedTransport()
+ throws Exception {
+ setUpPackage(PACKAGE_A, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
+ setUpTransports(mTransportA1, mTransportA2);
+ TransportManager transportManager = createTransportManager(mTransportA1, mTransportA2);
+ transportManager.registerTransports();
+ transportManager.selectTransport(mTransportA2.transportName);
+
+ String currentTransportName = transportManager.getCurrentTransportName();
+
+ assertThat(currentTransportName).isEqualTo(mTransportA2.transportName);
+ }
+
+ @Test
+ public void testGetTransportWhitelist() throws Exception {
+ setUpPackage(PACKAGE_A, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
+ setUpTransports(mTransportA1, mTransportA2);
+ TransportManager transportManager = createTransportManager(mTransportA1, mTransportA2);
+
+ Set<ComponentName> transportWhitelist = transportManager.getTransportWhitelist();
+
+ assertThat(transportWhitelist)
+ .containsExactlyElementsIn(
+ asList(
+ mTransportA1.getTransportComponent(),
+ mTransportA2.getTransportComponent()));
+ }
+
+ @Test
+ public void testSelectTransport() throws Exception {
+ setUpPackage(PACKAGE_A, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
+ setUpTransports(mTransportA1, mTransportA2);
TransportManager transportManager =
- createTransportManagerAndSetUpTransports(
- Arrays.asList(mTransport1, mTransport2), mTransport1.name);
+ createTransportManager(null, mTransportA1, mTransportA2);
+
+ String transport1 = transportManager.selectTransport(mTransportA1.transportName);
+ String transport2 = transportManager.selectTransport(mTransportA2.transportName);
+
+ assertThat(transport1).isNull();
+ assertThat(transport2).isEqualTo(mTransportA1.transportName);
+ }
+
+ @Test
+ public void testGetTransportClient_forRegisteredTransport() throws Exception {
+ setUpPackage(PACKAGE_A, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
+ setUpTransports(mTransportA1, mTransportA2);
+ TransportManager transportManager = createTransportManager(mTransportA1, mTransportA2);
+ transportManager.registerTransports();
TransportClient transportClient =
- transportManager.getTransportClient(mTransport1.name, "caller");
+ transportManager.getTransportClient(mTransportA1.transportName, "caller");
- assertThat(transportClient.getTransportComponent()).isEqualTo(mTransport1.componentName);
+ assertThat(transportClient.getTransportComponent())
+ .isEqualTo(mTransportA1.getTransportComponent());
}
@Test
- public void getTransportClient_forOldNameOfTransportThatChangedName_returnsNull()
+ public void testGetTransportClient_forOldNameOfTransportThatChangedName_returnsNull()
throws Exception {
- TransportManager transportManager =
- createTransportManagerAndSetUpTransports(
- Arrays.asList(mTransport1, mTransport2), mTransport1.name);
+ setUpPackage(PACKAGE_A, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
+ setUpTransports(mTransportA1, mTransportA2);
+ TransportManager transportManager = createTransportManager(mTransportA1, mTransportA2);
+ transportManager.registerTransports();
transportManager.updateTransportAttributes(
- mTransport1.componentName, "newName", null, "destinationString", null, null);
+ mTransportA1.getTransportComponent(),
+ "newName",
+ null,
+ "destinationString",
+ null,
+ null);
TransportClient transportClient =
- transportManager.getTransportClient(mTransport1.name, "caller");
+ transportManager.getTransportClient(mTransportA1.transportName, "caller");
assertThat(transportClient).isNull();
}
@Test
- public void getTransportClient_forNewNameOfTransportThatChangedName_returnsCorrectly()
+ public void testGetTransportClient_forNewNameOfTransportThatChangedName_returnsCorrectly()
throws Exception {
- TransportManager transportManager =
- createTransportManagerAndSetUpTransports(
- Arrays.asList(mTransport1, mTransport2), mTransport1.name);
+ setUpPackage(PACKAGE_A, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
+ setUpTransports(mTransportA1, mTransportA2);
+ TransportManager transportManager = createTransportManager(mTransportA1, mTransportA2);
+ transportManager.registerTransports();
transportManager.updateTransportAttributes(
- mTransport1.componentName, "newName", null, "destinationString", null, null);
+ mTransportA1.getTransportComponent(),
+ "newName",
+ null,
+ "destinationString",
+ null,
+ null);
- TransportClient transportClient =
- transportManager.getTransportClient("newName", "caller");
+ TransportClient transportClient = transportManager.getTransportClient("newName", "caller");
- assertThat(transportClient.getTransportComponent()).isEqualTo(mTransport1.componentName);
+ assertThat(transportClient.getTransportComponent())
+ .isEqualTo(mTransportA1.getTransportComponent());
}
@Test
- public void getTransportName_forTransportThatChangedName_returnsNewName()
- throws Exception {
- TransportManager transportManager =
- createTransportManagerAndSetUpTransports(
- Arrays.asList(mTransport1, mTransport2), mTransport1.name);
+ public void testGetTransportName_forTransportThatChangedName_returnsNewName() throws Exception {
+ setUpPackage(PACKAGE_A, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
+ setUpTransports(mTransportA1, mTransportA2);
+ TransportManager transportManager = createTransportManager(mTransportA1, mTransportA2);
+ transportManager.registerTransports();
transportManager.updateTransportAttributes(
- mTransport1.componentName, "newName", null, "destinationString", null, null);
+ mTransportA1.getTransportComponent(),
+ "newName",
+ null,
+ "destinationString",
+ null,
+ null);
- String transportName = transportManager.getTransportName(mTransport1.componentName);
+ String transportName =
+ transportManager.getTransportName(mTransportA1.getTransportComponent());
assertThat(transportName).isEqualTo("newName");
}
@Test
- public void isTransportRegistered_returnsCorrectly() throws Exception {
- TransportManager transportManager =
- createTransportManagerAndSetUpTransports(
- Collections.singletonList(mTransport1),
- Collections.singletonList(mTransport2),
- mTransport1.name);
+ public void testIsTransportRegistered() throws Exception {
+ setUpPackage(PACKAGE_A, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
+ setUpTransports(mTransportA1);
+ TransportManager transportManager = createTransportManager(mTransportA1, mTransportA2);
+ transportManager.registerTransports();
- assertThat(transportManager.isTransportRegistered(mTransport1.name)).isTrue();
- assertThat(transportManager.isTransportRegistered(mTransport2.name)).isFalse();
+ boolean isTransportA1Registered =
+ transportManager.isTransportRegistered(mTransportA1.transportName);
+ boolean isTransportA2Registered =
+ transportManager.isTransportRegistered(mTransportA2.transportName);
+
+ assertThat(isTransportA1Registered).isTrue();
+ assertThat(isTransportA2Registered).isFalse();
}
@Test
- public void getTransportAttributes_forRegisteredTransport_returnsCorrectValues()
+ public void testGetTransportAttributes_forRegisteredTransport_returnsCorrectValues()
throws Exception {
- TransportManager transportManager =
- createTransportManagerAndSetUpTransports(
- Collections.singletonList(mTransport1),
- mTransport1.name);
+ setUpPackage(PACKAGE_A, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
+ setUpTransports(mTransportA1);
+ TransportManager transportManager = createTransportManager(mTransportA1);
+ transportManager.registerTransports();
- assertThat(transportManager.getTransportConfigurationIntent(mTransport1.name))
- .isEqualTo(mTransport1.binderInterface.configurationIntent());
- assertThat(transportManager.getTransportDataManagementIntent(mTransport1.name))
- .isEqualTo(mTransport1.binderInterface.dataManagementIntent());
- assertThat(transportManager.getTransportDataManagementLabel(mTransport1.name))
- .isEqualTo(mTransport1.binderInterface.dataManagementLabel());
- assertThat(transportManager.getTransportDirName(mTransport1.name))
- .isEqualTo(mTransport1.binderInterface.transportDirName());
+ Intent configurationIntent =
+ transportManager.getTransportConfigurationIntent(mTransportA1.transportName);
+ Intent dataManagementIntent =
+ transportManager.getTransportDataManagementIntent(mTransportA1.transportName);
+ String dataManagementLabel =
+ transportManager.getTransportDataManagementLabel(mTransportA1.transportName);
+ String transportDirName = transportManager.getTransportDirName(mTransportA1.transportName);
+
+ assertThat(configurationIntent).isEqualTo(mTransportA1.configurationIntent);
+ assertThat(dataManagementIntent).isEqualTo(mTransportA1.dataManagementIntent);
+ assertThat(dataManagementLabel).isEqualTo(mTransportA1.dataManagementLabel);
+ assertThat(transportDirName).isEqualTo(mTransportA1.transportDirName);
}
@Test
- public void getTransportAttributes_forUnregisteredTransport_throws()
- throws Exception {
- TransportManager transportManager =
- createTransportManagerAndSetUpTransports(
- Collections.singletonList(mTransport1),
- Collections.singletonList(mTransport2),
- mTransport1.name);
+ public void testGetTransportAttributes_forUnregisteredTransport_throws() throws Exception {
+ setUpPackage(PACKAGE_A, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
+ setUpTransports(mTransportA1);
+ TransportManager transportManager = createTransportManager(mTransportA1);
+ transportManager.registerTransports();
expectThrows(
TransportNotRegisteredException.class,
- () -> transportManager.getTransportConfigurationIntent(mTransport2.name));
+ () -> transportManager.getTransportConfigurationIntent(mTransportA2.transportName));
expectThrows(
TransportNotRegisteredException.class,
- () -> transportManager.getTransportDataManagementIntent(
- mTransport2.name));
+ () ->
+ transportManager.getTransportDataManagementIntent(
+ mTransportA2.transportName));
expectThrows(
TransportNotRegisteredException.class,
- () -> transportManager.getTransportDataManagementLabel(mTransport2.name));
+ () -> transportManager.getTransportDataManagementLabel(mTransportA2.transportName));
expectThrows(
TransportNotRegisteredException.class,
- () -> transportManager.getTransportDirName(mTransport2.name));
+ () -> transportManager.getTransportDirName(mTransportA2.transportName));
}
- private void setUpPackageWithTransports(String packageName, List<TransportInfo> transports,
- int flags) throws Exception {
+ private List<TransportMock> setUpTransports(TransportData... transports) throws Exception {
+ setUpTransportsForTransportManager(mShadowPackageManager, transports);
+ List<TransportMock> transportMocks = new ArrayList<>(transports.length);
+ for (TransportData transport : transports) {
+ TransportMock transportMock = mockTransport(transport);
+ when(mTransportClientManager.getTransportClient(
+ eq(transport.getTransportComponent()), any()))
+ .thenReturn(transportMock.transportClient);
+ transportMocks.add(transportMock);
+ }
+ return transportMocks;
+ }
+
+ private void setUpPackage(String packageName, int flags) {
PackageInfo packageInfo = new PackageInfo();
packageInfo.packageName = packageName;
packageInfo.applicationInfo = new ApplicationInfo();
packageInfo.applicationInfo.privateFlags = flags;
-
- mPackageManagerShadow.addPackage(packageInfo);
-
- List<ResolveInfo> transportsInfo = new ArrayList<>();
- for (TransportInfo transport : transports) {
- ResolveInfo info = new ResolveInfo();
- info.serviceInfo = new ServiceInfo();
- info.serviceInfo.packageName = packageName;
- info.serviceInfo.name = transport.name;
- transportsInfo.add(info);
- }
-
- Intent intent = new Intent(TransportManager.SERVICE_ACTION_TRANSPORT_HOST);
- intent.setPackage(packageName);
-
- mPackageManagerShadow.addResolveInfoForIntent(intent, transportsInfo);
+ mShadowPackageManager.addPackage(packageInfo);
}
- private TransportManager createTransportManagerAndSetUpTransports(
- List<TransportInfo> availableTransports, String defaultTransportName) throws Exception {
- return createTransportManagerAndSetUpTransports(availableTransports,
- Collections.<TransportInfo>emptyList(), defaultTransportName);
- }
-
- private TransportManager createTransportManagerAndSetUpTransports(
- List<TransportInfo> availableTransports, List<TransportInfo> unavailableTransports,
- String defaultTransportName)
- throws Exception {
- List<String> availableTransportsNames = new ArrayList<>();
- List<ComponentName> availableTransportsComponentNames = new ArrayList<>();
- for (TransportInfo transport : availableTransports) {
- availableTransportsNames.add(transport.name);
- availableTransportsComponentNames.add(transport.componentName);
- }
-
- List<ComponentName> allTransportsComponentNames = new ArrayList<>();
- allTransportsComponentNames.addAll(availableTransportsComponentNames);
- for (TransportInfo transport : unavailableTransports) {
- allTransportsComponentNames.add(transport.componentName);
- }
-
- for (TransportInfo transport : unavailableTransports) {
- ShadowContextImplForBackup.sUnbindableComponents.add(transport.componentName);
- }
-
- setUpPackageWithTransports(PACKAGE_NAME, Arrays.asList(mTransport1, mTransport2),
- ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
-
- TransportManager transportManager = new TransportManager(
- RuntimeEnvironment.application.getApplicationContext(),
- new HashSet<>(allTransportsComponentNames),
- defaultTransportName,
- mTransportBoundListenerStub,
- ShadowLooper.getMainLooper());
- transportManager.onPackageAdded(PACKAGE_NAME);
-
- assertThat(transportManager.getAllTransportComponents()).asList().containsExactlyElementsIn(
- availableTransportsComponentNames);
- assertThat(transportManager.getBoundTransportNames()).asList().containsExactlyElementsIn(
- availableTransportsNames);
- for (TransportInfo transport : availableTransports) {
- assertThat(mTransportBoundListenerStub.isCalledForTransport(transport.binderInterface))
- .isTrue();
- }
- for (TransportInfo transport : unavailableTransports) {
- assertThat(mTransportBoundListenerStub.isCalledForTransport(transport.binderInterface))
- .isFalse();
- }
-
- mTransportBoundListenerStub.resetState();
-
+ private TransportManager createTransportManager(
+ @Nullable TransportData selectedTransport, TransportData... transports) {
+ Set<ComponentName> whitelist =
+ concat(Stream.of(selectedTransport), Stream.of(transports))
+ .filter(Objects::nonNull)
+ .map(TransportData::getTransportComponent)
+ .collect(toSet());
+ TransportManager transportManager =
+ new TransportManager(
+ mContext,
+ whitelist,
+ selectedTransport != null ? selectedTransport.transportName : null,
+ mTransportClientManager);
+ transportManager.setOnTransportRegisteredListener(mListener);
return transportManager;
}
- private static class TransportInfo {
- public final String packageName;
- public final String name;
- public final ComponentName componentName;
- public final IBackupTransport binderInterface;
- public final IBinder binder;
-
- TransportInfo(
- String packageName,
- String name,
- @Nullable Intent configurationIntent,
- String currentDestinationString,
- @Nullable Intent dataManagementIntent,
- String dataManagementLabel) {
- this.packageName = packageName;
- this.name = name;
- this.componentName = new ComponentName(packageName, name);
- this.binder = mock(IBinder.class);
- IBackupTransport transport = mock(IBackupTransport.class);
- try {
- when(transport.name()).thenReturn(name);
- when(transport.configurationIntent()).thenReturn(configurationIntent);
- when(transport.currentDestinationString()).thenReturn(currentDestinationString);
- when(transport.dataManagementIntent()).thenReturn(dataManagementIntent);
- when(transport.dataManagementLabel()).thenReturn(dataManagementLabel);
- } catch (RemoteException e) {
- // Only here to mock methods that throw RemoteException
- }
- this.binderInterface = transport;
- }
+ private void assertRegisteredTransports(
+ TransportManager transportManager, List<TransportData> transports) {
+ assertThat(transportManager.getRegisteredTransportComponents())
+ .asList()
+ .containsExactlyElementsIn(
+ transports
+ .stream()
+ .map(TransportData::getTransportComponent)
+ .collect(toList()));
+ assertThat(transportManager.getRegisteredTransportNames())
+ .asList()
+ .containsExactlyElementsIn(
+ transports.stream().map(t -> t.transportName).collect(toList()));
}
-
}
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 dfca901..ace0441 100644
--- a/services/robotests/src/com/android/server/backup/internal/PerformInitializeTaskTest.java
+++ b/services/robotests/src/com/android/server/backup/internal/PerformInitializeTaskTest.java
@@ -19,8 +19,10 @@
import static android.app.backup.BackupTransport.TRANSPORT_ERROR;
import static android.app.backup.BackupTransport.TRANSPORT_OK;
-import static com.android.server.backup.testing.TransportTestUtils.TRANSPORT_NAME;
-import static com.android.server.backup.testing.TransportTestUtils.TRANSPORT_NAMES;
+import static com.android.server.backup.testing.TransportData.backupTransport;
+import static com.android.server.backup.testing.TransportData.d2dTransport;
+import static com.android.server.backup.testing.TransportData.localTransport;
+import static com.android.server.backup.testing.TransportTestUtils.setUpTransports;
import static com.google.common.truth.Truth.assertThat;
@@ -28,7 +30,6 @@
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -44,7 +45,8 @@
import com.android.server.backup.RefactoredBackupManagerService;
import com.android.server.backup.TransportManager;
import com.android.server.backup.testing.TransportTestUtils;
-import com.android.server.backup.testing.TransportTestUtils.TransportData;
+import com.android.server.backup.testing.TransportData;
+import com.android.server.backup.testing.TransportTestUtils.TransportMock;
import com.android.server.backup.transport.TransportClient;
import com.android.server.testing.FrameworkRobolectricTestRunner;
import com.android.server.testing.SystemLoaderClasses;
@@ -58,7 +60,10 @@
import org.robolectric.annotation.Config;
import java.io.File;
+import java.util.Arrays;
+import java.util.Iterator;
import java.util.List;
+import java.util.stream.Stream;
@RunWith(FrameworkRobolectricTestRunner.class)
@Config(manifest = Config.NONE, sdk = 26)
@@ -68,16 +73,21 @@
@Mock private RefactoredBackupManagerService mBackupManagerService;
@Mock private TransportManager mTransportManager;
@Mock private OnTaskFinishedListener mListener;
- @Mock private IBackupTransport mTransport;
+ @Mock private IBackupTransport mTransportBinder;
@Mock private IBackupObserver mObserver;
@Mock private AlarmManager mAlarmManager;
@Mock private PendingIntent mRunInitIntent;
private File mBaseStateDir;
+ private TransportData mTransport;
+ private String mTransportName;
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
+ mTransport = backupTransport();
+ mTransportName = mTransport.transportName;
+
Application context = RuntimeEnvironment.application;
mBaseStateDir = new File(context.getCacheDir(), "base_state_dir");
assertThat(mBaseStateDir.mkdir()).isTrue();
@@ -88,82 +98,76 @@
@Test
public void testRun_callsTransportCorrectly() throws Exception {
- setUpTransport(TRANSPORT_NAME);
- configureTransport(mTransport, TRANSPORT_OK, TRANSPORT_OK);
- PerformInitializeTask performInitializeTask = createPerformInitializeTask(TRANSPORT_NAME);
+ setUpTransport(mTransport);
+ configureTransport(mTransportBinder, TRANSPORT_OK, TRANSPORT_OK);
+ PerformInitializeTask performInitializeTask = createPerformInitializeTask(mTransportName);
performInitializeTask.run();
- verify(mTransport).initializeDevice();
- verify(mTransport).finishBackup();
+ verify(mTransportBinder).initializeDevice();
+ verify(mTransportBinder).finishBackup();
}
@Test
public void testRun_callsBackupManagerCorrectly() throws Exception {
- setUpTransport(TRANSPORT_NAME);
- configureTransport(mTransport, TRANSPORT_OK, TRANSPORT_OK);
- PerformInitializeTask performInitializeTask = createPerformInitializeTask(TRANSPORT_NAME);
+ setUpTransport(mTransport);
+ configureTransport(mTransportBinder, TRANSPORT_OK, TRANSPORT_OK);
+ PerformInitializeTask performInitializeTask = createPerformInitializeTask(mTransportName);
performInitializeTask.run();
verify(mBackupManagerService)
- .recordInitPending(
- false, TRANSPORT_NAME, TransportTestUtils.transportDirName(TRANSPORT_NAME));
+ .recordInitPending(false, mTransportName, mTransport.transportDirName);
verify(mBackupManagerService)
- .resetBackupState(
- eq(
- new File(
- mBaseStateDir,
- TransportTestUtils.transportDirName(TRANSPORT_NAME))));
+ .resetBackupState(eq(new File(mBaseStateDir, mTransport.transportDirName)));
}
@Test
public void testRun_callsObserverAndListenerCorrectly() throws Exception {
- setUpTransport(TRANSPORT_NAME);
- configureTransport(mTransport, TRANSPORT_OK, TRANSPORT_OK);
- PerformInitializeTask performInitializeTask = createPerformInitializeTask(TRANSPORT_NAME);
+ setUpTransport(mTransport);
+ configureTransport(mTransportBinder, TRANSPORT_OK, TRANSPORT_OK);
+ PerformInitializeTask performInitializeTask = createPerformInitializeTask(mTransportName);
performInitializeTask.run();
- verify(mObserver).onResult(eq(TRANSPORT_NAME), eq(TRANSPORT_OK));
+ verify(mObserver).onResult(eq(mTransportName), eq(TRANSPORT_OK));
verify(mObserver).backupFinished(eq(TRANSPORT_OK));
verify(mListener).onFinished(any());
}
@Test
public void testRun_whenInitializeDeviceFails() throws Exception {
- setUpTransport(TRANSPORT_NAME);
- configureTransport(mTransport, TRANSPORT_ERROR, 0);
- PerformInitializeTask performInitializeTask = createPerformInitializeTask(TRANSPORT_NAME);
+ setUpTransport(mTransport);
+ configureTransport(mTransportBinder, TRANSPORT_ERROR, 0);
+ PerformInitializeTask performInitializeTask = createPerformInitializeTask(mTransportName);
performInitializeTask.run();
- verify(mTransport).initializeDevice();
- verify(mTransport, never()).finishBackup();
+ verify(mTransportBinder).initializeDevice();
+ verify(mTransportBinder, never()).finishBackup();
verify(mBackupManagerService)
- .recordInitPending(
- true, TRANSPORT_NAME, TransportTestUtils.transportDirName(TRANSPORT_NAME));
+ .recordInitPending(true, mTransportName, mTransport.transportDirName);
}
@Test
public void testRun_whenInitializeDeviceFails_callsObserverAndListenerCorrectly()
throws Exception {
- setUpTransport(TRANSPORT_NAME);
- configureTransport(mTransport, TRANSPORT_ERROR, 0);
- PerformInitializeTask performInitializeTask = createPerformInitializeTask(TRANSPORT_NAME);
+ setUpTransport(mTransport);
+ configureTransport(mTransportBinder, TRANSPORT_ERROR, 0);
+ PerformInitializeTask performInitializeTask = createPerformInitializeTask(mTransportName);
performInitializeTask.run();
- verify(mObserver).onResult(eq(TRANSPORT_NAME), eq(TRANSPORT_ERROR));
+ verify(mObserver).onResult(eq(mTransportName), eq(TRANSPORT_ERROR));
verify(mObserver).backupFinished(eq(TRANSPORT_ERROR));
verify(mListener).onFinished(any());
}
@Test
public void testRun_whenInitializeDeviceFails_schedulesAlarm() throws Exception {
- setUpTransport(TRANSPORT_NAME);
- configureTransport(mTransport, TRANSPORT_ERROR, 0);
- PerformInitializeTask performInitializeTask = createPerformInitializeTask(TRANSPORT_NAME);
+ setUpTransport(mTransport);
+ configureTransport(mTransportBinder, TRANSPORT_ERROR, 0);
+ PerformInitializeTask performInitializeTask = createPerformInitializeTask(mTransportName);
performInitializeTask.run();
@@ -172,37 +176,36 @@
@Test
public void testRun_whenFinishBackupFails() throws Exception {
- setUpTransport(TRANSPORT_NAME);
- configureTransport(mTransport, TRANSPORT_OK, TRANSPORT_ERROR);
- PerformInitializeTask performInitializeTask = createPerformInitializeTask(TRANSPORT_NAME);
+ setUpTransport(mTransport);
+ configureTransport(mTransportBinder, TRANSPORT_OK, TRANSPORT_ERROR);
+ PerformInitializeTask performInitializeTask = createPerformInitializeTask(mTransportName);
performInitializeTask.run();
- verify(mTransport).initializeDevice();
- verify(mTransport).finishBackup();
+ verify(mTransportBinder).initializeDevice();
+ verify(mTransportBinder).finishBackup();
verify(mBackupManagerService)
- .recordInitPending(
- true, TRANSPORT_NAME, TransportTestUtils.transportDirName(TRANSPORT_NAME));
+ .recordInitPending(true, mTransportName, mTransport.transportDirName);
}
@Test
public void testRun_whenFinishBackupFails_callsObserverAndListenerCorrectly() throws Exception {
- setUpTransport(TRANSPORT_NAME);
- configureTransport(mTransport, TRANSPORT_OK, TRANSPORT_ERROR);
- PerformInitializeTask performInitializeTask = createPerformInitializeTask(TRANSPORT_NAME);
+ setUpTransport(mTransport);
+ configureTransport(mTransportBinder, TRANSPORT_OK, TRANSPORT_ERROR);
+ PerformInitializeTask performInitializeTask = createPerformInitializeTask(mTransportName);
performInitializeTask.run();
- verify(mObserver).onResult(eq(TRANSPORT_NAME), eq(TRANSPORT_ERROR));
+ verify(mObserver).onResult(eq(mTransportName), eq(TRANSPORT_ERROR));
verify(mObserver).backupFinished(eq(TRANSPORT_ERROR));
verify(mListener).onFinished(any());
}
@Test
public void testRun_whenFinishBackupFails_schedulesAlarm() throws Exception {
- setUpTransport(TRANSPORT_NAME);
- configureTransport(mTransport, TRANSPORT_OK, TRANSPORT_ERROR);
- PerformInitializeTask performInitializeTask = createPerformInitializeTask(TRANSPORT_NAME);
+ setUpTransport(mTransport);
+ configureTransport(mTransportBinder, TRANSPORT_OK, TRANSPORT_ERROR);
+ PerformInitializeTask performInitializeTask = createPerformInitializeTask(mTransportName);
performInitializeTask.run();
@@ -211,64 +214,76 @@
@Test
public void testRun_whenOnlyOneTransportFails() throws Exception {
- List<TransportData> transports =
- TransportTestUtils.setUpTransports(
- mTransportManager, TRANSPORT_NAMES[0], TRANSPORT_NAMES[1]);
- configureTransport(transports.get(0).transportMock, TRANSPORT_ERROR, 0);
- configureTransport(transports.get(1).transportMock, TRANSPORT_OK, TRANSPORT_OK);
+ TransportData transport1 = backupTransport();
+ TransportData transport2 = d2dTransport();
+ List<TransportMock> transportMocks =
+ setUpTransports(mTransportManager, transport1, transport2);
+ configureTransport(transportMocks.get(0).transport, TRANSPORT_ERROR, 0);
+ configureTransport(transportMocks.get(1).transport, TRANSPORT_OK, TRANSPORT_OK);
PerformInitializeTask performInitializeTask =
- createPerformInitializeTask(TRANSPORT_NAMES[0], TRANSPORT_NAMES[1]);
+ createPerformInitializeTask(transport1.transportName, transport2.transportName);
performInitializeTask.run();
- verify(transports.get(1).transportMock).initializeDevice();
- verify(mObserver).onResult(eq(TRANSPORT_NAMES[0]), eq(TRANSPORT_ERROR));
- verify(mObserver).onResult(eq(TRANSPORT_NAMES[1]), eq(TRANSPORT_OK));
+ verify(transportMocks.get(1).transport).initializeDevice();
+ verify(mObserver).onResult(eq(transport1.transportName), eq(TRANSPORT_ERROR));
+ verify(mObserver).onResult(eq(transport2.transportName), eq(TRANSPORT_OK));
verify(mObserver).backupFinished(eq(TRANSPORT_ERROR));
}
@Test
public void testRun_withMultipleTransports() throws Exception {
- List<TransportData> transports =
- TransportTestUtils.setUpTransports(mTransportManager, TRANSPORT_NAMES);
- configureTransport(transports.get(0).transportMock, TRANSPORT_OK, TRANSPORT_OK);
- configureTransport(transports.get(1).transportMock, TRANSPORT_OK, TRANSPORT_OK);
- configureTransport(transports.get(2).transportMock, TRANSPORT_OK, TRANSPORT_OK);
- PerformInitializeTask performInitializeTask = createPerformInitializeTask(TRANSPORT_NAMES);
+ List<TransportMock> transportMocks =
+ setUpTransports(
+ mTransportManager, backupTransport(), d2dTransport(), localTransport());
+ configureTransport(transportMocks.get(0).transport, TRANSPORT_OK, TRANSPORT_OK);
+ configureTransport(transportMocks.get(1).transport, TRANSPORT_OK, TRANSPORT_OK);
+ configureTransport(transportMocks.get(2).transport, TRANSPORT_OK, TRANSPORT_OK);
+ String[] transportNames =
+ Stream.of(new TransportData[] {backupTransport(), d2dTransport(), localTransport()})
+ .map(t -> t.transportName)
+ .toArray(String[]::new);
+ PerformInitializeTask performInitializeTask = createPerformInitializeTask(transportNames);
performInitializeTask.run();
- for (TransportData transport : transports) {
+ Iterator<TransportData> transportsIterator =
+ Arrays.asList(
+ new TransportData[] {
+ backupTransport(), d2dTransport(), localTransport()
+ })
+ .iterator();
+ for (TransportMock transportMock : transportMocks) {
+ TransportData transport = transportsIterator.next();
verify(mTransportManager).getTransportClient(eq(transport.transportName), any());
verify(mTransportManager)
- .disposeOfTransportClient(eq(transport.transportClientMock), any());
+ .disposeOfTransportClient(eq(transportMock.transportClient), any());
}
}
@Test
public void testRun_whenOnlyOneTransportFails_disposesAllTransports() throws Exception {
- List<TransportData> transports =
- TransportTestUtils.setUpTransports(
- mTransportManager, TRANSPORT_NAMES[0], TRANSPORT_NAMES[1]);
- configureTransport(transports.get(0).transportMock, TRANSPORT_ERROR, 0);
- configureTransport(transports.get(1).transportMock, TRANSPORT_OK, TRANSPORT_OK);
+ TransportData transport1 = backupTransport();
+ TransportData transport2 = d2dTransport();
+ List<TransportMock> transportMocks =
+ setUpTransports(mTransportManager, transport1, transport2);
+ configureTransport(transportMocks.get(0).transport, TRANSPORT_ERROR, 0);
+ configureTransport(transportMocks.get(1).transport, TRANSPORT_OK, TRANSPORT_OK);
PerformInitializeTask performInitializeTask =
- createPerformInitializeTask(TRANSPORT_NAMES[0], TRANSPORT_NAMES[1]);
+ createPerformInitializeTask(transport1.transportName, transport2.transportName);
performInitializeTask.run();
verify(mTransportManager)
- .disposeOfTransportClient(eq(transports.get(0).transportClientMock), any());
+ .disposeOfTransportClient(eq(transportMocks.get(0).transportClient), any());
verify(mTransportManager)
- .disposeOfTransportClient(eq(transports.get(1).transportClientMock), any());
+ .disposeOfTransportClient(eq(transportMocks.get(1).transportClient), any());
}
@Test
public void testRun_whenTransportNotRegistered() throws Exception {
- TransportTestUtils.setUpTransports(
- mTransportManager, new TransportData(TRANSPORT_NAME, null, null));
-
- PerformInitializeTask performInitializeTask = createPerformInitializeTask(TRANSPORT_NAME);
+ setUpTransports(mTransportManager, mTransport.unregistered());
+ PerformInitializeTask performInitializeTask = createPerformInitializeTask(mTransportName);
performInitializeTask.run();
@@ -279,16 +294,15 @@
@Test
public void testRun_whenOnlyOneTransportNotRegistered() throws Exception {
- List<TransportData> transports =
- TransportTestUtils.setUpTransports(
- mTransportManager,
- new TransportData(TRANSPORT_NAMES[0], null, null),
- new TransportData(TRANSPORT_NAMES[1]));
- String registeredTransportName = transports.get(1).transportName;
- IBackupTransport registeredTransport = transports.get(1).transportMock;
- TransportClient registeredTransportClient = transports.get(1).transportClientMock;
+ TransportData transport1 = backupTransport().unregistered();
+ TransportData transport2 = d2dTransport();
+ List<TransportMock> transportMocks =
+ setUpTransports(mTransportManager, transport1, transport2);
+ String registeredTransportName = transport2.transportName;
+ IBackupTransport registeredTransport = transportMocks.get(1).transport;
+ TransportClient registeredTransportClient = transportMocks.get(1).transportClient;
PerformInitializeTask performInitializeTask =
- createPerformInitializeTask(TRANSPORT_NAMES[0], TRANSPORT_NAMES[1]);
+ createPerformInitializeTask(transport1.transportName, transport2.transportName);
performInitializeTask.run();
@@ -299,25 +313,24 @@
@Test
public void testRun_whenTransportNotAvailable() throws Exception {
- TransportClient transportClient = mock(TransportClient.class);
- TransportTestUtils.setUpTransports(
- mTransportManager, new TransportData(TRANSPORT_NAME, null, transportClient));
- PerformInitializeTask performInitializeTask = createPerformInitializeTask(TRANSPORT_NAME);
+ TransportMock transportMock = setUpTransport(mTransport.unavailable());
+ PerformInitializeTask performInitializeTask = createPerformInitializeTask(mTransportName);
performInitializeTask.run();
- verify(mTransportManager).disposeOfTransportClient(eq(transportClient), any());
+ verify(mTransportManager)
+ .disposeOfTransportClient(eq(transportMock.transportClient), any());
verify(mObserver).backupFinished(eq(TRANSPORT_ERROR));
verify(mListener).onFinished(any());
}
@Test
public void testRun_whenTransportThrowsDeadObjectException() throws Exception {
- TransportClient transportClient = mock(TransportClient.class);
- TransportTestUtils.setUpTransports(
- mTransportManager, new TransportData(TRANSPORT_NAME, mTransport, transportClient));
- when(mTransport.initializeDevice()).thenThrow(DeadObjectException.class);
- PerformInitializeTask performInitializeTask = createPerformInitializeTask(TRANSPORT_NAME);
+ TransportMock transportMock = setUpTransport(mTransport);
+ IBackupTransport transport = transportMock.transport;
+ TransportClient transportClient = transportMock.transportClient;
+ when(transport.initializeDevice()).thenThrow(DeadObjectException.class);
+ PerformInitializeTask performInitializeTask = createPerformInitializeTask(mTransportName);
performInitializeTask.run();
@@ -343,9 +356,10 @@
when(transportMock.finishBackup()).thenReturn(finishBackupStatus);
}
- private void setUpTransport(String transportName) throws Exception {
- TransportTestUtils.setUpTransport(
- mTransportManager,
- new TransportData(transportName, mTransport, mock(TransportClient.class)));
+ private TransportMock setUpTransport(TransportData transport) throws Exception {
+ TransportMock transportMock =
+ TransportTestUtils.setUpTransport(mTransportManager, transport);
+ mTransportBinder = transportMock.transport;
+ return transportMock;
}
}
diff --git a/services/robotests/src/com/android/server/backup/testing/TestUtils.java b/services/robotests/src/com/android/server/backup/testing/TestUtils.java
new file mode 100644
index 0000000..1be298d
--- /dev/null
+++ b/services/robotests/src/com/android/server/backup/testing/TestUtils.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.backup.testing;
+
+import com.android.internal.util.FunctionalUtils.ThrowingRunnable;
+
+import java.util.concurrent.Callable;
+
+public class TestUtils {
+ /**
+ * Calls {@link Runnable#run()} and returns if no exception is thrown. Otherwise, if the
+ * exception is unchecked, rethrow it; if it's checked wrap in a {@link RuntimeException} and
+ * throw.
+ *
+ * <p><b>Warning:</b>DON'T use this outside tests. A wrapped checked exception is just a failure
+ * in a test.
+ */
+ public static void uncheck(ThrowingRunnable runnable) {
+ try {
+ runnable.runOrThrow();
+ } catch (Exception e) {
+ throw wrapIfChecked(e);
+ }
+ }
+
+ /**
+ * Calls {@link Callable#call()} and returns the value if no exception is thrown. Otherwise, if
+ * the exception is unchecked, rethrow it; if it's checked wrap in a {@link RuntimeException}
+ * and throw.
+ *
+ * <p><b>Warning:</b>DON'T use this outside tests. A wrapped checked exception is just a failure
+ * in a test.
+ */
+ public static <T> T uncheck(Callable<T> callable) {
+ try {
+ return callable.call();
+ } catch (Exception e) {
+ throw wrapIfChecked(e);
+ }
+ }
+
+ /**
+ * Wrap {@code e} in a {@link RuntimeException} only if it's not one already, in which case it's
+ * returned.
+ */
+ public static RuntimeException wrapIfChecked(Exception e) {
+ if (e instanceof RuntimeException) {
+ return (RuntimeException) e;
+ }
+ return new RuntimeException(e);
+ }
+
+ private TestUtils() {}
+}
diff --git a/services/robotests/src/com/android/server/backup/testing/TransportBoundListenerStub.java b/services/robotests/src/com/android/server/backup/testing/TransportBoundListenerStub.java
deleted file mode 100644
index 84ac2c2..0000000
--- a/services/robotests/src/com/android/server/backup/testing/TransportBoundListenerStub.java
+++ /dev/null
@@ -1,64 +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.server.backup.testing;
-
-import com.android.internal.backup.IBackupTransport;
-import com.android.server.backup.TransportManager;
-
-import java.util.HashSet;
-import java.util.Set;
-
-/**
- * Stub implementation of TransportBoundListener, which returns given result and can tell whether
- * it was called for given transport.
- */
-public class TransportBoundListenerStub implements
- TransportManager.TransportBoundListener {
- private boolean mAlwaysReturnSuccess;
- private Set<IBackupTransport> mTransportsCalledFor = new HashSet<>();
-
- public TransportBoundListenerStub(boolean alwaysReturnSuccess) {
- this.mAlwaysReturnSuccess = alwaysReturnSuccess;
- }
-
- @Override
- public boolean onTransportBound(IBackupTransport binder) {
- mTransportsCalledFor.add(binder);
- return mAlwaysReturnSuccess;
- }
-
- /**
- * Returns whether the listener was called for the specified transport at least once.
- */
- public boolean isCalledForTransport(IBackupTransport binder) {
- return mTransportsCalledFor.contains(binder);
- }
-
- /**
- * Returns whether the listener was called at least once.
- */
- public boolean isCalled() {
- return !mTransportsCalledFor.isEmpty();
- }
-
- /**
- * Resets listener calls.
- */
- public void resetState() {
- mTransportsCalledFor.clear();
- }
-}
diff --git a/services/robotests/src/com/android/server/backup/testing/TransportData.java b/services/robotests/src/com/android/server/backup/testing/TransportData.java
new file mode 100644
index 0000000..9feaa8e
--- /dev/null
+++ b/services/robotests/src/com/android/server/backup/testing/TransportData.java
@@ -0,0 +1,149 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.backup.testing;
+
+import android.annotation.Nullable;
+import android.content.ComponentName;
+import android.content.Intent;
+
+public class TransportData {
+ // No constants since new Intent() can't be called in static context because of Robolectric
+ public static TransportData backupTransport() {
+ return new TransportData(
+ "com.google.android.gms/.backup.BackupTransportService",
+ "com.google.android.gms/.backup.BackupTransportService",
+ "com.google.android.gms.backup.BackupTransportService",
+ new Intent(),
+ "user@gmail.com",
+ new Intent(),
+ "Google Account");
+ }
+
+ public static TransportData d2dTransport() {
+ return new TransportData(
+ "com.google.android.gms/.backup.migrate.service.D2dTransport",
+ "com.google.android.gms/.backup.component.D2dTransportService",
+ "d2dMigrateTransport",
+ null,
+ "Moving data to new device",
+ null,
+ "");
+ }
+
+ public static TransportData localTransport() {
+ return new TransportData(
+ "android/com.android.internal.backup.LocalTransport",
+ "android/com.android.internal.backup.LocalTransportService",
+ "com.android.internal.backup.LocalTransport",
+ null,
+ "Backing up to debug-only private cache",
+ null,
+ "");
+ }
+
+ public static TransportData genericTransport(String packageName, String className) {
+ return new TransportData(
+ packageName + "/." + className,
+ packageName + "/." + className + "Service",
+ packageName + "." + className,
+ new Intent(),
+ "currentDestinationString",
+ new Intent(),
+ "dataManagementLabel");
+ }
+
+ @TransportTestUtils.TransportStatus
+ public int transportStatus;
+ public final String transportName;
+ private final String transportComponentShort;
+ @Nullable
+ public String transportDirName;
+ @Nullable public Intent configurationIntent;
+ @Nullable public String currentDestinationString;
+ @Nullable public Intent dataManagementIntent;
+ @Nullable public String dataManagementLabel;
+
+ private TransportData(
+ @TransportTestUtils.TransportStatus int transportStatus,
+ String transportName,
+ String transportComponentShort,
+ String transportDirName,
+ Intent configurationIntent,
+ String currentDestinationString,
+ Intent dataManagementIntent,
+ String dataManagementLabel) {
+ this.transportStatus = transportStatus;
+ this.transportName = transportName;
+ this.transportComponentShort = transportComponentShort;
+ this.transportDirName = transportDirName;
+ this.configurationIntent = configurationIntent;
+ this.currentDestinationString = currentDestinationString;
+ this.dataManagementIntent = dataManagementIntent;
+ this.dataManagementLabel = dataManagementLabel;
+ }
+
+ public TransportData(
+ String transportName,
+ String transportComponentShort,
+ String transportDirName,
+ Intent configurationIntent,
+ String currentDestinationString,
+ Intent dataManagementIntent,
+ String dataManagementLabel) {
+ this(
+ TransportTestUtils.TransportStatus.REGISTERED_AVAILABLE,
+ transportName,
+ transportComponentShort,
+ transportDirName,
+ configurationIntent,
+ currentDestinationString,
+ dataManagementIntent,
+ dataManagementLabel);
+ }
+
+ /**
+ * Not field because otherwise we'd have to call ComponentName::new in static context and
+ * Robolectric does not like this.
+ */
+ public ComponentName getTransportComponent() {
+ return ComponentName.unflattenFromString(transportComponentShort);
+ }
+
+ public TransportData unavailable() {
+ return new TransportData(
+ TransportTestUtils.TransportStatus.REGISTERED_UNAVAILABLE,
+ transportName,
+ transportComponentShort,
+ transportDirName,
+ configurationIntent,
+ currentDestinationString,
+ dataManagementIntent,
+ dataManagementLabel);
+ }
+
+ public TransportData unregistered() {
+ return new TransportData(
+ TransportTestUtils.TransportStatus.UNREGISTERED,
+ transportName,
+ transportComponentShort,
+ transportDirName,
+ configurationIntent,
+ currentDestinationString,
+ dataManagementIntent,
+ dataManagementLabel);
+ }
+}
diff --git a/services/robotests/src/com/android/server/backup/testing/TransportTestUtils.java b/services/robotests/src/com/android/server/backup/testing/TransportTestUtils.java
index 9770e40..e1dc7b5 100644
--- a/services/robotests/src/com/android/server/backup/testing/TransportTestUtils.java
+++ b/services/robotests/src/com/android/server/backup/testing/TransportTestUtils.java
@@ -16,13 +16,23 @@
package com.android.server.backup.testing;
+import static com.android.server.backup.testing.TestUtils.uncheck;
+
+import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
+import static java.util.stream.Collectors.toList;
+
import android.annotation.Nullable;
import android.content.ComponentName;
+import android.content.Intent;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
+import android.os.RemoteException;
+import android.support.annotation.IntDef;
import com.android.internal.backup.IBackupTransport;
import com.android.server.backup.TransportManager;
@@ -30,85 +40,82 @@
import com.android.server.backup.transport.TransportNotAvailableException;
import com.android.server.backup.transport.TransportNotRegisteredException;
-import java.util.Arrays;
+import org.robolectric.shadows.ShadowPackageManager;
+
import java.util.List;
+import java.util.stream.Stream;
public class TransportTestUtils {
- public static final String[] TRANSPORT_NAMES = {
- "android/com.android.internal.backup.LocalTransport",
- "com.google.android.gms/.backup.migrate.service.D2dTransport",
- "com.google.android.gms/.backup.BackupTransportService"
- };
+ /**
+ * Differently from {@link #setUpTransports(TransportManager, TransportData...)}, which
+ * configures {@link TransportManager}, this is meant to mock the environment for a real
+ * TransportManager.
+ */
+ public static void setUpTransportsForTransportManager(
+ ShadowPackageManager shadowPackageManager, TransportData... transports)
+ throws Exception {
+ for (TransportData transport : transports) {
+ ComponentName transportComponent = transport.getTransportComponent();
+ String packageName = transportComponent.getPackageName();
+ ResolveInfo resolveInfo = resolveInfo(transportComponent);
+ shadowPackageManager.addResolveInfoForIntent(transportIntent(), resolveInfo);
+ shadowPackageManager.addResolveInfoForIntent(
+ transportIntent().setPackage(packageName), resolveInfo);
+ }
+ }
- public static final String TRANSPORT_NAME = TRANSPORT_NAMES[0];
+ private static Intent transportIntent() {
+ return new Intent(TransportManager.SERVICE_ACTION_TRANSPORT_HOST);
+ }
- /** {@code transportName} has to be in the {@link ComponentName} format (with '/') */
- public static TransportData setUpCurrentTransport(
- TransportManager transportManager, String transportName) throws Exception {
- TransportData transport = setUpTransports(transportManager, transportName).get(0);
- when(transportManager.getCurrentTransportClient(any()))
- .thenReturn(transport.transportClientMock);
- return transport;
+ private static ResolveInfo resolveInfo(ComponentName transportComponent) {
+ ResolveInfo resolveInfo = new ResolveInfo();
+ resolveInfo.serviceInfo = new ServiceInfo();
+ resolveInfo.serviceInfo.packageName = transportComponent.getPackageName();
+ resolveInfo.serviceInfo.name = transportComponent.getClassName();
+ return resolveInfo;
}
/** {@code transportName} has to be in the {@link ComponentName} format (with '/') */
- public static List<TransportData> setUpTransports(
- TransportManager transportManager, String... transportNames) throws Exception {
- return setUpTransports(
- transportManager,
- Arrays.stream(transportNames)
- .map(TransportData::new)
- .toArray(TransportData[]::new));
+ public static TransportMock setUpCurrentTransport(
+ TransportManager transportManager, TransportData transport) throws Exception {
+ TransportMock transportMock = setUpTransports(transportManager, transport).get(0);
+ if (transportMock.transportClient != null) {
+ when(transportManager.getCurrentTransportClient(any()))
+ .thenReturn(transportMock.transportClient);
+ }
+ return transportMock;
}
/** @see #setUpTransport(TransportManager, TransportData) */
- public static List<TransportData> setUpTransports(
+ public static List<TransportMock> setUpTransports(
TransportManager transportManager, TransportData... transports) throws Exception {
- for (TransportData transport : transports) {
- setUpTransport(transportManager, transport);
- }
- return Arrays.asList(transports);
+ return Stream.of(transports)
+ .map(transport -> uncheck(() -> setUpTransport(transportManager, transport)))
+ .collect(toList());
}
- /**
- * Configures transport according to {@link TransportData}:
- *
- * <ul>
- * <li>{@link TransportData#transportMock} {@code null} means transport not available.
- * <li>{@link TransportData#transportClientMock} {@code null} means transport not registered.
- * </ul>
- */
- public static void setUpTransport(TransportManager transportManager, TransportData transport)
- throws Exception {
+ public static TransportMock setUpTransport(
+ TransportManager transportManager, TransportData transport) throws Exception {
+ int status = transport.transportStatus;
String transportName = transport.transportName;
- String transportDirName = transportDirName(transportName);
- ComponentName transportComponent = transportComponentName(transportName);
- IBackupTransport transportMock = transport.transportMock;
- TransportClient transportClientMock = transport.transportClientMock;
+ ComponentName transportComponent = transport.getTransportComponent();
+ String transportDirName = transport.transportDirName;
- if (transportClientMock != null) {
+ TransportMock transportMock = mockTransport(transport);
+ if (status == TransportStatus.REGISTERED_AVAILABLE
+ || status == TransportStatus.REGISTERED_UNAVAILABLE) {
// Transport registered
when(transportManager.getTransportClient(eq(transportName), any()))
- .thenReturn(transportClientMock);
+ .thenReturn(transportMock.transportClient);
when(transportManager.getTransportClientOrThrow(eq(transportName), any()))
- .thenReturn(transportClientMock);
+ .thenReturn(transportMock.transportClient);
when(transportManager.getTransportName(transportComponent)).thenReturn(transportName);
when(transportManager.getTransportDirName(eq(transportName)))
.thenReturn(transportDirName);
when(transportManager.getTransportDirName(eq(transportComponent)))
.thenReturn(transportDirName);
- when(transportClientMock.getTransportComponent()).thenReturn(transportComponent);
-
- if (transportMock != null) {
- // Transport registered and available
- when(transportClientMock.connectOrThrow(any())).thenReturn(transportMock);
- when(transportMock.name()).thenReturn(transportName);
- when(transportMock.transportDirName()).thenReturn(transportDirName);
- } else {
- // Transport registered but unavailable
- when(transportClientMock.connectOrThrow(any()))
- .thenThrow(TransportNotAvailableException.class);
- }
+ // TODO: Mock rest of description methods
} else {
// Transport not registered
when(transportManager.getTransportClient(eq(transportName), any())).thenReturn(null);
@@ -121,34 +128,73 @@
when(transportManager.getTransportDirName(eq(transportComponent)))
.thenThrow(TransportNotRegisteredException.class);
}
+ return transportMock;
}
- /** {@code transportName} has to be in the {@link ComponentName} format (with '/') */
- public static ComponentName transportComponentName(String transportName) {
- return ComponentName.unflattenFromString(transportName);
- }
+ public static TransportMock mockTransport(TransportData transport) throws Exception {
+ final TransportClient transportClientMock;
+ int status = transport.transportStatus;
+ ComponentName transportComponent = transport.getTransportComponent();
+ if (status == TransportStatus.REGISTERED_AVAILABLE
+ || status == TransportStatus.REGISTERED_UNAVAILABLE) {
+ // Transport registered
+ transportClientMock = mock(TransportClient.class);
+ when(transportClientMock.getTransportComponent()).thenReturn(transportComponent);
+ if (status == TransportStatus.REGISTERED_AVAILABLE) {
+ // Transport registered and available
+ IBackupTransport transportMock = mockTransportBinder(transport);
+ when(transportClientMock.connectOrThrow(any())).thenReturn(transportMock);
- public static String transportDirName(String transportName) {
- return transportName + "_dir_name";
- }
+ return new TransportMock(transportClientMock, transportMock);
+ } else {
+ // Transport registered but unavailable
+ when(transportClientMock.connectOrThrow(any()))
+ .thenThrow(TransportNotAvailableException.class);
- public static class TransportData {
- public final String transportName;
- @Nullable public final IBackupTransport transportMock;
- @Nullable public final TransportClient transportClientMock;
-
- public TransportData(
- String transportName,
- @Nullable IBackupTransport transportMock,
- @Nullable TransportClient transportClientMock) {
- this.transportName = transportName;
- this.transportMock = transportMock;
- this.transportClientMock = transportClientMock;
+ return new TransportMock(transportClientMock, null);
+ }
+ } else {
+ // Transport not registered
+ return new TransportMock(null, null);
}
+ }
- public TransportData(String transportName) {
- this(transportName, mock(IBackupTransport.class), mock(TransportClient.class));
+ private static IBackupTransport mockTransportBinder(TransportData transport) throws Exception {
+ IBackupTransport transportBinder = mock(IBackupTransport.class);
+ try {
+ when(transportBinder.name()).thenReturn(transport.transportName);
+ when(transportBinder.transportDirName()).thenReturn(transport.transportDirName);
+ when(transportBinder.configurationIntent()).thenReturn(transport.configurationIntent);
+ when(transportBinder.currentDestinationString())
+ .thenReturn(transport.currentDestinationString);
+ when(transportBinder.dataManagementIntent()).thenReturn(transport.dataManagementIntent);
+ when(transportBinder.dataManagementLabel()).thenReturn(transport.dataManagementLabel);
+ } catch (RemoteException e) {
+ fail("RemoteException?");
}
+ return transportBinder;
+ }
+
+ public static class TransportMock {
+ @Nullable public final TransportClient transportClient;
+ @Nullable public final IBackupTransport transport;
+
+ private TransportMock(
+ @Nullable TransportClient transportClient, @Nullable IBackupTransport transport) {
+ this.transportClient = transportClient;
+ this.transport = transport;
+ }
+ }
+
+ @IntDef({
+ TransportStatus.REGISTERED_AVAILABLE,
+ TransportStatus.REGISTERED_UNAVAILABLE,
+ TransportStatus.UNREGISTERED
+ })
+ public @interface TransportStatus {
+ int REGISTERED_AVAILABLE = 0;
+ int REGISTERED_UNAVAILABLE = 1;
+ int UNREGISTERED = 2;
}
private TransportTestUtils() {}
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 3bc0b30..9b4dec6 100644
--- a/services/robotests/src/com/android/server/backup/transport/TransportClientTest.java
+++ b/services/robotests/src/com/android/server/backup/transport/TransportClientTest.java
@@ -17,9 +17,7 @@
package com.android.server.backup.transport;
import static com.android.server.backup.TransportManager.SERVICE_ACTION_TRANSPORT_HOST;
-
import static com.google.common.truth.Truth.assertThat;
-
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
@@ -36,12 +34,10 @@
import android.os.Looper;
import android.os.UserHandle;
import android.platform.test.annotations.Presubmit;
-
import com.android.internal.backup.IBackupTransport;
import com.android.server.backup.TransportManager;
import com.android.server.testing.FrameworkRobolectricTestRunner;
import com.android.server.testing.SystemLoaderClasses;
-
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -78,11 +74,7 @@
mBindIntent = new Intent(SERVICE_ACTION_TRANSPORT_HOST).setComponent(mTransportComponent);
mTransportClient =
new TransportClient(
- mContext,
- mBindIntent,
- mTransportComponent,
- "1",
- new Handler(mainLooper));
+ mContext, mBindIntent, mTransportComponent, "1", new Handler(mainLooper));
when(mContext.bindServiceAsUser(
eq(mBindIntent),
@@ -213,32 +205,34 @@
.onTransportConnectionResult(isNull(), eq(mTransportClient));
}
- // TODO(b/69153972): Support SDK 26 API (ServiceConnection.inBindingDied) for transport tests
- /*@Test
+ @Test
public void testConnectAsync_callsListenerIfBindingDies() throws Exception {
- mTransportClient.connectAsync(mTransportListener, "caller");
+ mTransportClient.connectAsync(mTransportConnectionListener, "caller");
ServiceConnection connection = verifyBindServiceAsUserAndCaptureServiceConnection(mContext);
connection.onBindingDied(mTransportComponent);
mShadowLooper.runToEndOfTasks();
- verify(mTransportListener).onTransportBound(isNull(), eq(mTransportClient));
+ verify(mTransportConnectionListener)
+ .onTransportConnectionResult(isNull(), eq(mTransportClient));
}
@Test
public void testConnectAsync_whenPendingConnection_callsListenersIfBindingDies()
throws Exception {
- mTransportClient.connectAsync(mTransportListener, "caller1");
+ mTransportClient.connectAsync(mTransportConnectionListener, "caller1");
ServiceConnection connection = verifyBindServiceAsUserAndCaptureServiceConnection(mContext);
- mTransportClient.connectAsync(mTransportListener2, "caller2");
+ mTransportClient.connectAsync(mTransportConnectionListener2, "caller2");
connection.onBindingDied(mTransportComponent);
mShadowLooper.runToEndOfTasks();
- verify(mTransportListener).onTransportBound(isNull(), eq(mTransportClient));
- verify(mTransportListener2).onTransportBound(isNull(), eq(mTransportClient));
- }*/
+ verify(mTransportConnectionListener)
+ .onTransportConnectionResult(isNull(), eq(mTransportClient));
+ verify(mTransportConnectionListener2)
+ .onTransportConnectionResult(isNull(), eq(mTransportClient));
+ }
private ServiceConnection verifyBindServiceAsUserAndCaptureServiceConnection(Context context) {
ArgumentCaptor<ServiceConnection> connectionCaptor =
diff --git a/services/robotests/src/com/android/server/testing/shadows/FrameworkShadowContextImpl.java b/services/robotests/src/com/android/server/testing/shadows/FrameworkShadowContextImpl.java
new file mode 100644
index 0000000..6d22073
--- /dev/null
+++ b/services/robotests/src/com/android/server/testing/shadows/FrameworkShadowContextImpl.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.shadows;
+
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.UserHandle;
+
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+import org.robolectric.shadows.ShadowContextImpl;
+
+@Implements(className = ShadowContextImpl.CLASS_NAME, inheritImplementationMethods = true)
+public class FrameworkShadowContextImpl extends ShadowContextImpl {
+ @Implementation
+ public boolean bindServiceAsUser(
+ Intent service,
+ ServiceConnection connection,
+ int flags,
+ UserHandle user) {
+ return bindService(service, connection, flags);
+ }
+}
diff --git a/services/robotests/src/com/android/server/backup/testing/ShadowPackageManagerForBackup.java b/services/robotests/src/com/android/server/testing/shadows/FrameworkShadowPackageManager.java
similarity index 83%
rename from services/robotests/src/com/android/server/backup/testing/ShadowPackageManagerForBackup.java
rename to services/robotests/src/com/android/server/testing/shadows/FrameworkShadowPackageManager.java
index b64b59d..5cdbe7f 100644
--- a/services/robotests/src/com/android/server/backup/testing/ShadowPackageManagerForBackup.java
+++ b/services/robotests/src/com/android/server/testing/shadows/FrameworkShadowPackageManager.java
@@ -14,22 +14,18 @@
* limitations under the License
*/
-package com.android.server.backup.testing;
+package com.android.server.testing.shadows;
import android.app.ApplicationPackageManager;
import android.content.Intent;
import android.content.pm.ResolveInfo;
-
+import java.util.List;
import org.robolectric.annotation.Implements;
import org.robolectric.shadows.ShadowApplicationPackageManager;
-import java.util.List;
-
-/**
- * Implementation of PackageManager for Robolectric which handles queryIntentServicesAsUser().
- */
+/** Extension of ShadowApplicationPackageManager */
@Implements(value = ApplicationPackageManager.class, inheritImplementationMethods = true)
-public class ShadowPackageManagerForBackup extends ShadowApplicationPackageManager {
+public class FrameworkShadowPackageManager extends ShadowApplicationPackageManager {
@Override
public List<ResolveInfo> queryIntentServicesAsUser(Intent intent, int flags, int userId) {
return queryIntentServices(intent, flags);
diff --git a/services/tests/servicestests/src/com/android/server/am/ActivityRecordTests.java b/services/tests/servicestests/src/com/android/server/am/ActivityRecordTests.java
index 1dba39f..b38a413 100644
--- a/services/tests/servicestests/src/com/android/server/am/ActivityRecordTests.java
+++ b/services/tests/servicestests/src/com/android/server/am/ActivityRecordTests.java
@@ -35,24 +35,23 @@
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.anyInt;
-import static org.mockito.Mockito.atLeast;
+import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
-
import android.app.servertransaction.ClientTransaction;
import android.app.servertransaction.PauseActivityItem;
import android.graphics.Rect;
import android.platform.test.annotations.Presubmit;
import android.support.test.filters.MediumTest;
import android.support.test.runner.AndroidJUnit4;
+import android.util.MutableBoolean;
import org.junit.runner.RunWith;
import org.junit.Before;
import org.junit.Test;
-
-import org.mockito.ArgumentCaptor;
+import org.mockito.invocation.InvocationOnMock;
/**
* Tests for the {@link ActivityRecord} class.
@@ -110,23 +109,20 @@
@Test
public void testPausingWhenVisibleFromStopped() throws Exception {
- mActivity.state = STOPPED;
- mActivity.makeVisibleIfNeeded(null /* starting */);
- assertEquals(mActivity.state, PAUSING);
-
- final ArgumentCaptor<ClientTransaction> transaction =
- ArgumentCaptor.forClass(ClientTransaction.class);
- verify(mActivity.app.thread, atLeast(1)).scheduleTransaction(transaction.capture());
-
- boolean pauseFound = false;
-
- for (ClientTransaction targetTransaction : transaction.getAllValues()) {
- if (targetTransaction.getLifecycleStateRequest() instanceof PauseActivityItem) {
- pauseFound = true;
+ final MutableBoolean pauseFound = new MutableBoolean(false);
+ doAnswer((InvocationOnMock invocationOnMock) -> {
+ final ClientTransaction transaction = invocationOnMock.getArgument(0);
+ if (transaction.getLifecycleStateRequest() instanceof PauseActivityItem) {
+ pauseFound.value = true;
}
- }
+ return null;
+ }).when(mActivity.app.thread).scheduleTransaction(any());
+ mActivity.state = STOPPED;
- assertTrue(pauseFound);
+ mActivity.makeVisibleIfNeeded(null /* starting */);
+
+ assertEquals(mActivity.state, PAUSING);
+ assertTrue(pauseFound.value);
}
@Test
diff --git a/services/tests/servicestests/src/com/android/server/am/ClientLifecycleManagerTests.java b/services/tests/servicestests/src/com/android/server/am/ClientLifecycleManagerTests.java
new file mode 100644
index 0000000..ef6d5e8
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/am/ClientLifecycleManagerTests.java
@@ -0,0 +1,44 @@
+package com.android.server.am;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.app.IApplicationThread;
+import android.app.servertransaction.ClientTransaction;
+import android.os.Binder;
+import android.platform.test.annotations.Presubmit;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+@Presubmit
+public class ClientLifecycleManagerTests {
+
+ @Test
+ public void testScheduleAndRecycleBinderClientTransaction() throws Exception {
+ ClientTransaction item = spy(ClientTransaction.obtain(mock(IApplicationThread.class),
+ new Binder()));
+
+ ClientLifecycleManager clientLifecycleManager = new ClientLifecycleManager();
+ clientLifecycleManager.scheduleTransaction(item);
+
+ verify(item, times(1)).recycle();
+ }
+
+ @Test
+ public void testScheduleNoRecycleNonBinderClientTransaction() throws Exception {
+ ClientTransaction item = spy(ClientTransaction.obtain(mock(IApplicationThread.Stub.class),
+ new Binder()));
+
+ ClientLifecycleManager clientLifecycleManager = new ClientLifecycleManager();
+ clientLifecycleManager.scheduleTransaction(item);
+
+ verify(item, times(0)).recycle();
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/backup/BackupManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/backup/BackupManagerServiceTest.java
index 766d30d..7b4441a 100644
--- a/services/tests/servicestests/src/com/android/server/backup/BackupManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/backup/BackupManagerServiceTest.java
@@ -96,7 +96,7 @@
createBackupManagerService();
verify(mTransportManager)
- .setTransportBoundListener(any(TransportManager.TransportBoundListener.class));
+ .setOnTransportRegisteredListener(any());
}
@Test
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java
index 0650acb..5134f52 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java
@@ -15,11 +15,13 @@
*/
package com.android.server.devicepolicy;
+import android.app.ActivityManagerInternal;
import android.app.AlarmManager;
import android.app.IActivityManager;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.backup.IBackupManager;
+import android.app.usage.UsageStatsManagerInternal;
import android.content.Context;
import android.content.Intent;
import android.content.pm.IPackageManager;
@@ -152,6 +154,11 @@
}
@Override
+ UsageStatsManagerInternal getUsageStatsManagerInternal() {
+ return services.usageStatsManagerInternal;
+ }
+
+ @Override
PackageManagerInternal getPackageManagerInternal() {
return services.packageManagerInternal;
}
@@ -182,6 +189,11 @@
}
@Override
+ ActivityManagerInternal getActivityManagerInternal() {
+ return services.activityManagerInternal;
+ }
+
+ @Override
IPackageManager getIPackageManager() {
return services.ipackageManager;
}
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 58ac7d2..1df0ff2 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -44,6 +44,7 @@
import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;
import static org.mockito.hamcrest.MockitoHamcrest.argThat;
@@ -86,10 +87,12 @@
import com.android.internal.widget.LockPatternUtils;
import com.android.server.LocalServices;
import com.android.server.SystemService;
+import com.android.server.devicepolicy.DevicePolicyManagerService.RestrictionsListener;
import com.android.server.pm.UserRestrictionsUtils;
import org.hamcrest.BaseMatcher;
import org.hamcrest.Description;
+import org.mockito.Mockito;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
@@ -182,6 +185,7 @@
initializeDpms();
+ Mockito.reset(getServices().usageStatsManagerInternal);
setUpPackageManagerForAdmin(admin1, DpmMockContext.CALLER_UID);
setUpPackageManagerForAdmin(admin2, DpmMockContext.CALLER_UID);
setUpPackageManagerForAdmin(admin3, DpmMockContext.CALLER_UID);
@@ -207,6 +211,7 @@
LocalServices.removeServiceForTest(DevicePolicyManagerInternal.class);
dpms = new DevicePolicyManagerServiceTestable(getServices(), mContext);
+ dpms.handleStart();
dpms.systemReady(SystemService.PHASE_LOCK_SETTINGS_READY);
dpms.systemReady(SystemService.PHASE_BOOT_COMPLETED);
@@ -278,6 +283,32 @@
assertNull(LocalServices.getService(DevicePolicyManagerInternal.class));
}
+ public void testHandleStart() throws Exception {
+ // Device owner in SYSTEM_USER
+ setDeviceOwner();
+ // Profile owner in CALLER_USER_HANDLE
+ setUpPackageManagerForAdmin(admin2, DpmMockContext.CALLER_UID);
+ setAsProfileOwner(admin2);
+ // Active admin in CALLER_USER_HANDLE
+ final int ANOTHER_UID = UserHandle.getUid(DpmMockContext.CALLER_USER_HANDLE, 1306);
+ setUpPackageManagerForFakeAdmin(adminAnotherPackage, ANOTHER_UID, admin2);
+ dpm.setActiveAdmin(adminAnotherPackage, /* replace =*/ false,
+ DpmMockContext.CALLER_USER_HANDLE);
+ assertTrue(dpm.isAdminActiveAsUser(adminAnotherPackage,
+ DpmMockContext.CALLER_USER_HANDLE));
+
+ initializeDpms();
+
+ // Verify
+ verify(getServices().usageStatsManagerInternal).setActiveAdminApps(
+ MockUtils.checkAdminApps(admin1.getPackageName()),
+ eq(UserHandle.USER_SYSTEM));
+ verify(getServices().usageStatsManagerInternal).setActiveAdminApps(
+ MockUtils.checkAdminApps(admin2.getPackageName(),
+ adminAnotherPackage.getPackageName()),
+ eq(DpmMockContext.CALLER_USER_HANDLE));
+ }
+
/**
* Caller doesn't have proper permissions.
*/
@@ -330,6 +361,9 @@
eq(DpmMockContext.CALLER_USER_HANDLE),
anyString());
+ verify(getServices().usageStatsManagerInternal).onActiveAdminAdded(
+ admin1.getPackageName(), DpmMockContext.CALLER_USER_HANDLE);
+
// TODO Verify other calls too.
// Make sure it's active admin1.
@@ -369,6 +403,11 @@
eq(DpmMockContext.CALLER_USER_HANDLE),
anyString());
+ // times(2) because it was previously called for admin1 which is in the same package
+ // as admin2.
+ verify(getServices().usageStatsManagerInternal, times(2)).onActiveAdminAdded(
+ admin2.getPackageName(), DpmMockContext.CALLER_USER_HANDLE);
+
// 4. Add the same admin1 again without replace, which should throw.
assertExpectException(IllegalArgumentException.class, /* messageRegex= */ null,
() -> dpm.setActiveAdmin(admin1, /* replace =*/ false));
@@ -384,6 +423,10 @@
assertEquals(admin1, admins.get(0));
assertEquals(admin2, admins.get(1));
+ // There shouldn't be any callback to UsageStatsManagerInternal when the admin is being
+ // replaced
+ verifyNoMoreInteractions(getServices().usageStatsManagerInternal);
+
// Another user has no admins.
mContext.callerPermissions.add("android.permission.INTERACT_ACROSS_USERS_FULL");
@@ -516,6 +559,8 @@
() -> dpm.removeActiveAdmin(admin1));
assertFalse(dpm.isRemovingAdmin(admin1, DpmMockContext.CALLER_USER_HANDLE));
+ verify(getServices().usageStatsManagerInternal, times(0)).setActiveAdminApps(
+ null, DpmMockContext.CALLER_USER_HANDLE);
// 2. User unlocked.
when(getServices().userManager.isUserUnlocked(eq(DpmMockContext.CALLER_USER_HANDLE)))
@@ -523,6 +568,8 @@
dpm.removeActiveAdmin(admin1);
assertFalse(dpm.isAdminActiveAsUser(admin1, DpmMockContext.CALLER_USER_HANDLE));
+ verify(getServices().usageStatsManagerInternal).setActiveAdminApps(
+ null, DpmMockContext.CALLER_USER_HANDLE);
}
/**
@@ -547,6 +594,8 @@
dpms.removeActiveAdmin(admin1, DpmMockContext.CALLER_USER_HANDLE);
assertFalse(dpm.isAdminActiveAsUser(admin1, DpmMockContext.CALLER_USER_HANDLE));
+ verify(getServices().usageStatsManagerInternal).setActiveAdminApps(
+ null, DpmMockContext.CALLER_USER_HANDLE);
// TODO DO Still can't be removed in this case.
}
@@ -588,6 +637,8 @@
isNull(Bundle.class));
assertFalse(dpm.isAdminActiveAsUser(admin1, DpmMockContext.CALLER_USER_HANDLE));
+ verify(getServices().usageStatsManagerInternal).setActiveAdminApps(
+ null, DpmMockContext.CALLER_USER_HANDLE);
// Again broadcast from saveSettingsLocked().
verify(mContext.spiedContext, times(2)).sendBroadcastAsUser(
@@ -598,6 +649,86 @@
// TODO Check other internal calls.
}
+ public void testRemoveActiveAdmin_multipleAdminsInUser() {
+ // Need MANAGE_DEVICE_ADMINS for setActiveAdmin. We'll remove it later.
+ mContext.callerPermissions.add(android.Manifest.permission.MANAGE_DEVICE_ADMINS);
+
+ // Add admin1.
+ dpm.setActiveAdmin(admin1, /* replace =*/ false);
+
+ assertTrue(dpm.isAdminActive(admin1));
+ assertFalse(dpm.isRemovingAdmin(admin1, DpmMockContext.CALLER_USER_HANDLE));
+
+ // Add admin2.
+ dpm.setActiveAdmin(admin2, /* replace =*/ false);
+
+ assertTrue(dpm.isAdminActive(admin2));
+ assertFalse(dpm.isRemovingAdmin(admin2, DpmMockContext.CALLER_USER_HANDLE));
+
+ // Broadcast from saveSettingsLocked().
+ verify(mContext.spiedContext, times(2)).sendBroadcastAsUser(
+ MockUtils.checkIntentAction(
+ DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED),
+ MockUtils.checkUserHandle(DpmMockContext.CALLER_USER_HANDLE));
+
+ // Remove. No permissions, but same user, so it'll work.
+ mContext.callerPermissions.clear();
+ dpm.removeActiveAdmin(admin1);
+
+ verify(mContext.spiedContext).sendOrderedBroadcastAsUser(
+ MockUtils.checkIntentAction(
+ DeviceAdminReceiver.ACTION_DEVICE_ADMIN_DISABLED),
+ MockUtils.checkUserHandle(DpmMockContext.CALLER_USER_HANDLE),
+ isNull(String.class),
+ any(BroadcastReceiver.class),
+ eq(dpms.mHandler),
+ eq(Activity.RESULT_OK),
+ isNull(String.class),
+ isNull(Bundle.class));
+
+ assertFalse(dpm.isAdminActiveAsUser(admin1, DpmMockContext.CALLER_USER_HANDLE));
+ verify(getServices().usageStatsManagerInternal).setActiveAdminApps(
+ MockUtils.checkAdminApps(admin2.getPackageName()),
+ eq(DpmMockContext.CALLER_USER_HANDLE));
+
+ // Again broadcast from saveSettingsLocked().
+ verify(mContext.spiedContext, times(3)).sendBroadcastAsUser(
+ MockUtils.checkIntentAction(
+ DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED),
+ MockUtils.checkUserHandle(DpmMockContext.CALLER_USER_HANDLE));
+ }
+
+ /**
+ * Test for:
+ * {@link DevicePolicyManager#forceRemoveActiveAdmin(ComponentName, int)}
+ */
+ public void testForceRemoveActiveAdmin() throws Exception {
+ mContext.callerPermissions.add(android.Manifest.permission.MANAGE_DEVICE_ADMINS);
+
+ // Add admin.
+ setupPackageInPackageManager(admin1.getPackageName(),
+ /* userId= */ DpmMockContext.CALLER_USER_HANDLE,
+ /* appId= */ 10138,
+ /* flags= */ ApplicationInfo.FLAG_TEST_ONLY);
+ dpm.setActiveAdmin(admin1, /* replace =*/ false);
+ assertTrue(dpm.isAdminActive(admin1));
+
+ // Calling from a non-shell uid should fail with a SecurityException
+ mContext.binder.callingUid = 123456;
+ assertExpectException(SecurityException.class,
+ /* messageRegex =*/ "Non-shell user attempted to call",
+ () -> dpms.forceRemoveActiveAdmin(admin1, DpmMockContext.CALLER_USER_HANDLE));
+
+ mContext.binder.callingUid = Process.SHELL_UID;
+ dpms.forceRemoveActiveAdmin(admin1, DpmMockContext.CALLER_USER_HANDLE);
+
+ mContext.callerPermissions.add(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL);
+ // Verify
+ assertFalse(dpm.isAdminActiveAsUser(admin1, DpmMockContext.CALLER_USER_HANDLE));
+ verify(getServices().usageStatsManagerInternal).setActiveAdminApps(
+ null, DpmMockContext.CALLER_USER_HANDLE);
+ }
+
/**
* Test for: @{link DevicePolicyManager#setActivePasswordState}
*
@@ -954,6 +1085,9 @@
eq(null),
eq(true), eq(CAMERA_NOT_DISABLED));
+ verify(getServices().usageStatsManagerInternal).setActiveAdminApps(
+ null, UserHandle.USER_SYSTEM);
+
assertFalse(dpm.isAdminActiveAsUser(admin1, UserHandle.USER_SYSTEM));
// ACTION_DEVICE_OWNER_CHANGED should be sent twice, once for setting the device owner
@@ -1044,6 +1178,8 @@
// Check
assertFalse(dpm.isProfileOwnerApp(admin1.getPackageName()));
assertFalse(dpm.isAdminActiveAsUser(admin1, DpmMockContext.CALLER_USER_HANDLE));
+ verify(getServices().usageStatsManagerInternal).setActiveAdminApps(
+ null, DpmMockContext.CALLER_USER_HANDLE);
}
public void testSetProfileOwner_failures() throws Exception {
@@ -3885,6 +4021,8 @@
dpm.setPasswordMinimumNumeric(admin1, 1);
dpm.setPasswordMinimumSymbols(admin1, 0);
+ reset(mContext.spiedContext);
+
PasswordMetrics passwordMetricsNoSymbols = new PasswordMetrics(
DevicePolicyManager.PASSWORD_QUALITY_COMPLEX, 9,
8, 2,
@@ -3934,9 +4072,16 @@
dpm.setActivePasswordState(passwordMetrics, userHandle);
dpm.reportPasswordChanged(userHandle);
+ // Drain ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED broadcasts as part of
+ // reportPasswordChanged()
+ verify(mContext.spiedContext, times(3)).sendBroadcastAsUser(
+ MockUtils.checkIntentAction(
+ DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED),
+ MockUtils.checkUserHandle(userHandle));
+
final Intent intent = new Intent(DeviceAdminReceiver.ACTION_PASSWORD_CHANGED);
intent.setComponent(admin1);
- intent.putExtra(Intent.EXTRA_USER, UserHandle.of(mContext.binder.callingUid));
+ intent.putExtra(Intent.EXTRA_USER, UserHandle.of(userHandle));
verify(mContext.spiedContext, times(1)).sendBroadcastAsUser(
MockUtils.checkIntent(intent),
@@ -4347,6 +4492,45 @@
verifyCantGetOwnerInstalledCaCertsProfileOwnerRemoval(null, caller);
}
+ public void testDisallowSharingIntoProfileSetRestriction() {
+ Bundle restriction = new Bundle();
+ restriction.putBoolean(UserManager.DISALLOW_SHARE_INTO_MANAGED_PROFILE, true);
+
+ mContext.binder.callingUid = DpmMockContext.SYSTEM_UID;
+ RestrictionsListener listener = new RestrictionsListener(mContext);
+ listener.onUserRestrictionsChanged(DpmMockContext.CALLER_USER_HANDLE, restriction,
+ new Bundle());
+ verifyDataSharingChangedBroadcast();
+ }
+
+ public void testDisallowSharingIntoProfileClearRestriction() {
+ Bundle restriction = new Bundle();
+ restriction.putBoolean(UserManager.DISALLOW_SHARE_INTO_MANAGED_PROFILE, true);
+
+ mContext.binder.callingUid = DpmMockContext.SYSTEM_UID;
+ RestrictionsListener listener = new RestrictionsListener(mContext);
+ listener.onUserRestrictionsChanged(DpmMockContext.CALLER_USER_HANDLE, new Bundle(),
+ restriction);
+ verifyDataSharingChangedBroadcast();
+ }
+
+ public void testDisallowSharingIntoProfileUnchanged() {
+ RestrictionsListener listener = new RestrictionsListener(mContext);
+ listener.onUserRestrictionsChanged(DpmMockContext.CALLER_USER_HANDLE, new Bundle(),
+ new Bundle());
+ verify(mContext.spiedContext, never()).sendBroadcastAsUser(any(), any());
+ }
+
+ private void verifyDataSharingChangedBroadcast() {
+ Intent expectedIntent = new Intent(
+ DevicePolicyManager.ACTION_DATA_SHARING_RESTRICTION_CHANGED);
+ expectedIntent.setPackage("com.android.managedprovisioning");
+ expectedIntent.putExtra(Intent.EXTRA_USER_ID, DpmMockContext.CALLER_USER_HANDLE);
+ verify(mContext.spiedContext, times(1)).sendBroadcastAsUser(
+ MockUtils.checkIntent(expectedIntent),
+ MockUtils.checkUserHandle(UserHandle.USER_SYSTEM));
+ }
+
private void verifyCanGetOwnerInstalledCaCerts(
final ComponentName caller, final DpmMockContext callerContext) throws Exception {
final String alias = "cert";
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java b/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java
index 4232c44..268d424 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java
@@ -25,10 +25,12 @@
import android.accounts.Account;
import android.accounts.AccountManager;
+import android.app.ActivityManagerInternal;
import android.app.AlarmManager;
import android.app.IActivityManager;
import android.app.NotificationManager;
import android.app.backup.IBackupManager;
+import android.app.usage.UsageStatsManagerInternal;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
@@ -73,6 +75,7 @@
public final SystemPropertiesForMock systemProperties;
public final UserManager userManager;
public final UserManagerInternal userManagerInternal;
+ public final UsageStatsManagerInternal usageStatsManagerInternal;
public final PackageManagerInternal packageManagerInternal;
public final UserManagerForMock userManagerForMock;
public final PowerManagerForMock powerManager;
@@ -82,6 +85,7 @@
public final IIpConnectivityMetrics iipConnectivityMetrics;
public final IWindowManager iwindowManager;
public final IActivityManager iactivityManager;
+ public ActivityManagerInternal activityManagerInternal;
public final IPackageManager ipackageManager;
public final IBackupManager ibackupManager;
public final IAudioService iaudioService;
@@ -108,6 +112,7 @@
systemProperties = mock(SystemPropertiesForMock.class);
userManager = mock(UserManager.class);
userManagerInternal = mock(UserManagerInternal.class);
+ usageStatsManagerInternal = mock(UsageStatsManagerInternal.class);
userManagerForMock = mock(UserManagerForMock.class);
packageManagerInternal = mock(PackageManagerInternal.class);
powerManager = mock(PowerManagerForMock.class);
@@ -117,6 +122,7 @@
iipConnectivityMetrics = mock(IIpConnectivityMetrics.class);
iwindowManager = mock(IWindowManager.class);
iactivityManager = mock(IActivityManager.class);
+ activityManagerInternal = mock(ActivityManagerInternal.class);
ipackageManager = mock(IPackageManager.class);
ibackupManager = mock(IBackupManager.class);
iaudioService = mock(IAudioService.class);
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/MockUtils.java b/services/tests/servicestests/src/com/android/server/devicepolicy/MockUtils.java
index e43786c..dec962e 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/MockUtils.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/MockUtils.java
@@ -24,13 +24,16 @@
import android.content.Intent;
import android.os.Bundle;
import android.os.UserHandle;
+import android.util.ArraySet;
import org.hamcrest.BaseMatcher;
import org.hamcrest.Description;
import org.hamcrest.Matcher;
-import org.mockito.Mockito;
import org.mockito.hamcrest.MockitoHamcrest;
+import java.util.Arrays;
+import java.util.Set;
+
public class MockUtils {
private MockUtils() {
}
@@ -88,7 +91,8 @@
@Override
public boolean matches(Object item) {
if (item == null) return false;
- return intent.filterEquals((Intent) item);
+ if (!intent.filterEquals((Intent) item)) return false;
+ return intent.getExtras().kindofEquals(((Intent) item).getExtras());
}
@Override
public void describeTo(Description description) {
@@ -115,6 +119,30 @@
return MockitoHamcrest.argThat(m);
}
+ public static Set<String> checkAdminApps(String... adminApps) {
+ final Matcher<Set<String>> m = new BaseMatcher<Set<String>>() {
+ @Override
+ public boolean matches(Object item) {
+ if (item == null) return false;
+ final Set<String> actualAdminApps = (Set<String>) item;
+ if (adminApps.length != actualAdminApps.size()) {
+ return false;
+ }
+ final Set<String> copyOfAdmins = new ArraySet<>(actualAdminApps);
+ for (String adminApp : adminApps) {
+ copyOfAdmins.remove(adminApp);
+ }
+ return copyOfAdmins.isEmpty();
+ }
+
+ @Override
+ public void describeTo(Description description) {
+ description.appendText("Admin apps=" + Arrays.toString(adminApps));
+ }
+ };
+ return MockitoHamcrest.argThat(m);
+ }
+
private static String getRestrictionsAsString(Bundle b) {
final StringBuilder sb = new StringBuilder();
sb.append("[");
diff --git a/services/tests/servicestests/src/com/android/server/display/BrightnessTrackerTest.java b/services/tests/servicestests/src/com/android/server/display/BrightnessTrackerTest.java
index 08edd52..edc7d74 100644
--- a/services/tests/servicestests/src/com/android/server/display/BrightnessTrackerTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/BrightnessTrackerTest.java
@@ -447,21 +447,24 @@
@Test
public void testParcelUnParcel() {
Parcel parcel = Parcel.obtain();
- BrightnessChangeEvent event = new BrightnessChangeEvent();
- event.brightness = 23f;
- event.timeStamp = 345L;
- event.packageName = "com.example";
- event.userId = 12;
- event.luxValues = new float[2];
- event.luxValues[0] = 3000.0f;
- event.luxValues[1] = 4000.0f;
- event.luxTimestamps = new long[2];
- event.luxTimestamps[0] = 325L;
- event.luxTimestamps[1] = 315L;
- event.batteryLevel = 0.7f;
- event.nightMode = false;
- event.colorTemperature = 345;
- event.lastBrightness = 50f;
+ BrightnessChangeEvent.Builder builder = new BrightnessChangeEvent.Builder();
+ builder.setBrightness(23f);
+ builder.setTimeStamp(345L);
+ builder.setPackageName("com.example");
+ builder.setUserId(12);
+ float[] luxValues = new float[2];
+ luxValues[0] = 3000.0f;
+ luxValues[1] = 4000.0f;
+ builder.setLuxValues(luxValues);
+ long[] luxTimestamps = new long[2];
+ luxTimestamps[0] = 325L;
+ luxTimestamps[1] = 315L;
+ builder.setLuxTimestamps(luxTimestamps);
+ builder.setBatteryLevel(0.7f);
+ builder.setNightMode(false);
+ builder.setColorTemperature(345);
+ builder.setLastBrightness(50f);
+ BrightnessChangeEvent event = builder.build();
event.writeToParcel(parcel, 0);
byte[] parceled = parcel.marshall();
@@ -485,7 +488,8 @@
assertEquals(event.lastBrightness, event2.lastBrightness, FLOAT_DELTA);
parcel = Parcel.obtain();
- event.batteryLevel = Float.NaN;
+ builder.setBatteryLevel(Float.NaN);
+ event = builder.build();
event.writeToParcel(parcel, 0);
parceled = parcel.marshall();
parcel.recycle();
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncTaskTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncTaskTest.java
index f798e9c..9eb42e9 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncTaskTest.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncTaskTest.java
@@ -16,11 +16,11 @@
package com.android.server.locksettings.recoverablekeystore;
-import static android.security.recoverablekeystore.KeyStoreRecoveryMetadata.TYPE_LOCKSCREEN;
+import static android.security.keystore.RecoveryMetadata.TYPE_LOCKSCREEN;
-import static android.security.recoverablekeystore.KeyStoreRecoveryMetadata.TYPE_PASSWORD;
-import static android.security.recoverablekeystore.KeyStoreRecoveryMetadata.TYPE_PATTERN;
-import static android.security.recoverablekeystore.KeyStoreRecoveryMetadata.TYPE_PIN;
+import static android.security.keystore.RecoveryMetadata.TYPE_PASSWORD;
+import static android.security.keystore.RecoveryMetadata.TYPE_PATTERN;
+import static android.security.keystore.RecoveryMetadata.TYPE_PIN;
import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PASSWORD;
import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PATTERN;
@@ -40,9 +40,9 @@
import android.security.keystore.AndroidKeyStoreSecretKey;
import android.security.keystore.KeyGenParameterSpec;
import android.security.keystore.KeyProperties;
-import android.security.recoverablekeystore.KeyDerivationParameters;
-import android.security.recoverablekeystore.KeyEntryRecoveryData;
-import android.security.recoverablekeystore.KeyStoreRecoveryData;
+import android.security.keystore.KeyDerivationParams;
+import android.security.keystore.EntryRecoveryData;
+import android.security.keystore.RecoveryData;
import android.support.test.InstrumentationRegistry;
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
@@ -77,7 +77,8 @@
private static final int TEST_USER_ID = 1000;
private static final int TEST_RECOVERY_AGENT_UID = 10009;
private static final int TEST_RECOVERY_AGENT_UID2 = 10010;
- private static final long TEST_DEVICE_ID = 13295035643L;
+ private static final byte[] TEST_DEVICE_ID =
+ new byte[]{1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2};
private static final String TEST_APP_KEY_ALIAS = "rcleaver";
private static final int TEST_GENERATION_ID = 2;
private static final int TEST_CREDENTIAL_TYPE = CREDENTIAL_TYPE_PASSWORD;
@@ -239,7 +240,7 @@
@Test
public void run_doesNotSendAnythingIfNoRecoveryAgentPendingIntentRegistered() throws Exception {
SecretKey applicationKey = generateKey();
- mRecoverableKeyStoreDb.setServerParameters(
+ mRecoverableKeyStoreDb.setServerParams(
TEST_USER_ID, TEST_RECOVERY_AGENT_UID, TEST_DEVICE_ID);
mRecoverableKeyStoreDb.setPlatformKeyGenerationId(TEST_USER_ID, TEST_GENERATION_ID);
mRecoverableKeyStoreDb.insertKey(
@@ -275,7 +276,6 @@
@Test
public void run_sendsEncryptedKeysIfAvailableToSync() throws Exception {
-
mRecoverableKeyStoreDb.setRecoveryServicePublicKey(
TEST_USER_ID, TEST_RECOVERY_AGENT_UID, mKeyPair.getPublic());
when(mSnapshotListenersStorage.hasListener(TEST_RECOVERY_AGENT_UID)).thenReturn(true);
@@ -283,14 +283,14 @@
addApplicationKey(TEST_USER_ID, TEST_RECOVERY_AGENT_UID, TEST_APP_KEY_ALIAS);
mKeySyncTask.run();
- KeyStoreRecoveryData recoveryData = mRecoverySnapshotStorage.get(TEST_RECOVERY_AGENT_UID);
- KeyDerivationParameters keyDerivationParameters =
- recoveryData.getRecoveryMetadata().get(0).getKeyDerivationParameters();
- assertThat(keyDerivationParameters.getAlgorithm()).isEqualTo(
- KeyDerivationParameters.ALGORITHM_SHA256);
+ RecoveryData recoveryData = mRecoverySnapshotStorage.get(TEST_RECOVERY_AGENT_UID);
+ KeyDerivationParams KeyDerivationParams =
+ recoveryData.getRecoveryMetadata().get(0).getKeyDerivationParams();
+ assertThat(KeyDerivationParams.getAlgorithm()).isEqualTo(
+ KeyDerivationParams.ALGORITHM_SHA256);
verify(mSnapshotListenersStorage).recoverySnapshotAvailable(TEST_RECOVERY_AGENT_UID);
byte[] lockScreenHash = KeySyncTask.hashCredentials(
- keyDerivationParameters.getSalt(),
+ KeyDerivationParams.getSalt(),
TEST_CREDENTIAL);
Long counterId = mRecoverableKeyStoreDb.getCounterId(TEST_USER_ID, TEST_RECOVERY_AGENT_UID);
assertThat(counterId).isNotNull();
@@ -300,11 +300,11 @@
/*vaultParams=*/ KeySyncUtils.packVaultParams(
mKeyPair.getPublic(),
counterId,
- /*maxAttempts=*/ 10,
- TEST_DEVICE_ID));
- List<KeyEntryRecoveryData> applicationKeys = recoveryData.getApplicationKeyBlobs();
+ TEST_DEVICE_ID,
+ /*maxAttempts=*/ 10));
+ List<EntryRecoveryData> applicationKeys = recoveryData.getEntryRecoveryData();
assertThat(applicationKeys).hasSize(1);
- KeyEntryRecoveryData keyData = applicationKeys.get(0);
+ EntryRecoveryData keyData = applicationKeys.get(0);
assertEquals(TEST_APP_KEY_ALIAS, keyData.getAlias());
assertThat(keyData.getAlias()).isEqualTo(keyData.getAlias());
byte[] appKey = KeySyncUtils.decryptApplicationKey(
@@ -314,7 +314,6 @@
@Test
public void run_setsCorrectSnapshotVersion() throws Exception {
-
mRecoverableKeyStoreDb.setRecoveryServicePublicKey(
TEST_USER_ID, TEST_RECOVERY_AGENT_UID, mKeyPair.getPublic());
when(mSnapshotListenersStorage.hasListener(TEST_RECOVERY_AGENT_UID)).thenReturn(true);
@@ -323,7 +322,7 @@
mKeySyncTask.run();
- KeyStoreRecoveryData recoveryData = mRecoverySnapshotStorage.get(TEST_RECOVERY_AGENT_UID);
+ RecoveryData recoveryData = mRecoverySnapshotStorage.get(TEST_RECOVERY_AGENT_UID);
assertThat(recoveryData.getSnapshotVersion()).isEqualTo(1); // default value;
mRecoverableKeyStoreDb.setShouldCreateSnapshot(TEST_USER_ID, TEST_RECOVERY_AGENT_UID, true);
@@ -353,9 +352,9 @@
mKeySyncTask.run();
- KeyStoreRecoveryData recoveryData = mRecoverySnapshotStorage.get(TEST_RECOVERY_AGENT_UID);
+ RecoveryData recoveryData = mRecoverySnapshotStorage.get(TEST_RECOVERY_AGENT_UID);
assertThat(recoveryData.getRecoveryMetadata()).hasSize(1);
- assertThat(recoveryData.getRecoveryMetadata().get(1).getLockScreenUiFormat()).
+ assertThat(recoveryData.getRecoveryMetadata().get(0).getLockScreenUiFormat()).
isEqualTo(TYPE_PASSWORD);
}
@@ -379,10 +378,10 @@
mKeySyncTask.run();
- KeyStoreRecoveryData recoveryData = mRecoverySnapshotStorage.get(TEST_RECOVERY_AGENT_UID);
+ RecoveryData recoveryData = mRecoverySnapshotStorage.get(TEST_RECOVERY_AGENT_UID);
assertThat(recoveryData.getRecoveryMetadata()).hasSize(1);
// Password with only digits is changed to pin.
- assertThat(recoveryData.getRecoveryMetadata().get(1).getLockScreenUiFormat()).
+ assertThat(recoveryData.getRecoveryMetadata().get(0).getLockScreenUiFormat()).
isEqualTo(TYPE_PIN);
}
@@ -406,9 +405,9 @@
mKeySyncTask.run();
- KeyStoreRecoveryData recoveryData = mRecoverySnapshotStorage.get(TEST_RECOVERY_AGENT_UID);
+ RecoveryData recoveryData = mRecoverySnapshotStorage.get(TEST_RECOVERY_AGENT_UID);
assertThat(recoveryData.getRecoveryMetadata()).hasSize(1);
- assertThat(recoveryData.getRecoveryMetadata().get(1).getLockScreenUiFormat()).
+ assertThat(recoveryData.getRecoveryMetadata().get(0).getLockScreenUiFormat()).
isEqualTo(TYPE_PATTERN);
}
@@ -431,10 +430,10 @@
@Test
public void run_sendsEncryptedKeysOnlyForAgentWhichActiveUserSecretType() throws Exception {
mRecoverableKeyStoreDb.setRecoverySecretTypes(TEST_USER_ID, TEST_RECOVERY_AGENT_UID,
- new int[] {TYPE_LOCKSCREEN, 100});
+ new int[] {TYPE_LOCKSCREEN, 1000});
// Snapshot will not be created during unlock event.
mRecoverableKeyStoreDb.setRecoverySecretTypes(TEST_USER_ID, TEST_RECOVERY_AGENT_UID2,
- new int[] {100});
+ new int[] {1000});
mRecoverableKeyStoreDb.setRecoveryServicePublicKey(
TEST_USER_ID, TEST_RECOVERY_AGENT_UID, mKeyPair.getPublic());
@@ -471,7 +470,7 @@
private SecretKey addApplicationKey(int userId, int recoveryAgentUid, String alias)
throws Exception{
SecretKey applicationKey = generateKey();
- mRecoverableKeyStoreDb.setServerParameters(
+ mRecoverableKeyStoreDb.setServerParams(
userId, recoveryAgentUid, TEST_DEVICE_ID);
mRecoverableKeyStoreDb.setPlatformKeyGenerationId(userId, TEST_GENERATION_ID);
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncUtilsTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncUtilsTest.java
index 114da1a..3a9ff85 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncUtilsTest.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncUtilsTest.java
@@ -50,6 +50,8 @@
private static final int RECOVERY_KEY_LENGTH_BITS = 256;
private static final int THM_KF_HASH_SIZE = 256;
private static final int KEY_CLAIMANT_LENGTH_BYTES = 16;
+ private static final byte[] TEST_DEVICE_ID =
+ new byte[]{1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2};
private static final String SHA_256_ALGORITHM = "SHA-256";
private static final String APPLICATION_KEY_ALGORITHM = "AES";
private static final byte[] LOCK_SCREEN_HASH_1 =
@@ -348,8 +350,8 @@
byte[] packedForm = KeySyncUtils.packVaultParams(
thmPublicKey,
/*counterId=*/ 1001L,
- /*maxAttempts=*/ 10,
- /*deviceId=*/ 1L);
+ TEST_DEVICE_ID,
+ /*maxAttempts=*/ 10);
assertEquals(VAULT_PARAMS_LENGTH_BYTES, packedForm.length);
}
@@ -361,8 +363,8 @@
byte[] packedForm = KeySyncUtils.packVaultParams(
thmPublicKey,
/*counterId=*/ 1001L,
- /*maxAttempts=*/ 10,
- /*deviceId=*/ 1L);
+ TEST_DEVICE_ID,
+ /*maxAttempts=*/ 10);
assertArrayEquals(
SecureBox.encodePublicKey(thmPublicKey),
@@ -376,8 +378,8 @@
byte[] packedForm = KeySyncUtils.packVaultParams(
SecureBox.genKeyPair().getPublic(),
counterId,
- /*maxAttempts=*/ 10,
- /*deviceId=*/ 1L);
+ TEST_DEVICE_ID,
+ /*maxAttempts=*/ 10);
ByteBuffer byteBuffer = ByteBuffer.wrap(packedForm)
.order(ByteOrder.LITTLE_ENDIAN);
@@ -387,18 +389,17 @@
@Test
public void packVaultParams_encodesDeviceIdAsThirdParam() throws Exception {
- long deviceId = 102942158152L;
byte[] packedForm = KeySyncUtils.packVaultParams(
SecureBox.genKeyPair().getPublic(),
/*counterId=*/ 10021L,
- /*maxAttempts=*/ 10,
- deviceId);
+ TEST_DEVICE_ID,
+ /*maxAttempts=*/ 10);
ByteBuffer byteBuffer = ByteBuffer.wrap(packedForm)
.order(ByteOrder.LITTLE_ENDIAN);
byteBuffer.position(PUBLIC_KEY_LENGTH_BYTES + Long.BYTES);
- assertEquals(deviceId, byteBuffer.getLong());
+ assertEquals(/* default value*/0, byteBuffer.getLong());
}
@Test
@@ -408,11 +409,12 @@
byte[] packedForm = KeySyncUtils.packVaultParams(
SecureBox.genKeyPair().getPublic(),
/*counterId=*/ 1001L,
- maxAttempts,
- /*deviceId=*/ 1L);
+ TEST_DEVICE_ID,
+ maxAttempts);
ByteBuffer byteBuffer = ByteBuffer.wrap(packedForm)
.order(ByteOrder.LITTLE_ENDIAN);
+ // TODO: update position.
byteBuffer.position(PUBLIC_KEY_LENGTH_BYTES + 2 * Long.BYTES);
assertEquals(maxAttempts, byteBuffer.getInt());
}
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java
index ac2d36b..1bdcf47 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java
@@ -16,8 +16,8 @@
package com.android.server.locksettings.recoverablekeystore;
-import static android.security.recoverablekeystore.KeyStoreRecoveryMetadata.TYPE_LOCKSCREEN;
-import static android.security.recoverablekeystore.KeyStoreRecoveryMetadata.TYPE_PASSWORD;
+import static android.security.keystore.RecoveryMetadata.TYPE_LOCKSCREEN;
+import static android.security.keystore.RecoveryMetadata.TYPE_PASSWORD;
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertArrayEquals;
@@ -35,18 +35,19 @@
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
+import android.Manifest;
import android.os.Binder;
import android.os.ServiceSpecificException;
import android.os.UserHandle;
import android.security.keystore.AndroidKeyStoreSecretKey;
import android.security.keystore.KeyGenParameterSpec;
import android.security.keystore.KeyProperties;
-import android.security.recoverablekeystore.KeyDerivationParameters;
-import android.security.recoverablekeystore.KeyEntryRecoveryData;
-import android.security.recoverablekeystore.KeyStoreRecoveryMetadata;
-import android.security.recoverablekeystore.RecoverableKeyStoreLoader;
-import android.support.test.InstrumentationRegistry;
+import android.security.keystore.KeyDerivationParams;
+import android.security.keystore.EntryRecoveryData;
+import android.security.keystore.RecoveryMetadata;
+import android.security.keystore.RecoveryManager;
import android.support.test.filters.SmallTest;
+import android.support.test.InstrumentationRegistry;
import android.support.test.runner.AndroidJUnit4;
import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDb;
@@ -78,6 +79,7 @@
public class RecoverableKeyStoreManagerTest {
private static final String DATABASE_FILE_NAME = "recoverablekeystore.db";
+ private static final String ROOT_CERTIFICATE_ALIAS = "put_default_alias_here";
private static final String TEST_SESSION_ID = "karlin";
private static final byte[] TEST_PUBLIC_KEY = new byte[] {
(byte) 0x30, (byte) 0x59, (byte) 0x30, (byte) 0x13, (byte) 0x06, (byte) 0x07, (byte) 0x2a,
@@ -206,10 +208,9 @@
}
@Test
- public void removeKey_UpdatesShouldCreateSnapshot() throws Exception {
+ public void removeKey_updatesShouldCreateSnapshot() throws Exception {
int uid = Binder.getCallingUid();
int userId = UserHandle.getCallingUserId();
-
mRecoverableKeyStoreManager.generateAndStoreKey(TEST_ALIAS);
// Pretend that key was synced
mRecoverableKeyStoreDb.setShouldCreateSnapshot(userId, uid, false);
@@ -220,6 +221,29 @@
}
@Test
+ public void removeKey_failureDoesNotUpdateShouldCreateSnapshot() throws Exception {
+ int uid = Binder.getCallingUid();
+ int userId = UserHandle.getCallingUserId();
+ mRecoverableKeyStoreDb.setShouldCreateSnapshot(userId, uid, false);
+ // Key did not exist
+ mRecoverableKeyStoreManager.removeKey(TEST_ALIAS);
+
+ assertThat(mRecoverableKeyStoreDb.getShouldCreateSnapshot(userId, uid)).isFalse();
+ }
+
+ @Test
+ public void initRecoveryService_updatesShouldCreateSnapshot() throws Exception {
+ int uid = Binder.getCallingUid();
+ int userId = UserHandle.getCallingUserId();
+ // Sync is not needed.
+ mRecoverableKeyStoreDb.setShouldCreateSnapshot(userId, uid, false);
+
+ mRecoverableKeyStoreManager.initRecoveryService(ROOT_CERTIFICATE_ALIAS, TEST_PUBLIC_KEY);
+
+ assertThat(mRecoverableKeyStoreDb.getShouldCreateSnapshot(userId, uid)).isTrue();
+ }
+
+ @Test
public void startRecoverySession_checksPermissionFirst() throws Exception {
mRecoverableKeyStoreManager.startRecoverySession(
TEST_SESSION_ID,
@@ -227,15 +251,15 @@
TEST_VAULT_PARAMS,
TEST_VAULT_CHALLENGE,
ImmutableList.of(
- new KeyStoreRecoveryMetadata(
+ new RecoveryMetadata(
TYPE_LOCKSCREEN,
TYPE_PASSWORD,
- KeyDerivationParameters.createSha256Parameters(TEST_SALT),
+ KeyDerivationParams.createSha256Params(TEST_SALT),
TEST_SECRET)));
verify(mMockContext, times(1))
.enforceCallingOrSelfPermission(
- eq(RecoverableKeyStoreLoader.PERMISSION_RECOVER_KEYSTORE), any());
+ eq(Manifest.permission.RECOVER_KEYSTORE), any());
}
@Test
@@ -246,10 +270,10 @@
TEST_VAULT_PARAMS,
TEST_VAULT_CHALLENGE,
ImmutableList.of(
- new KeyStoreRecoveryMetadata(
+ new RecoveryMetadata(
TYPE_LOCKSCREEN,
TYPE_PASSWORD,
- KeyDerivationParameters.createSha256Parameters(TEST_SALT),
+ KeyDerivationParams.createSha256Params(TEST_SALT),
TEST_SECRET)));
assertEquals(1, mRecoverySessionStorage.size());
@@ -271,7 +295,7 @@
fail("should have thrown");
} catch (ServiceSpecificException e) {
assertThat(e.getMessage()).startsWith(
- "Only a single KeyStoreRecoveryMetadata is supported");
+ "Only a single RecoveryMetadata is supported");
}
}
@@ -284,10 +308,10 @@
TEST_VAULT_PARAMS,
TEST_VAULT_CHALLENGE,
ImmutableList.of(
- new KeyStoreRecoveryMetadata(
+ new RecoveryMetadata(
TYPE_LOCKSCREEN,
TYPE_PASSWORD,
- KeyDerivationParameters.createSha256Parameters(TEST_SALT),
+ KeyDerivationParams.createSha256Params(TEST_SALT),
TEST_SECRET)));
fail("should have thrown");
} catch (ServiceSpecificException e) {
@@ -306,10 +330,10 @@
vaultParams,
TEST_VAULT_CHALLENGE,
ImmutableList.of(
- new KeyStoreRecoveryMetadata(
+ new RecoveryMetadata(
TYPE_LOCKSCREEN,
TYPE_PASSWORD,
- KeyDerivationParameters.createSha256Parameters(TEST_SALT),
+ KeyDerivationParams.createSha256Params(TEST_SALT),
TEST_SECRET)));
fail("should have thrown");
} catch (ServiceSpecificException e) {
@@ -324,7 +348,7 @@
TEST_SESSION_ID,
/*recoveryKeyBlob=*/ randomBytes(32),
/*applicationKeys=*/ ImmutableList.of(
- new KeyEntryRecoveryData("alias", randomBytes(32))
+ new EntryRecoveryData("alias", randomBytes(32))
));
fail("should have thrown");
} catch (ServiceSpecificException e) {
@@ -339,10 +363,10 @@
TEST_PUBLIC_KEY,
TEST_VAULT_PARAMS,
TEST_VAULT_CHALLENGE,
- ImmutableList.of(new KeyStoreRecoveryMetadata(
+ ImmutableList.of(new RecoveryMetadata(
TYPE_LOCKSCREEN,
TYPE_PASSWORD,
- KeyDerivationParameters.createSha256Parameters(TEST_SALT),
+ KeyDerivationParams.createSha256Params(TEST_SALT),
TEST_SECRET)));
try {
@@ -363,17 +387,17 @@
TEST_PUBLIC_KEY,
TEST_VAULT_PARAMS,
TEST_VAULT_CHALLENGE,
- ImmutableList.of(new KeyStoreRecoveryMetadata(
+ ImmutableList.of(new RecoveryMetadata(
TYPE_LOCKSCREEN,
TYPE_PASSWORD,
- KeyDerivationParameters.createSha256Parameters(TEST_SALT),
+ KeyDerivationParams.createSha256Params(TEST_SALT),
TEST_SECRET)));
byte[] keyClaimant = mRecoverySessionStorage.get(Binder.getCallingUid(), TEST_SESSION_ID)
.getKeyClaimant();
SecretKey recoveryKey = randomRecoveryKey();
byte[] encryptedClaimResponse = encryptClaimResponse(
keyClaimant, TEST_SECRET, TEST_VAULT_PARAMS, recoveryKey);
- KeyEntryRecoveryData badApplicationKey = new KeyEntryRecoveryData(
+ EntryRecoveryData badApplicationKey = new EntryRecoveryData(
TEST_ALIAS,
randomBytes(32));
@@ -395,10 +419,10 @@
TEST_PUBLIC_KEY,
TEST_VAULT_PARAMS,
TEST_VAULT_CHALLENGE,
- ImmutableList.of(new KeyStoreRecoveryMetadata(
+ ImmutableList.of(new RecoveryMetadata(
TYPE_LOCKSCREEN,
TYPE_PASSWORD,
- KeyDerivationParameters.createSha256Parameters(TEST_SALT),
+ KeyDerivationParams.createSha256Params(TEST_SALT),
TEST_SECRET)));
byte[] keyClaimant = mRecoverySessionStorage.get(Binder.getCallingUid(), TEST_SESSION_ID)
.getKeyClaimant();
@@ -406,7 +430,7 @@
byte[] encryptedClaimResponse = encryptClaimResponse(
keyClaimant, TEST_SECRET, TEST_VAULT_PARAMS, recoveryKey);
byte[] applicationKeyBytes = randomBytes(32);
- KeyEntryRecoveryData applicationKey = new KeyEntryRecoveryData(
+ EntryRecoveryData applicationKey = new EntryRecoveryData(
TEST_ALIAS,
encryptedApplicationKey(recoveryKey, applicationKeyBytes));
@@ -449,6 +473,20 @@
}
@Test
+ public void setRecoverySecretTypes_updatesShouldCreateSnapshot() throws Exception {
+ int uid = Binder.getCallingUid();
+ int userId = UserHandle.getCallingUserId();
+ int[] types = new int[]{1, 2, 3};
+
+ mRecoverableKeyStoreManager.generateAndStoreKey(TEST_ALIAS);
+ // Pretend that key was synced
+ mRecoverableKeyStoreDb.setShouldCreateSnapshot(userId, uid, false);
+ mRecoverableKeyStoreManager.setRecoverySecretTypes(types);
+
+ assertThat(mRecoverableKeyStoreDb.getShouldCreateSnapshot(userId, uid)).isTrue();
+ }
+
+ @Test
public void setRecoveryStatus_forOneAlias() throws Exception {
int userId = UserHandle.getCallingUserId();
int uid = Binder.getCallingUid();
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbTest.java
index b8080ab..5cb7b67 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbTest.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbTest.java
@@ -29,7 +29,7 @@
import android.content.Context;
import android.content.SharedPreferences;
-import android.security.recoverablekeystore.RecoverableKeyStoreLoader;
+import android.security.keystore.RecoveryManager;
import android.support.test.InstrumentationRegistry;
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
@@ -51,6 +51,12 @@
private RecoverableKeyStoreDb mRecoverableKeyStoreDb;
private File mDatabaseFile;
+ private static final byte[] SERVER_PARAMS =
+ new byte[]{1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2};
+
+ private static final byte[] SERVER_PARAMS2 =
+ new byte[]{1, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4};
+
@Before
public void setUp() {
Context context = InstrumentationRegistry.getTargetContext();
@@ -277,8 +283,7 @@
Map<String, Integer> statuses = mRecoverableKeyStoreDb.getStatusForAllKeys(uid);
assertThat(statuses).hasSize(3);
- assertThat(statuses).containsEntry(alias,
- RecoverableKeyStoreLoader.RECOVERY_STATUS_SYNC_IN_PROGRESS);
+ assertThat(statuses).containsEntry(alias, RecoveryManager.RECOVERY_STATUS_SYNC_IN_PROGRESS);
assertThat(statuses).containsEntry(alias2, status);
assertThat(statuses).containsEntry(alias3, status);
@@ -329,8 +334,7 @@
int uid = 10009;
assertThat(mRecoverableKeyStoreDb.getRecoveryServicePublicKey(userId, uid)).isNull();
- long serverParams = 123456L;
- mRecoverableKeyStoreDb.setServerParameters(userId, uid, serverParams);
+ mRecoverableKeyStoreDb.setServerParams(userId, uid, SERVER_PARAMS);
assertThat(mRecoverableKeyStoreDb.getRecoveryServicePublicKey(userId, uid)).isNull();
}
@@ -438,32 +442,30 @@
PublicKey pubkey1 = genRandomPublicKey();
int[] types1 = new int[]{1};
- long serverParams1 = 111L;
PublicKey pubkey2 = genRandomPublicKey();
int[] types2 = new int[]{2};
- long serverParams2 = 222L;
mRecoverableKeyStoreDb.setRecoveryServicePublicKey(userId, uid, pubkey1);
mRecoverableKeyStoreDb.setRecoverySecretTypes(userId, uid, types1);
- mRecoverableKeyStoreDb.setServerParameters(userId, uid, serverParams1);
+ mRecoverableKeyStoreDb.setServerParams(userId, uid, SERVER_PARAMS);
assertThat(mRecoverableKeyStoreDb.getRecoverySecretTypes(userId, uid)).isEqualTo(
types1);
- assertThat(mRecoverableKeyStoreDb.getServerParameters(userId, uid)).isEqualTo(
- serverParams1);
+ assertThat(mRecoverableKeyStoreDb.getServerParams(userId, uid)).isEqualTo(
+ SERVER_PARAMS);
assertThat(mRecoverableKeyStoreDb.getRecoveryServicePublicKey(userId, uid)).isEqualTo(
pubkey1);
// Check that the methods don't interfere with each other.
mRecoverableKeyStoreDb.setRecoveryServicePublicKey(userId, uid, pubkey2);
mRecoverableKeyStoreDb.setRecoverySecretTypes(userId, uid, types2);
- mRecoverableKeyStoreDb.setServerParameters(userId, uid, serverParams2);
+ mRecoverableKeyStoreDb.setServerParams(userId, uid, SERVER_PARAMS2);
assertThat(mRecoverableKeyStoreDb.getRecoverySecretTypes(userId, uid)).isEqualTo(
types2);
- assertThat(mRecoverableKeyStoreDb.getServerParameters(userId, uid)).isEqualTo(
- serverParams2);
+ assertThat(mRecoverableKeyStoreDb.getServerParams(userId, uid)).isEqualTo(
+ SERVER_PARAMS2);
assertThat(mRecoverableKeyStoreDb.getRecoveryServicePublicKey(userId, uid)).isEqualTo(
pubkey2);
}
@@ -480,35 +482,33 @@
}
@Test
- public void setServerParameters_replaceOldValue() throws Exception {
+ public void setServerParams_replaceOldValue() throws Exception {
int userId = 12;
int uid = 10009;
- long serverParams1 = 111L;
- long serverParams2 = 222L;
- mRecoverableKeyStoreDb.setServerParameters(userId, uid, serverParams1);
- mRecoverableKeyStoreDb.setServerParameters(userId, uid, serverParams2);
- assertThat(mRecoverableKeyStoreDb.getServerParameters(userId, uid)).isEqualTo(
- serverParams2);
+
+ mRecoverableKeyStoreDb.setServerParams(userId, uid, SERVER_PARAMS);
+ mRecoverableKeyStoreDb.setServerParams(userId, uid, SERVER_PARAMS2);
+ assertThat(mRecoverableKeyStoreDb.getServerParams(userId, uid)).isEqualTo(
+ SERVER_PARAMS2);
}
@Test
- public void getServerParameters_returnsNullIfNoValue() throws Exception {
+ public void getServerParams_returnsNullIfNoValue() throws Exception {
int userId = 12;
int uid = 10009;
- assertThat(mRecoverableKeyStoreDb.getServerParameters(userId, uid)).isNull();
+ assertThat(mRecoverableKeyStoreDb.getServerParams(userId, uid)).isNull();
PublicKey pubkey = genRandomPublicKey();
mRecoverableKeyStoreDb.setRecoveryServicePublicKey(userId, uid, pubkey);
- assertThat(mRecoverableKeyStoreDb.getServerParameters(userId, uid)).isNull();
+ assertThat(mRecoverableKeyStoreDb.getServerParams(userId, uid)).isNull();
}
@Test
- public void getServerParameters_returnsInsertedValue() throws Exception {
+ public void getServerParams_returnsInsertedValue() throws Exception {
int userId = 12;
int uid = 10009;
- long serverParams = 123456L;
- mRecoverableKeyStoreDb.setServerParameters(userId, uid, serverParams);
- assertThat(mRecoverableKeyStoreDb.getServerParameters(userId, uid)).isEqualTo(serverParams);
+ mRecoverableKeyStoreDb.setServerParams(userId, uid, SERVER_PARAMS);
+ assertThat(mRecoverableKeyStoreDb.getServerParams(userId, uid)).isEqualTo(SERVER_PARAMS);
}
@Test
@@ -591,22 +591,21 @@
int uid = 10009;
PublicKey pubkey1 = genRandomPublicKey();
PublicKey pubkey2 = genRandomPublicKey();
- long serverParams = 123456L;
mRecoverableKeyStoreDb.setRecoveryServicePublicKey(userId, uid, pubkey1);
assertThat(mRecoverableKeyStoreDb.getRecoveryServicePublicKey(userId, uid)).isEqualTo(
pubkey1);
- assertThat(mRecoverableKeyStoreDb.getServerParameters(userId, uid)).isNull();
+ assertThat(mRecoverableKeyStoreDb.getServerParams(userId, uid)).isNull();
- mRecoverableKeyStoreDb.setServerParameters(userId, uid, serverParams);
+ mRecoverableKeyStoreDb.setServerParams(userId, uid, SERVER_PARAMS);
assertThat(mRecoverableKeyStoreDb.getRecoveryServicePublicKey(userId, uid)).isEqualTo(
pubkey1);
- assertThat(mRecoverableKeyStoreDb.getServerParameters(userId, uid)).isEqualTo(serverParams);
+ assertThat(mRecoverableKeyStoreDb.getServerParams(userId, uid)).isEqualTo(SERVER_PARAMS);
mRecoverableKeyStoreDb.setRecoveryServicePublicKey(userId, uid, pubkey2);
assertThat(mRecoverableKeyStoreDb.getRecoveryServicePublicKey(userId, uid)).isEqualTo(
pubkey2);
- assertThat(mRecoverableKeyStoreDb.getServerParameters(userId, uid)).isEqualTo(serverParams);
+ assertThat(mRecoverableKeyStoreDb.getServerParams(userId, uid)).isEqualTo(SERVER_PARAMS);
}
private static byte[] getUtf8Bytes(String s) {
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/RecoverySnapshotStorageTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/RecoverySnapshotStorageTest.java
index 2759e39..6308f74 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/RecoverySnapshotStorageTest.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/RecoverySnapshotStorageTest.java
@@ -3,7 +3,7 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
-import android.security.recoverablekeystore.KeyStoreRecoveryData;
+import android.security.keystore.RecoveryData;
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
@@ -26,7 +26,7 @@
@Test
public void get_returnsSetSnapshot() {
int userId = 1000;
- KeyStoreRecoveryData recoveryData = new KeyStoreRecoveryData(
+ RecoveryData recoveryData = new RecoveryData(
/*snapshotVersion=*/ 1,
new ArrayList<>(),
new ArrayList<>(),
@@ -39,7 +39,7 @@
@Test
public void remove_removesSnapshots() {
int userId = 1000;
- KeyStoreRecoveryData recoveryData = new KeyStoreRecoveryData(
+ RecoveryData recoveryData = new RecoveryData(
/*snapshotVersion=*/ 1,
new ArrayList<>(),
new ArrayList<>(),
diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java b/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java
index b073ee5..0abb48f 100644
--- a/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java
+++ b/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java
@@ -294,6 +294,7 @@
null /*disabledPkg*/,
null /*sharedUser*/,
UPDATED_CODE_PATH /*codePath*/,
+ null /*resourcePath*/,
null /*legacyNativeLibraryPath*/,
"arm64-v8a" /*primaryCpuAbi*/,
"armeabi" /*secondaryCpuAbi*/,
@@ -327,6 +328,7 @@
null /*disabledPkg*/,
null /*sharedUser*/,
UPDATED_CODE_PATH /*codePath*/,
+ null /*resourcePath*/,
null /*legacyNativeLibraryPath*/,
"arm64-v8a" /*primaryCpuAbi*/,
"armeabi" /*secondaryCpuAbi*/,
@@ -367,6 +369,7 @@
null /*disabledPkg*/,
testUserSetting01 /*sharedUser*/,
UPDATED_CODE_PATH /*codePath*/,
+ null /*resourcePath*/,
null /*legacyNativeLibraryPath*/,
"arm64-v8a" /*primaryCpuAbi*/,
"armeabi" /*secondaryCpuAbi*/,
diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageParserTest.java b/services/tests/servicestests/src/com/android/server/pm/PackageParserTest.java
index 32b0b26..49601c3 100644
--- a/services/tests/servicestests/src/com/android/server/pm/PackageParserTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/PackageParserTest.java
@@ -34,7 +34,6 @@
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.nio.charset.StandardCharsets;
-import java.security.cert.Certificate;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
@@ -262,14 +261,13 @@
assertBundleApproximateEquals(a.mAppMetaData, b.mAppMetaData);
assertEquals(a.mVersionName, b.mVersionName);
assertEquals(a.mSharedUserId, b.mSharedUserId);
- assertTrue(Arrays.equals(a.mSignatures, b.mSignatures));
- assertTrue(Arrays.equals(a.mCertificates, b.mCertificates));
+ assertTrue(Arrays.equals(a.mSigningDetails.signatures, b.mSigningDetails.signatures));
assertTrue(Arrays.equals(a.mLastPackageUsageTimeInMills, b.mLastPackageUsageTimeInMills));
assertEquals(a.mExtras, b.mExtras);
assertEquals(a.mRestrictedAccountType, b.mRestrictedAccountType);
assertEquals(a.mRequiredAccountType, b.mRequiredAccountType);
assertEquals(a.mOverlayTarget, b.mOverlayTarget);
- assertEquals(a.mSigningKeys, b.mSigningKeys);
+ assertEquals(a.mSigningDetails.publicKeys, b.mSigningDetails.publicKeys);
assertEquals(a.mUpgradeKeySets, b.mUpgradeKeySets);
assertEquals(a.mKeySetMapping, b.mKeySetMapping);
assertEquals(a.cpuAbiOverride, b.cpuAbiOverride);
@@ -495,14 +493,16 @@
pkg.mAppMetaData = new Bundle();
pkg.mVersionName = "foo17";
pkg.mSharedUserId = "foo18";
- pkg.mSignatures = new Signature[] { new Signature(new byte[16]) };
- pkg.mCertificates = new Certificate[][] { new Certificate[] { null }};
+ pkg.mSigningDetails =
+ new PackageParser.SigningDetails(
+ new Signature[] { new Signature(new byte[16]) },
+ 2,
+ new ArraySet<>());
pkg.mExtras = new Bundle();
pkg.mRestrictedAccountType = "foo19";
pkg.mRequiredAccountType = "foo20";
pkg.mOverlayTarget = "foo21";
pkg.mOverlayPriority = 100;
- pkg.mSigningKeys = new ArraySet<>();
pkg.mUpgradeKeySets = new ArraySet<>();
pkg.mKeySetMapping = new ArrayMap<>();
pkg.cpuAbiOverride = "foo22";
diff --git a/services/tests/servicestests/src/com/android/server/policy/FakeWindowState.java b/services/tests/servicestests/src/com/android/server/policy/FakeWindowState.java
index 62f7cd0..a628b7b 100644
--- a/services/tests/servicestests/src/com/android/server/policy/FakeWindowState.java
+++ b/services/tests/servicestests/src/com/android/server/policy/FakeWindowState.java
@@ -19,6 +19,7 @@
import android.annotation.Nullable;
import android.graphics.Point;
import android.graphics.Rect;
+import android.util.proto.ProtoOutputStream;
import android.view.Display;
import android.view.DisplayCutout;
import android.view.IApplicationToken;
@@ -254,4 +255,9 @@
public boolean canAcquireSleepToken() {
throw new UnsupportedOperationException("not implemented");
}
+
+ @Override
+ public void writeIdentifierToProto(ProtoOutputStream proto, long fieldId){
+ throw new UnsupportedOperationException("not implemented");
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/policy/PhoneWindowManagerLayoutTest.java b/services/tests/servicestests/src/com/android/server/policy/PhoneWindowManagerLayoutTest.java
index 9a6da0e..1ad73cf 100644
--- a/services/tests/servicestests/src/com/android/server/policy/PhoneWindowManagerLayoutTest.java
+++ b/services/tests/servicestests/src/com/android/server/policy/PhoneWindowManagerLayoutTest.java
@@ -19,6 +19,7 @@
import static android.view.Surface.ROTATION_180;
import static android.view.Surface.ROTATION_270;
import static android.view.Surface.ROTATION_90;
+import static android.view.View.SYSTEM_UI_FLAG_FULLSCREEN;
import static android.view.View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN;
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
import static android.view.WindowManager.LayoutParams.FLAG2_LAYOUT_IN_DISPLAY_CUTOUT_AREA;
@@ -128,7 +129,7 @@
}
@Test
- public void layoutWindowLw_withDisplayCutout_fullscreen() {
+ public void layoutWindowLw_withDisplayCutout_layoutFullscreen() {
addDisplayCutout();
mAppWindow.attrs.subtreeSystemUiVisibility |= SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN;
@@ -137,6 +138,22 @@
mPolicy.beginLayoutLw(mFrames, 0 /* UI mode */);
mPolicy.layoutWindowLw(mAppWindow, null, mFrames);
+ assertInsetByTopBottom(mAppWindow.parentFrame, 0, 0);
+ assertInsetByTopBottom(mAppWindow.stableFrame, STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT);
+ assertInsetByTopBottom(mAppWindow.contentFrame, STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT);
+ assertInsetByTopBottom(mAppWindow.decorFrame, 0, 0);
+ }
+
+ @Test
+ public void layoutWindowLw_withDisplayCutout_fullscreen() {
+ addDisplayCutout();
+
+ mAppWindow.attrs.subtreeSystemUiVisibility |= SYSTEM_UI_FLAG_FULLSCREEN;
+ mPolicy.addWindow(mAppWindow);
+
+ mPolicy.beginLayoutLw(mFrames, 0 /* UI mode */);
+ mPolicy.layoutWindowLw(mAppWindow, null, mFrames);
+
assertInsetByTopBottom(mAppWindow.parentFrame, STATUS_BAR_HEIGHT, 0);
assertInsetByTopBottom(mAppWindow.stableFrame, STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT);
assertInsetByTopBottom(mAppWindow.contentFrame, STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT);
@@ -147,7 +164,7 @@
public void layoutWindowLw_withDisplayCutout_fullscreenInCutout() {
addDisplayCutout();
- mAppWindow.attrs.subtreeSystemUiVisibility |= SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN;
+ mAppWindow.attrs.subtreeSystemUiVisibility |= SYSTEM_UI_FLAG_FULLSCREEN;
mAppWindow.attrs.flags2 |= FLAG2_LAYOUT_IN_DISPLAY_CUTOUT_AREA;
mPolicy.addWindow(mAppWindow);
diff --git a/services/tests/servicestests/src/com/android/server/policy/PhoneWindowManagerTestBase.java b/services/tests/servicestests/src/com/android/server/policy/PhoneWindowManagerTestBase.java
index e7e9abad..ad89953 100644
--- a/services/tests/servicestests/src/com/android/server/policy/PhoneWindowManagerTestBase.java
+++ b/services/tests/servicestests/src/com/android/server/policy/PhoneWindowManagerTestBase.java
@@ -24,6 +24,8 @@
import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR;
import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR;
+import static com.android.server.wm.utils.CoordinateTransforms.transformPhysicalToLogicalCoordinates;
+
import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.mock;
@@ -31,6 +33,8 @@
import android.content.ContextWrapper;
import android.content.pm.PackageManager;
import android.content.res.Resources;
+import android.graphics.Matrix;
+import android.graphics.Path;
import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.os.IBinder;
@@ -38,6 +42,7 @@
import android.support.test.InstrumentationRegistry;
import android.testing.TestableResources;
import android.view.Display;
+import android.view.DisplayCutout;
import android.view.DisplayInfo;
import android.view.Gravity;
import android.view.View;
@@ -65,6 +70,9 @@
FakeWindowState mStatusBar;
FakeWindowState mNavigationBar;
+ private boolean mHasDisplayCutout;
+ private int mRotation = ROTATION_0;
+ private final Matrix mTmpMatrix = new Matrix();
@Before
public void setUpBase() throws Exception {
@@ -80,16 +88,32 @@
mPolicy = TestablePhoneWindowManager.create(mContext);
- setRotation(ROTATION_0);
+ updateDisplayFrames();
}
public void setRotation(int rotation) {
+ mRotation = rotation;
+ updateDisplayFrames();
+ }
+
+ private void updateDisplayFrames() {
DisplayInfo info = new DisplayInfo();
- final boolean flippedDimensions = rotation == ROTATION_90 || rotation == ROTATION_270;
+ final boolean flippedDimensions = mRotation == ROTATION_90 || mRotation == ROTATION_270;
info.logicalWidth = flippedDimensions ? DISPLAY_HEIGHT : DISPLAY_WIDTH;
info.logicalHeight = flippedDimensions ? DISPLAY_WIDTH : DISPLAY_HEIGHT;
- info.rotation = rotation;
+ info.rotation = mRotation;
+ if (mHasDisplayCutout) {
+ Path p = new Path();
+ p.addRect(DISPLAY_WIDTH / 4, 0, DISPLAY_WIDTH * 3 / 4, DISPLAY_CUTOUT_HEIGHT,
+ Path.Direction.CCW);
+ transformPhysicalToLogicalCoordinates(
+ mRotation, DISPLAY_WIDTH, DISPLAY_HEIGHT, mTmpMatrix);
+ p.transform(mTmpMatrix);
+ info.displayCutout = DisplayCutout.fromBounds(p);
+ } else {
+ info.displayCutout = null;
+ }
mFrames = new DisplayFrames(Display.DEFAULT_DISPLAY, info);
}
@@ -116,7 +140,8 @@
}
public void addDisplayCutout() {
- mPolicy.mEmulateDisplayCutout = true;
+ mHasDisplayCutout = true;
+ updateDisplayFrames();
}
/** Asserts that {@code actual} is inset by the given amounts from the full display rect. */
diff --git a/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java b/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java
index 725fb21..40964c0 100644
--- a/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java
+++ b/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java
@@ -34,13 +34,13 @@
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Matchers.anyString;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import android.app.usage.UsageEvents;
-import android.app.usage.UsageStatsManager;
import android.appwidget.AppWidgetManager;
import android.content.Context;
import android.content.ContextWrapper;
@@ -55,6 +55,7 @@
import android.support.test.filters.SmallTest;
import android.support.test.InstrumentationRegistry;
import android.support.test.runner.AndroidJUnit4;
+import android.util.ArraySet;
import android.view.Display;
import com.android.server.SystemService;
@@ -65,7 +66,9 @@
import java.io.File;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.List;
+import java.util.Set;
/**
* Unit test for AppStandbyController.
@@ -78,6 +81,11 @@
private static final String PACKAGE_1 = "com.example.foo";
private static final int UID_1 = 10000;
private static final int USER_ID = 0;
+ private static final int USER_ID2 = 10;
+
+ private static final String ADMIN_PKG = "com.android.admin";
+ private static final String ADMIN_PKG2 = "com.android.admin2";
+ private static final String ADMIN_PKG3 = "com.android.admin3";
private static final long MINUTE_MS = 60 * 1000;
private static final long HOUR_MS = 60 * MINUTE_MS;
@@ -454,6 +462,105 @@
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_FREQUENT,
REASON_PREDICTED, mInjector.mElapsedRealtime);
assertBucket(STANDBY_BUCKET_FREQUENT);
+ }
+ @Test
+ public void testAddActiveDeviceAdmin() {
+ assertActiveAdmins(USER_ID, (String[]) null);
+ assertActiveAdmins(USER_ID2, (String[]) null);
+
+ mController.addActiveDeviceAdmin(ADMIN_PKG, USER_ID);
+ assertActiveAdmins(USER_ID, ADMIN_PKG);
+ assertActiveAdmins(USER_ID2, (String[]) null);
+
+ mController.addActiveDeviceAdmin(ADMIN_PKG, USER_ID);
+ assertActiveAdmins(USER_ID, ADMIN_PKG);
+ assertActiveAdmins(USER_ID2, (String[]) null);
+
+ mController.addActiveDeviceAdmin(ADMIN_PKG2, USER_ID2);
+ assertActiveAdmins(USER_ID, ADMIN_PKG);
+ assertActiveAdmins(USER_ID2, ADMIN_PKG2);
+ }
+
+ @Test
+ public void testSetActiveAdminApps() {
+ assertActiveAdmins(USER_ID, (String[]) null);
+ assertActiveAdmins(USER_ID2, (String[]) null);
+
+ setActiveAdmins(USER_ID, ADMIN_PKG, ADMIN_PKG2);
+ assertActiveAdmins(USER_ID, ADMIN_PKG, ADMIN_PKG2);
+ assertActiveAdmins(USER_ID2, (String[]) null);
+
+ mController.addActiveDeviceAdmin(ADMIN_PKG2, USER_ID2);
+ setActiveAdmins(USER_ID2, ADMIN_PKG);
+ assertActiveAdmins(USER_ID, ADMIN_PKG, ADMIN_PKG2);
+ assertActiveAdmins(USER_ID2, ADMIN_PKG);
+
+ mController.setActiveAdminApps(null, USER_ID);
+ assertActiveAdmins(USER_ID, (String[]) null);
+ }
+
+ @Test
+ public void isActiveDeviceAdmin() {
+ assertActiveAdmins(USER_ID, (String[]) null);
+ assertActiveAdmins(USER_ID2, (String[]) null);
+
+ mController.addActiveDeviceAdmin(ADMIN_PKG, USER_ID);
+ assertIsActiveAdmin(ADMIN_PKG, USER_ID);
+ assertIsNotActiveAdmin(ADMIN_PKG, USER_ID2);
+
+ mController.addActiveDeviceAdmin(ADMIN_PKG2, USER_ID2);
+ mController.addActiveDeviceAdmin(ADMIN_PKG, USER_ID2);
+ assertIsActiveAdmin(ADMIN_PKG, USER_ID);
+ assertIsNotActiveAdmin(ADMIN_PKG2, USER_ID);
+ assertIsActiveAdmin(ADMIN_PKG, USER_ID2);
+ assertIsActiveAdmin(ADMIN_PKG2, USER_ID2);
+
+ setActiveAdmins(USER_ID2, ADMIN_PKG2);
+ assertIsActiveAdmin(ADMIN_PKG2, USER_ID2);
+ assertIsNotActiveAdmin(ADMIN_PKG, USER_ID2);
+ assertIsActiveAdmin(ADMIN_PKG, USER_ID);
+ assertIsNotActiveAdmin(ADMIN_PKG2, USER_ID);
+ }
+
+ private String getAdminAppsStr(int userId) {
+ return getAdminAppsStr(userId, mController.getActiveAdminAppsForTest(userId));
+ }
+
+ private String getAdminAppsStr(int userId, Set<String> adminApps) {
+ return "admin apps for u" + userId + ": "
+ + (adminApps == null ? "null" : Arrays.toString(adminApps.toArray()));
+ }
+
+ private void assertIsActiveAdmin(String adminApp, int userId) {
+ assertTrue(adminApp + " should be an active admin; " + getAdminAppsStr(userId),
+ mController.isActiveDeviceAdmin(adminApp, userId));
+ }
+
+ private void assertIsNotActiveAdmin(String adminApp, int userId) {
+ assertFalse(adminApp + " shouldn't be an active admin; " + getAdminAppsStr(userId),
+ mController.isActiveDeviceAdmin(adminApp, userId));
+ }
+
+ private void assertActiveAdmins(int userId, String... admins) {
+ final Set<String> actualAdminApps = mController.getActiveAdminAppsForTest(userId);
+ if (admins == null) {
+ if (actualAdminApps != null && !actualAdminApps.isEmpty()) {
+ fail("Admin apps should be null; " + getAdminAppsStr(userId, actualAdminApps));
+ }
+ return;
+ }
+ assertEquals("No. of admin apps not equal; " + getAdminAppsStr(userId, actualAdminApps)
+ + "; expected=" + Arrays.toString(admins), admins.length, actualAdminApps.size());
+ final Set<String> adminAppsCopy = new ArraySet<>(actualAdminApps);
+ for (String admin : admins) {
+ adminAppsCopy.remove(admin);
+ }
+ assertTrue("Unexpected admin apps; " + getAdminAppsStr(userId, actualAdminApps)
+ + "; expected=" + Arrays.toString(admins), adminAppsCopy.isEmpty());
+ }
+
+ private void setActiveAdmins(int userId, String... admins) {
+ mController.setActiveAdminApps(new ArraySet<>(Arrays.asList(admins)), userId);
}
}
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 a317706..5ed17cc 100644
--- a/services/tests/servicestests/src/com/android/server/wm/TestWindowManagerPolicy.java
+++ b/services/tests/servicestests/src/com/android/server/wm/TestWindowManagerPolicy.java
@@ -416,7 +416,8 @@
}
@Override
- public void dismissKeyguardLw(@Nullable IKeyguardDismissCallback callback) {
+ public void dismissKeyguardLw(@Nullable IKeyguardDismissCallback callback,
+ CharSequence message) {
}
@Override
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
index 0c7397a..c532a8a 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
@@ -107,6 +107,68 @@
}
@Test
+ public void testAlarmsOnly_alarmMediaMuteNotApplied() {
+ mZenModeHelperSpy.mZenMode = Settings.Global.ZEN_MODE_ALARMS;
+ mZenModeHelperSpy.mConfig.allowAlarms = false;
+ mZenModeHelperSpy.mConfig.allowMediaSystemOther = false;
+ assertFalse(mZenModeHelperSpy.mConfig.allowAlarms);
+ assertFalse(mZenModeHelperSpy.mConfig.allowMediaSystemOther);
+ mZenModeHelperSpy.applyRestrictions();
+
+ // Alarms only mode will not silence alarms
+ verify(mZenModeHelperSpy, atLeastOnce()).applyRestrictions(false,
+ AudioAttributes.USAGE_ALARM);
+
+ // Alarms only mode will not silence media
+ verify(mZenModeHelperSpy, atLeastOnce()).applyRestrictions(false,
+ AudioAttributes.USAGE_MEDIA);
+ verify(mZenModeHelperSpy, atLeastOnce()).applyRestrictions(false,
+ AudioAttributes.USAGE_GAME);
+ verify(mZenModeHelperSpy, atLeastOnce()).applyRestrictions(false,
+ AudioAttributes.USAGE_ASSISTANCE_SONIFICATION);
+ verify(mZenModeHelperSpy, atLeastOnce()).applyRestrictions(false,
+ AudioAttributes.USAGE_UNKNOWN);
+ }
+
+ @Test
+ public void testAlarmsOnly_callsMuteApplied() {
+ mZenModeHelperSpy.mZenMode = Settings.Global.ZEN_MODE_ALARMS;
+ mZenModeHelperSpy.mConfig.allowCalls = true;
+ assertTrue(mZenModeHelperSpy.mConfig.allowCalls);
+ mZenModeHelperSpy.applyRestrictions();
+
+ // Alarms only mode will silence calls despite priority-mode config
+ verify(mZenModeHelperSpy, atLeastOnce()).applyRestrictions(true,
+ AudioAttributes.USAGE_NOTIFICATION_RINGTONE);
+ verify(mZenModeHelperSpy, atLeastOnce()).applyRestrictions(true,
+ AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_REQUEST);
+ }
+
+ @Test
+ public void testAlarmsOnly_allZenConfigToggledCannotBypass_alarmMuteNotApplied() {
+ // Only audio attributes with SUPPRESIBLE_NEVER can bypass
+ mZenModeHelperSpy.mZenMode = Settings.Global.ZEN_MODE_ALARMS;
+ mZenModeHelperSpy.mConfig.allowAlarms = false;
+ mZenModeHelperSpy.mConfig.allowMediaSystemOther = false;
+ mZenModeHelperSpy.mConfig.allowReminders = false;
+ mZenModeHelperSpy.mConfig.allowCalls = false;
+ mZenModeHelperSpy.mConfig.allowMessages = false;
+ mZenModeHelperSpy.mConfig.allowEvents = false;
+ mZenModeHelperSpy.mConfig.allowRepeatCallers= false;
+ assertFalse(mZenModeHelperSpy.mConfig.allowAlarms);
+ assertFalse(mZenModeHelperSpy.mConfig.allowMediaSystemOther);
+ assertFalse(mZenModeHelperSpy.mConfig.allowReminders);
+ assertFalse(mZenModeHelperSpy.mConfig.allowCalls);
+ assertFalse(mZenModeHelperSpy.mConfig.allowMessages);
+ assertFalse(mZenModeHelperSpy.mConfig.allowEvents);
+ assertFalse(mZenModeHelperSpy.mConfig.allowRepeatCallers);
+ mZenModeHelperSpy.applyRestrictions();
+
+ verify(mZenModeHelperSpy, atLeastOnce()).applyRestrictions(false,
+ AudioAttributes.USAGE_ALARM);
+ }
+
+ @Test
public void testZenAllCannotBypass() {
// Only audio attributes with SUPPRESIBLE_NEVER can bypass
mZenModeHelperSpy.mZenMode = Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS;
diff --git a/services/usage/java/com/android/server/usage/AppStandbyController.java b/services/usage/java/com/android/server/usage/AppStandbyController.java
index 2ec218a..ff3d586 100644
--- a/services/usage/java/com/android/server/usage/AppStandbyController.java
+++ b/services/usage/java/com/android/server/usage/AppStandbyController.java
@@ -33,7 +33,6 @@
import android.app.ActivityManager;
import android.app.AppGlobals;
-import android.app.admin.DevicePolicyManager;
import android.app.usage.UsageStatsManager.StandbyBuckets;
import android.app.usage.UsageEvents;
import android.app.usage.UsageStatsManagerInternal.AppIdleStateChangeListener;
@@ -66,13 +65,16 @@
import android.os.UserHandle;
import android.provider.Settings;
import android.telephony.TelephonyManager;
+import android.util.ArraySet;
import android.util.KeyValueListParser;
import android.util.Slog;
+import android.util.SparseArray;
import android.util.SparseIntArray;
import android.util.TimeUtils;
import android.view.Display;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.app.IBatteryStats;
import com.android.internal.os.SomeArgs;
import com.android.internal.util.ArrayUtils;
@@ -87,6 +89,7 @@
import java.util.Arrays;
import java.util.List;
import java.util.Map;
+import java.util.Set;
/**
* Manages the standby state of an app, listening to various events.
@@ -147,6 +150,9 @@
@GuardedBy("mAppIdleLock")
private List<String> mCarrierPrivilegedApps;
+ @GuardedBy("mActiveAdminApps")
+ private final SparseArray<Set<String>> mActiveAdminApps = new SparseArray<>();
+
// Messages for the handler
static final int MSG_INFORM_LISTENERS = 3;
static final int MSG_FORCE_IDLE_STATE = 4;
@@ -619,6 +625,9 @@
public void onUserRemoved(int userId) {
synchronized (mAppIdleLock) {
mAppIdleHistory.onUserRemoved(userId);
+ synchronized (mActiveAdminApps) {
+ mActiveAdminApps.remove(userId);
+ }
}
}
@@ -857,10 +866,39 @@
newBucket);
}
- private boolean isActiveDeviceAdmin(String packageName, int userId) {
- DevicePolicyManager dpm = mContext.getSystemService(DevicePolicyManager.class);
- if (dpm == null) return false;
- return dpm.packageHasActiveAdmins(packageName, userId);
+ @VisibleForTesting
+ boolean isActiveDeviceAdmin(String packageName, int userId) {
+ synchronized (mActiveAdminApps) {
+ final Set<String> adminPkgs = mActiveAdminApps.get(userId);
+ return adminPkgs != null && adminPkgs.contains(packageName);
+ }
+ }
+
+ public void addActiveDeviceAdmin(String adminPkg, int userId) {
+ synchronized (mActiveAdminApps) {
+ Set<String> adminPkgs = mActiveAdminApps.get(userId);
+ if (adminPkgs == null) {
+ adminPkgs = new ArraySet<>();
+ mActiveAdminApps.put(userId, adminPkgs);
+ }
+ adminPkgs.add(adminPkg);
+ }
+ }
+
+ public void setActiveAdminApps(Set<String> adminPkgs, int userId) {
+ synchronized (mActiveAdminApps) {
+ if (adminPkgs == null) {
+ mActiveAdminApps.remove(userId);
+ } else {
+ mActiveAdminApps.put(userId, adminPkgs);
+ }
+ }
+ }
+
+ Set<String> getActiveAdminAppsForTest(int userId) {
+ synchronized (mActiveAdminApps) {
+ return mActiveAdminApps.get(userId);
+ }
}
/**
diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java
index 463a26e..78cc81f 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsService.java
@@ -70,6 +70,7 @@
import java.util.Arrays;
import java.util.List;
import java.util.Map;
+import java.util.Set;
/**
* A service that collects, aggregates, and persists application usage data.
@@ -1020,5 +1021,14 @@
public long getTimeSinceLastJobRun(String packageName, int userId) {
return mAppStandby.getTimeSinceLastJobRun(packageName, userId);
}
+
+ public void onActiveAdminAdded(String packageName, int userId) {
+ mAppStandby.addActiveDeviceAdmin(packageName, userId);
+ }
+
+ @Override
+ public void setActiveAdminApps(Set<String> packageNames, int userId) {
+ mAppStandby.setActiveAdminApps(packageNames, userId);
+ }
}
}
diff --git a/telecomm/java/android/telecom/Call.java b/telecomm/java/android/telecom/Call.java
index 2091101..8c7d6b3 100644
--- a/telecomm/java/android/telecom/Call.java
+++ b/telecomm/java/android/telecom/Call.java
@@ -1408,7 +1408,7 @@
* @param extras Bundle containing extra information associated with the event.
*/
public void sendCallEvent(String event, Bundle extras) {
- mInCallAdapter.sendCallEvent(mTelecomCallId, event, extras);
+ mInCallAdapter.sendCallEvent(mTelecomCallId, event, mTargetSdkVersion, extras);
}
/**
diff --git a/telecomm/java/android/telecom/InCallAdapter.java b/telecomm/java/android/telecom/InCallAdapter.java
index 4bc2a9b..658685f 100644
--- a/telecomm/java/android/telecom/InCallAdapter.java
+++ b/telecomm/java/android/telecom/InCallAdapter.java
@@ -286,11 +286,12 @@
*
* @param callId The callId to send the event for.
* @param event The event.
+ * @param targetSdkVer Target sdk version of the app calling this api
* @param extras Extras associated with the event.
*/
- public void sendCallEvent(String callId, String event, Bundle extras) {
+ public void sendCallEvent(String callId, String event, int targetSdkVer, Bundle extras) {
try {
- mAdapter.sendCallEvent(callId, event, extras);
+ mAdapter.sendCallEvent(callId, event, targetSdkVer, extras);
} catch (RemoteException ignored) {
}
}
diff --git a/telecomm/java/android/telecom/TelecomManager.java b/telecomm/java/android/telecom/TelecomManager.java
index bddf3ea..d292db3 100644
--- a/telecomm/java/android/telecom/TelecomManager.java
+++ b/telecomm/java/android/telecom/TelecomManager.java
@@ -24,6 +24,7 @@
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
+import android.os.Build;
import android.os.Bundle;
import android.os.RemoteException;
import android.os.ServiceManager;
@@ -1440,6 +1441,13 @@
public void addNewIncomingCall(PhoneAccountHandle phoneAccount, Bundle extras) {
try {
if (isServiceConnected()) {
+ if (extras != null && extras.getBoolean(EXTRA_IS_HANDOVER) &&
+ mContext.getApplicationContext().getApplicationInfo().targetSdkVersion >
+ Build.VERSION_CODES.O_MR1) {
+ Log.e("TAG", "addNewIncomingCall failed. Use public api " +
+ "acceptHandover for API > O-MR1");
+ // TODO add "return" after DUO team adds support for new handover API
+ }
getTelecomService().addNewIncomingCall(
phoneAccount, extras == null ? new Bundle() : extras);
}
diff --git a/telecomm/java/com/android/internal/telecom/IInCallAdapter.aidl b/telecomm/java/com/android/internal/telecom/IInCallAdapter.aidl
index 23ac940..87ccd3e 100644
--- a/telecomm/java/com/android/internal/telecom/IInCallAdapter.aidl
+++ b/telecomm/java/com/android/internal/telecom/IInCallAdapter.aidl
@@ -64,7 +64,7 @@
void pullExternalCall(String callId);
- void sendCallEvent(String callId, String event, in Bundle extras);
+ void sendCallEvent(String callId, String event, int targetSdkVer, in Bundle extras);
void putExtras(String callId, in Bundle extras);
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index fccc2a6..5a1a3e3 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -337,6 +337,19 @@
"notify_handover_video_from_wifi_to_lte_bool";
/**
+ * Flag specifying whether the carrier wants to notify the user when a VT call has been handed
+ * over from LTE to WIFI.
+ * <p>
+ * The handover notification is sent as a
+ * {@link TelephonyManager#EVENT_HANDOVER_VIDEO_FROM_LTE_TO_WIFI}
+ * {@link android.telecom.Connection} event, which an {@link android.telecom.InCallService}
+ * should use to trigger the display of a user-facing message.
+ * @hide
+ */
+ public static final String KEY_NOTIFY_HANDOVER_VIDEO_FROM_LTE_TO_WIFI_BOOL =
+ "notify_handover_video_from_lte_to_wifi_bool";
+
+ /**
* Flag specifying whether the carrier supports downgrading a video call (tx, rx or tx/rx)
* directly to an audio call.
* @hide
@@ -1740,6 +1753,13 @@
public static final String KEY_CHECK_PRICING_WITH_CARRIER_FOR_DATA_ROAMING_BOOL =
"check_pricing_with_carrier_data_roaming_bool";
+ /**
+ * List of thresholds of RSRP for determining the display level of LTE signal bar.
+ * @hide
+ */
+ public static final String KEY_LTE_RSRP_THRESHOLDS_INT_ARRAY =
+ "lte_rsrp_thresholds_int_array";
+
/** The default value for every variable. */
private final static PersistableBundle sDefaults;
@@ -1756,6 +1776,7 @@
sDefaults.putBoolean(KEY_CARRIER_VOLTE_AVAILABLE_BOOL, false);
sDefaults.putBoolean(KEY_CARRIER_VT_AVAILABLE_BOOL, false);
sDefaults.putBoolean(KEY_NOTIFY_HANDOVER_VIDEO_FROM_WIFI_TO_LTE_BOOL, false);
+ sDefaults.putBoolean(KEY_NOTIFY_HANDOVER_VIDEO_FROM_LTE_TO_WIFI_BOOL, false);
sDefaults.putBoolean(KEY_SUPPORT_DOWNGRADE_VT_TO_AUDIO_BOOL, true);
sDefaults.putString(KEY_DEFAULT_VM_NUMBER_STRING, "");
sDefaults.putBoolean(KEY_CONFIG_TELEPHONY_USE_OWN_NUMBER_FOR_VOICEMAIL_BOOL, false);
@@ -2028,6 +2049,15 @@
sDefaults.putBoolean(KEY_ALWAYS_SHOW_DATA_RAT_ICON_BOOL, false);
sDefaults.putBoolean(KEY_CARRIER_CONFIG_APPLIED_BOOL, false);
sDefaults.putBoolean(KEY_CHECK_PRICING_WITH_CARRIER_FOR_DATA_ROAMING_BOOL, false);
+ sDefaults.putIntArray(KEY_LTE_RSRP_THRESHOLDS_INT_ARRAY,
+ new int[] {
+ -140, /* SIGNAL_STRENGTH_NONE_OR_UNKNOWN */
+ -128, /* SIGNAL_STRENGTH_POOR */
+ -118, /* SIGNAL_STRENGTH_MODERATE */
+ -108, /* SIGNAL_STRENGTH_GOOD */
+ -98, /* SIGNAL_STRENGTH_GREAT */
+ -44
+ });
}
/**
diff --git a/telephony/java/android/telephony/PhoneStateListener.java b/telephony/java/android/telephony/PhoneStateListener.java
index c7e5131..98ea451 100644
--- a/telephony/java/android/telephony/PhoneStateListener.java
+++ b/telephony/java/android/telephony/PhoneStateListener.java
@@ -244,6 +244,13 @@
*/
public static final int LISTEN_DATA_ACTIVATION_STATE = 0x00040000;
+ /**
+ * Listen for changes to the user mobile data state
+ *
+ * @see #onUserMobileDataStateChanged
+ */
+ public static final int LISTEN_USER_MOBILE_DATA_STATE = 0x00080000;
+
/*
* Subscription used to listen to the phone state changes
* @hide
@@ -349,6 +356,9 @@
case LISTEN_DATA_ACTIVATION_STATE:
PhoneStateListener.this.onDataActivationStateChanged((int)msg.obj);
break;
+ case LISTEN_USER_MOBILE_DATA_STATE:
+ PhoneStateListener.this.onUserMobileDataStateChanged((boolean)msg.obj);
+ break;
case LISTEN_CARRIER_NETWORK_CHANGE:
PhoneStateListener.this.onCarrierNetworkChange((boolean)msg.obj);
break;
@@ -543,6 +553,14 @@
}
/**
+ * Callback invoked when the user mobile data state has changed
+ * @param enabled indicates whether the current user mobile data state is enabled or disabled.
+ */
+ public void onUserMobileDataStateChanged(boolean enabled) {
+ // default implementation empty
+ }
+
+ /**
* Callback invoked when telephony has received notice from a carrier
* app that a network action that could result in connectivity loss
* has been requested by an app using
@@ -654,6 +672,10 @@
send(LISTEN_DATA_ACTIVATION_STATE, 0, 0, activationState);
}
+ public void onUserMobileDataStateChanged(boolean enabled) {
+ send(LISTEN_USER_MOBILE_DATA_STATE, 0, 0, enabled);
+ }
+
public void onCarrierNetworkChange(boolean active) {
send(LISTEN_CARRIER_NETWORK_CHANGE, 0, 0, active);
}
diff --git a/telephony/java/android/telephony/SignalStrength.java b/telephony/java/android/telephony/SignalStrength.java
index de02de7..d2134f9 100644
--- a/telephony/java/android/telephony/SignalStrength.java
+++ b/telephony/java/android/telephony/SignalStrength.java
@@ -19,9 +19,13 @@
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
+import android.telephony.CarrierConfigManager;
import android.util.Log;
import android.content.res.Resources;
+import java.util.ArrayList;
+import java.util.Arrays;
+
/**
* Contains phone signal strength related information.
*/
@@ -51,6 +55,8 @@
//Use int max, as -1 is a valid value in signal strength
public static final int INVALID = 0x7FFFFFFF;
+ private static final int LTE_RSRP_THRESHOLDS_NUM = 6;
+
private int mGsmSignalStrength; // Valid values are (0-31, 99) as defined in TS 27.007 8.5
private int mGsmBitErrorRate; // bit error rate (0-7, 99) as defined in TS 27.007 8.5
private int mCdmaDbm; // This value is the RSSI value
@@ -70,6 +76,9 @@
private boolean isGsm; // This value is set by the ServiceStateTracker onSignalStrengthResult
private boolean mUseOnlyRsrpForLteLevel; // Use only RSRP for the number of LTE signal bar.
+ // The threshold of LTE RSRP for determining the display level of LTE signal bar.
+ private int mLteRsrpThresholds[] = new int[LTE_RSRP_THRESHOLDS_NUM];
+
/**
* Create a new SignalStrength from a intent notifier Bundle
*
@@ -110,6 +119,7 @@
mTdScdmaRscp = INVALID;
isGsm = true;
mUseOnlyRsrpForLteLevel = false;
+ setLteRsrpThresholds(getDefaultLteRsrpThresholds());
}
/**
@@ -137,6 +147,7 @@
mTdScdmaRscp = INVALID;
isGsm = gsmFlag;
mUseOnlyRsrpForLteLevel = false;
+ setLteRsrpThresholds(getDefaultLteRsrpThresholds());
}
/**
@@ -276,6 +287,8 @@
mTdScdmaRscp = INVALID;
isGsm = gsm;
mUseOnlyRsrpForLteLevel = useOnlyRsrpForLteLevel;
+
+ setLteRsrpThresholds(getDefaultLteRsrpThresholds());
if (DBG) log("initialize: " + toString());
}
@@ -299,6 +312,7 @@
mTdScdmaRscp = s.mTdScdmaRscp;
isGsm = s.isGsm;
mUseOnlyRsrpForLteLevel = s.mUseOnlyRsrpForLteLevel;
+ setLteRsrpThresholds(s.mLteRsrpThresholds);
}
/**
@@ -325,6 +339,9 @@
mTdScdmaRscp = in.readInt();
isGsm = (in.readInt() != 0);
mUseOnlyRsrpForLteLevel = (in.readInt() != 0);
+ for (int i = 0; i < LTE_RSRP_THRESHOLDS_NUM; i++) {
+ mLteRsrpThresholds[i] = in.readInt();
+ }
}
/**
@@ -374,6 +391,9 @@
out.writeInt(mTdScdmaRscp);
out.writeInt(isGsm ? 1 : 0);
out.writeInt(mUseOnlyRsrpForLteLevel ? 1 : 0);
+ for (int i = 0; i < LTE_RSRP_THRESHOLDS_NUM; i++) {
+ out.writeInt(mLteRsrpThresholds[i]);
+ }
}
/**
@@ -480,6 +500,22 @@
}
/**
+ * Sets the threshold array for determining the display level of LTE signal bar.
+ *
+ * @param lteRsrpThresholds int array for determining the display level.
+ *
+ * @hide
+ */
+ public void setLteRsrpThresholds(int[] lteRsrpThresholds) {
+ if ((lteRsrpThresholds == null)
+ || (lteRsrpThresholds.length != LTE_RSRP_THRESHOLDS_NUM)) {
+ Log.wtf(LOG_TAG, "setLteRsrpThresholds - lteRsrpThresholds is invalid.");
+ return;
+ }
+ System.arraycopy(lteRsrpThresholds, 0, mLteRsrpThresholds, 0, LTE_RSRP_THRESHOLDS_NUM);
+ }
+
+ /**
* Get the GSM Signal Strength, valid values are (0-31, 99) as defined in TS
* 27.007 8.5
*/
@@ -833,25 +869,18 @@
*/
int rssiIconLevel = SIGNAL_STRENGTH_NONE_OR_UNKNOWN, rsrpIconLevel = -1, snrIconLevel = -1;
- int[] threshRsrp = Resources.getSystem().getIntArray(
- com.android.internal.R.array.config_lteDbmThresholds);
- if (threshRsrp.length != 6) {
- Log.wtf(LOG_TAG, "getLteLevel - config_lteDbmThresholds has invalid num of elements."
- + " Cannot evaluate RSRP signal.");
- } else {
- if (mLteRsrp > threshRsrp[5]) {
- rsrpIconLevel = -1;
- } else if (mLteRsrp >= (threshRsrp[4] - mLteRsrpBoost)) {
- rsrpIconLevel = SIGNAL_STRENGTH_GREAT;
- } else if (mLteRsrp >= (threshRsrp[3] - mLteRsrpBoost)) {
- rsrpIconLevel = SIGNAL_STRENGTH_GOOD;
- } else if (mLteRsrp >= (threshRsrp[2] - mLteRsrpBoost)) {
- rsrpIconLevel = SIGNAL_STRENGTH_MODERATE;
- } else if (mLteRsrp >= (threshRsrp[1] - mLteRsrpBoost)) {
- rsrpIconLevel = SIGNAL_STRENGTH_POOR;
- } else if (mLteRsrp >= threshRsrp[0]) {
- rsrpIconLevel = SIGNAL_STRENGTH_NONE_OR_UNKNOWN;
- }
+ if (mLteRsrp > mLteRsrpThresholds[5]) {
+ rsrpIconLevel = -1;
+ } else if (mLteRsrp >= (mLteRsrpThresholds[4] - mLteRsrpBoost)) {
+ rsrpIconLevel = SIGNAL_STRENGTH_GREAT;
+ } else if (mLteRsrp >= (mLteRsrpThresholds[3] - mLteRsrpBoost)) {
+ rsrpIconLevel = SIGNAL_STRENGTH_GOOD;
+ } else if (mLteRsrp >= (mLteRsrpThresholds[2] - mLteRsrpBoost)) {
+ rsrpIconLevel = SIGNAL_STRENGTH_MODERATE;
+ } else if (mLteRsrp >= (mLteRsrpThresholds[1] - mLteRsrpBoost)) {
+ rsrpIconLevel = SIGNAL_STRENGTH_POOR;
+ } else if (mLteRsrp >= mLteRsrpThresholds[0]) {
+ rsrpIconLevel = SIGNAL_STRENGTH_NONE_OR_UNKNOWN;
}
if (useOnlyRsrpForLteLevel()) {
@@ -1010,7 +1039,7 @@
+ (mLteSignalStrength * primeNum) + (mLteRsrp * primeNum)
+ (mLteRsrq * primeNum) + (mLteRssnr * primeNum) + (mLteCqi * primeNum)
+ (mLteRsrpBoost * primeNum) + (mTdScdmaRscp * primeNum) + (isGsm ? 1 : 0)
- + (mUseOnlyRsrpForLteLevel ? 1 : 0));
+ + (mUseOnlyRsrpForLteLevel ? 1 : 0) + (Arrays.hashCode(mLteRsrpThresholds)));
}
/**
@@ -1045,7 +1074,8 @@
&& mLteRsrpBoost == s.mLteRsrpBoost
&& mTdScdmaRscp == s.mTdScdmaRscp
&& isGsm == s.isGsm
- && mUseOnlyRsrpForLteLevel == s.mUseOnlyRsrpForLteLevel);
+ && mUseOnlyRsrpForLteLevel == s.mUseOnlyRsrpForLteLevel
+ && Arrays.equals(mLteRsrpThresholds, s.mLteRsrpThresholds));
}
/**
@@ -1070,7 +1100,8 @@
+ " " + mTdScdmaRscp
+ " " + (isGsm ? "gsm|lte" : "cdma")
+ " " + (mUseOnlyRsrpForLteLevel ? "use_only_rsrp_for_lte_level" :
- "use_rsrp_and_rssnr_for_lte_level"));
+ "use_rsrp_and_rssnr_for_lte_level")
+ + " " + (Arrays.toString(mLteRsrpThresholds)));
}
/** Returns the signal strength related to GSM. */
@@ -1126,6 +1157,10 @@
mTdScdmaRscp = m.getInt("TdScdma");
isGsm = m.getBoolean("isGsm");
mUseOnlyRsrpForLteLevel = m.getBoolean("useOnlyRsrpForLteLevel");
+ ArrayList<Integer> lteRsrpThresholds = m.getIntegerArrayList("lteRsrpThresholds");
+ for (int i = 0; i < lteRsrpThresholds.size(); i++) {
+ mLteRsrpThresholds[i] = lteRsrpThresholds.get(i);
+ }
}
/**
@@ -1151,6 +1186,21 @@
m.putInt("TdScdma", mTdScdmaRscp);
m.putBoolean("isGsm", isGsm);
m.putBoolean("useOnlyRsrpForLteLevel", mUseOnlyRsrpForLteLevel);
+ ArrayList<Integer> lteRsrpThresholds = new ArrayList<Integer>();
+ for (int value : mLteRsrpThresholds) {
+ lteRsrpThresholds.add(value);
+ }
+ m.putIntegerArrayList("lteRsrpThresholds", lteRsrpThresholds);
+ }
+
+ /**
+ * Gets the default threshold array for determining the display level of LTE signal bar.
+ *
+ * @return int array for determining the display level.
+ */
+ private int[] getDefaultLteRsrpThresholds() {
+ return CarrierConfigManager.getDefaultConfig().getIntArray(
+ CarrierConfigManager.KEY_LTE_RSRP_THRESHOLDS_INT_ARRAY);
}
/**
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index af5b190..1945e5d 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -53,6 +53,7 @@
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;
@@ -831,6 +832,17 @@
"android.telephony.event.EVENT_HANDOVER_VIDEO_FROM_WIFI_TO_LTE";
/**
+ * {@link android.telecom.Connection} event used to indicate that an IMS call has be
+ * successfully handed over from LTE to WIFI.
+ * <p>
+ * Sent via {@link android.telecom.Connection#sendConnectionEvent(String, Bundle)}.
+ * The {@link Bundle} parameter is expected to be null when this connection event is used.
+ * @hide
+ */
+ public static final String EVENT_HANDOVER_VIDEO_FROM_LTE_TO_WIFI =
+ "android.telephony.event.EVENT_HANDOVER_VIDEO_FROM_LTE_TO_WIFI";
+
+ /**
* {@link android.telecom.Connection} event used to indicate that an IMS call failed to be
* handed over from LTE to WIFI.
* <p>
@@ -4921,6 +4933,25 @@
}
/**
+ * @return the {@IImsRegistration} interface that corresponds with the slot index and feature.
+ * @param slotIndex The SIM slot corresponding to the ImsService ImsRegistration is active for.
+ * @param feature An integer indicating the feature that we wish to get the ImsRegistration for.
+ * Corresponds to features defined in ImsFeature.
+ * @hide
+ */
+ public @Nullable IImsRegistration getImsRegistration(int slotIndex, int feature) {
+ try {
+ ITelephony telephony = getITelephony();
+ if (telephony != null) {
+ return telephony.getImsRegistration(slotIndex, feature);
+ }
+ } catch (RemoteException e) {
+ Rlog.e(TAG, "getImsRegistration, RemoteException: " + e.getMessage());
+ }
+ return null;
+ }
+
+ /**
* Set IMS registration state
*
* @param Registration state
@@ -6253,8 +6284,10 @@
* <p>Requires Permission:
* {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE}
*
- * @hide
+ * {@hide}
**/
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
public void setSimPowerState(int state) {
setSimPowerStateForSlot(getSlotIndex(), state);
}
@@ -6273,8 +6306,10 @@
* <p>Requires Permission:
* {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE}
*
- * @hide
+ * {@hide}
**/
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
public void setSimPowerStateForSlot(int slotIndex, int state) {
try {
ITelephony telephony = getITelephony();
diff --git a/telephony/java/android/telephony/VisualVoicemailSmsFilterSettings.java b/telephony/java/android/telephony/VisualVoicemailSmsFilterSettings.java
index 8ed96a3..7eeb1ce 100644
--- a/telephony/java/android/telephony/VisualVoicemailSmsFilterSettings.java
+++ b/telephony/java/android/telephony/VisualVoicemailSmsFilterSettings.java
@@ -15,12 +15,10 @@
*/
package android.telephony;
-import android.content.Context;
import android.os.Parcel;
import android.os.Parcelable;
-
-import android.telecom.PhoneAccountHandle;
import android.telephony.VisualVoicemailService.VisualVoicemailTask;
+
import java.util.Collections;
import java.util.List;
@@ -75,6 +73,7 @@
private String mClientPrefix = DEFAULT_CLIENT_PREFIX;
private List<String> mOriginatingNumbers = DEFAULT_ORIGINATING_NUMBERS;
private int mDestinationPort = DEFAULT_DESTINATION_PORT;
+ private String mPackageName;
public VisualVoicemailSmsFilterSettings build() {
return new VisualVoicemailSmsFilterSettings(this);
@@ -116,6 +115,15 @@
return this;
}
+ /**
+ * The package that registered this filter.
+ *
+ * @hide
+ */
+ public Builder setPackageName(String packageName) {
+ mPackageName = packageName;
+ return this;
+ }
}
/**
@@ -138,12 +146,20 @@
public final int destinationPort;
/**
+ * The package that registered this filter.
+ *
+ * @hide
+ */
+ public final String packageName;
+
+ /**
* Use {@link Builder} to construct
*/
private VisualVoicemailSmsFilterSettings(Builder builder) {
clientPrefix = builder.mClientPrefix;
originatingNumbers = builder.mOriginatingNumbers;
destinationPort = builder.mDestinationPort;
+ packageName = builder.mPackageName;
}
public static final Creator<VisualVoicemailSmsFilterSettings> CREATOR =
@@ -154,7 +170,7 @@
builder.setClientPrefix(in.readString());
builder.setOriginatingNumbers(in.createStringArrayList());
builder.setDestinationPort(in.readInt());
-
+ builder.setPackageName(in.readString());
return builder.build();
}
@@ -174,10 +190,11 @@
dest.writeString(clientPrefix);
dest.writeStringList(originatingNumbers);
dest.writeInt(destinationPort);
+ dest.writeString(packageName);
}
@Override
- public String toString(){
+ public String toString() {
return "[VisualVoicemailSmsFilterSettings "
+ "clientPrefix=" + clientPrefix
+ ", originatingNumbers=" + originatingNumbers
diff --git a/telephony/java/android/telephony/data/DataCallResponse.java b/telephony/java/android/telephony/data/DataCallResponse.java
index da51c86..ef3a183 100644
--- a/telephony/java/android/telephony/data/DataCallResponse.java
+++ b/telephony/java/android/telephony/data/DataCallResponse.java
@@ -20,6 +20,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
+import android.net.LinkAddress;
import android.os.Parcel;
import android.os.Parcelable;
@@ -40,7 +41,7 @@
private final int mActive;
private final String mType;
private final String mIfname;
- private final List<InterfaceAddress> mAddresses;
+ private final List<LinkAddress> mAddresses;
private final List<InetAddress> mDnses;
private final List<InetAddress> mGateways;
private final List<String> mPcscfs;
@@ -71,7 +72,7 @@
*/
public DataCallResponse(int status, int suggestedRetryTime, int cid, int active,
@Nullable String type, @Nullable String ifname,
- @Nullable List<InterfaceAddress> addresses,
+ @Nullable List<LinkAddress> addresses,
@Nullable List<InetAddress> dnses,
@Nullable List<InetAddress> gateways,
@Nullable List<String> pcscfs, int mtu) {
@@ -96,7 +97,7 @@
mType = source.readString();
mIfname = source.readString();
mAddresses = new ArrayList<>();
- source.readList(mAddresses, InterfaceAddress.class.getClassLoader());
+ source.readList(mAddresses, LinkAddress.class.getClassLoader());
mDnses = new ArrayList<>();
source.readList(mDnses, InetAddress.class.getClassLoader());
mGateways = new ArrayList<>();
@@ -140,10 +141,10 @@
public String getIfname() { return mIfname; }
/**
- * @return A list of {@link InterfaceAddress}
+ * @return A list of {@link LinkAddress}
*/
@NonNull
- public List<InterfaceAddress> getAddresses() { return mAddresses; }
+ public List<LinkAddress> getAddresses() { return mAddresses; }
/**
* @return A list of DNS server addresses, e.g., "192.0.1.3" or
diff --git a/telephony/java/android/telephony/data/InterfaceAddress.java b/telephony/java/android/telephony/data/InterfaceAddress.java
deleted file mode 100644
index 00d212a..0000000
--- a/telephony/java/android/telephony/data/InterfaceAddress.java
+++ /dev/null
@@ -1,127 +0,0 @@
-/*
- * Copyright 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.data;
-
-import android.annotation.SystemApi;
-import android.net.NetworkUtils;
-import android.os.Parcel;
-import android.os.Parcelable;
-
-import java.net.InetAddress;
-import java.net.UnknownHostException;
-
-/**
- * This class represents a Network Interface address. In short it's an IP address, a subnet mask
- * when the address is an IPv4 one. An IP address and a network prefix length in the case of IPv6
- * address.
- *
- * @hide
- */
-@SystemApi
-public final class InterfaceAddress implements Parcelable {
-
- private final InetAddress mInetAddress;
-
- private final int mPrefixLength;
-
- /**
- * @param inetAddress A {@link InetAddress} of the address
- * @param prefixLength The network prefix length for this address.
- */
- public InterfaceAddress(InetAddress inetAddress, int prefixLength) {
- mInetAddress = inetAddress;
- mPrefixLength = prefixLength;
- }
-
- /**
- * @param address The address in string format
- * @param prefixLength The network prefix length for this address.
- * @throws UnknownHostException
- */
- public InterfaceAddress(String address, int prefixLength) throws UnknownHostException {
- InetAddress ia;
- try {
- ia = NetworkUtils.numericToInetAddress(address);
- } catch (IllegalArgumentException e) {
- throw new UnknownHostException("Non-numeric ip addr=" + address);
- }
- mInetAddress = ia;
- mPrefixLength = prefixLength;
- }
-
- public InterfaceAddress(Parcel source) {
- mInetAddress = (InetAddress) source.readSerializable();
- mPrefixLength = source.readInt();
- }
-
- /**
- * @return an InetAddress for this address.
- */
- public InetAddress getAddress() { return mInetAddress; }
-
- /**
- * @return The network prefix length for this address.
- */
- public int getNetworkPrefixLength() { return mPrefixLength; }
-
- @Override
- public boolean equals (Object o) {
- if (this == o) return true;
-
- if (o == null || !(o instanceof InterfaceAddress)) {
- return false;
- }
-
- InterfaceAddress other = (InterfaceAddress) o;
- return this.mInetAddress.equals(other.mInetAddress)
- && this.mPrefixLength == other.mPrefixLength;
- }
-
- @Override
- public int hashCode() {
- return mInetAddress.hashCode() * 31 + mPrefixLength * 37;
- }
-
- @Override
- public int describeContents() {
- return 0;
- }
-
- @Override
- public String toString() {
- return mInetAddress + "/" + mPrefixLength;
- }
-
- @Override
- public void writeToParcel(Parcel dest, int flags) {
- dest.writeSerializable(mInetAddress);
- dest.writeInt(mPrefixLength);
- }
-
- public static final Parcelable.Creator<InterfaceAddress> CREATOR =
- new Parcelable.Creator<InterfaceAddress>() {
- @Override
- public InterfaceAddress createFromParcel(Parcel source) {
- return new InterfaceAddress(source);
- }
-
- @Override
- public InterfaceAddress[] newArray(int size) {
- return new InterfaceAddress[size];
- }
- };
-}
diff --git a/telephony/java/android/telephony/euicc/EuiccCardManager.java b/telephony/java/android/telephony/euicc/EuiccCardManager.java
new file mode 100644
index 0000000..29849c1
--- /dev/null
+++ b/telephony/java/android/telephony/euicc/EuiccCardManager.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.telephony.euicc;
+
+import android.content.Context;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.service.euicc.EuiccProfileInfo;
+import android.util.Log;
+
+import com.android.internal.telephony.euicc.IEuiccCardController;
+import com.android.internal.telephony.euicc.IGetAllProfilesCallback;
+
+/**
+ * EuiccCardManager is the application interface to an eSIM card.
+ *
+ * @hide
+ *
+ * TODO(b/35851809): Make this a SystemApi.
+ */
+public class EuiccCardManager {
+ private static final String TAG = "EuiccCardManager";
+
+ /** Result code of execution with no error. */
+ public static final int RESULT_OK = 0;
+
+ /**
+ * Callback to receive the result of an eUICC card API.
+ *
+ * @param <T> Type of the result.
+ */
+ public interface ResultCallback<T> {
+ /**
+ * This method will be called when an eUICC card API call is completed.
+ *
+ * @param resultCode This can be {@link #RESULT_OK} or other positive values returned by the
+ * eUICC.
+ * @param result The result object. It can be null if the {@code resultCode} is not
+ * {@link #RESULT_OK}.
+ */
+ void onComplete(int resultCode, T result);
+ }
+
+ private final Context mContext;
+
+ /** @hide */
+ public EuiccCardManager(Context context) {
+ mContext = context;
+ }
+
+ private IEuiccCardController getIEuiccCardController() {
+ return IEuiccCardController.Stub.asInterface(
+ ServiceManager.getService("euicc_card_controller"));
+ }
+
+ /**
+ * Gets all the profiles on eUicc.
+ *
+ * @param callback the callback to get the result code and all the profiles.
+ */
+ public void getAllProfiles(ResultCallback<EuiccProfileInfo[]> callback) {
+ try {
+ getIEuiccCardController().getAllProfiles(mContext.getOpPackageName(),
+ new IGetAllProfilesCallback.Stub() {
+ @Override
+ public void onComplete(int resultCode, EuiccProfileInfo[] profiles) {
+ callback.onComplete(resultCode, profiles);
+ }
+ });
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error calling getAllProfiles", e);
+ throw e.rethrowFromSystemServer();
+ }
+ }
+}
diff --git a/telephony/java/android/telephony/euicc/EuiccNotification.java b/telephony/java/android/telephony/euicc/EuiccNotification.java
new file mode 100644
index 0000000..ef3c1ce
--- /dev/null
+++ b/telephony/java/android/telephony/euicc/EuiccNotification.java
@@ -0,0 +1,179 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.euicc;
+
+import android.annotation.IntDef;
+import android.annotation.Nullable;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Arrays;
+import java.util.Objects;
+
+/**
+ * This represents a signed notification which is defined in SGP.22. It can be either a profile
+ * installation result or a notification generated for profile operations (e.g., enabling,
+ * disabling, or deleting).
+ *
+ * @hide
+ *
+ * TODO(b/35851809): Make this a @SystemApi.
+ */
+public class EuiccNotification implements Parcelable {
+ /** Event */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(flag = true, prefix = { "EVENT_" }, value = {
+ EVENT_INSTALL,
+ EVENT_ENABLE,
+ EVENT_DISABLE,
+ EVENT_DELETE
+ })
+ public @interface Event {}
+
+ /** A profile is downloaded and installed. */
+ public static final int EVENT_INSTALL = 1;
+
+ /** A profile is enabled. */
+ public static final int EVENT_ENABLE = 1 << 1;
+
+ /** A profile is disabled. */
+ public static final int EVENT_DISABLE = 1 << 2;
+
+ /** A profile is deleted. */
+ public static final int EVENT_DELETE = 1 << 3;
+
+ /** Value of the bits of all above events */
+ @Event
+ public static final int ALL_EVENTS =
+ EVENT_INSTALL | EVENT_ENABLE | EVENT_DISABLE | EVENT_DELETE;
+
+ private final int mSeq;
+ private final String mTargetAddr;
+ @Event private final int mEvent;
+ @Nullable private final byte[] mData;
+
+ /**
+ * Creates an instance.
+ *
+ * @param seq The sequence number of this notification.
+ * @param targetAddr The target server where to send this notification.
+ * @param event The event which causes this notification.
+ * @param data The data which needs to be sent to the target server. This can be null for
+ * building a list of notification metadata without data.
+ */
+ public EuiccNotification(int seq, String targetAddr, @Event int event, @Nullable byte[] data) {
+ mSeq = seq;
+ mTargetAddr = targetAddr;
+ mEvent = event;
+ mData = data;
+ }
+
+ /** @return The sequence number of this notification. */
+ public int getSeq() {
+ return mSeq;
+ }
+
+ /** @return The target server address where this notification should be sent to. */
+ public String getTargetAddr() {
+ return mTargetAddr;
+ }
+
+ /** @return The event of this notification. */
+ @Event
+ public int getEvent() {
+ return mEvent;
+ }
+
+ /** @return The notification data which needs to be sent to the target server. */
+ @Nullable
+ public byte[] getData() {
+ return mData;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null || getClass() != obj.getClass()) {
+ return false;
+ }
+
+ EuiccNotification that = (EuiccNotification) obj;
+ return mSeq == that.mSeq
+ && Objects.equals(mTargetAddr, that.mTargetAddr)
+ && mEvent == that.mEvent
+ && Arrays.equals(mData, that.mData);
+ }
+
+ @Override
+ public int hashCode() {
+ int result = 1;
+ result = 31 * result + mSeq;
+ result = 31 * result + Objects.hashCode(mTargetAddr);
+ result = 31 * result + mEvent;
+ result = 31 * result + Arrays.hashCode(mData);
+ return result;
+ }
+
+ @Override
+ public String toString() {
+ return "EuiccNotification (seq="
+ + mSeq
+ + ", targetAddr="
+ + mTargetAddr
+ + ", event="
+ + mEvent
+ + ", data="
+ + (mData == null ? "null" : "byte[" + mData.length + "]")
+ + ")";
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(mSeq);
+ dest.writeString(mTargetAddr);
+ dest.writeInt(mEvent);
+ dest.writeByteArray(mData);
+ }
+
+ private EuiccNotification(Parcel source) {
+ mSeq = source.readInt();
+ mTargetAddr = source.readString();
+ mEvent = source.readInt();
+ mData = source.createByteArray();
+ }
+
+ public static final Creator<EuiccNotification> CREATOR =
+ new Creator<EuiccNotification>() {
+ @Override
+ public EuiccNotification createFromParcel(Parcel source) {
+ return new EuiccNotification(source);
+ }
+
+ @Override
+ public EuiccNotification[] newArray(int size) {
+ return new EuiccNotification[size];
+ }
+ };
+}
diff --git a/telephony/java/android/telephony/euicc/EuiccRat.java b/telephony/java/android/telephony/euicc/EuiccRat.java
new file mode 100644
index 0000000..6a56503a
--- /dev/null
+++ b/telephony/java/android/telephony/euicc/EuiccRat.java
@@ -0,0 +1,259 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.euicc;
+
+import android.annotation.IntDef;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.service.carrier.CarrierIdentifier;
+import android.service.euicc.EuiccProfileInfo;
+import android.text.TextUtils;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Arrays;
+
+/**
+ * This represents the RAT (Rules Authorisation Table) stored on eUICC.
+ *
+ * @hide
+ *
+ * TODO(b/35851809): Make this a @SystemApi.
+ */
+public final class EuiccRat implements Parcelable {
+ /** Profile policy rule flags */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(flag = true, prefix = { "POLICY_RULE_FLAG_" }, value = {
+ POLICY_RULE_FLAG_CONSENT_REQUIRED
+ })
+ public @interface PolicyRuleFlag {}
+
+ /** User consent is required to install the profile. */
+ public static final int POLICY_RULE_FLAG_CONSENT_REQUIRED = 1;
+
+ private final int[] mPolicyRules;
+ private final CarrierIdentifier[][] mCarrierIds;
+ private final int[] mPolicyRuleFlags;
+
+ /** This is used to build new {@link EuiccRat} instance. */
+ public static final class Builder {
+ private int[] mPolicyRules;
+ private CarrierIdentifier[][] mCarrierIds;
+ private int[] mPolicyRuleFlags;
+ private int mPosition;
+
+ /**
+ * Creates a new builder.
+ *
+ * @param ruleNum The number of authorisation rules in the table.
+ */
+ public Builder(int ruleNum) {
+ mPolicyRules = new int[ruleNum];
+ mCarrierIds = new CarrierIdentifier[ruleNum][];
+ mPolicyRuleFlags = new int[ruleNum];
+ }
+
+ /**
+ * Builds the RAT instance. This builder should not be used anymore after this method is
+ * called, otherwise {@link NullPointerException} will be thrown.
+ */
+ public EuiccRat build() {
+ if (mPosition != mPolicyRules.length) {
+ throw new IllegalStateException(
+ "Not enough rules are added, expected: "
+ + mPolicyRules.length
+ + ", added: "
+ + mPosition);
+ }
+ return new EuiccRat(mPolicyRules, mCarrierIds, mPolicyRuleFlags);
+ }
+
+ /**
+ * Adds an authorisation rule.
+ *
+ * @throws ArrayIndexOutOfBoundsException If the {@code mPosition} is larger than the size
+ * this table.
+ */
+ public Builder add(int policyRules, CarrierIdentifier[] carrierId, int policyRuleFlags) {
+ if (mPosition >= mPolicyRules.length) {
+ throw new ArrayIndexOutOfBoundsException(mPosition);
+ }
+ mPolicyRules[mPosition] = policyRules;
+ mCarrierIds[mPosition] = carrierId;
+ mPolicyRuleFlags[mPosition] = policyRuleFlags;
+ mPosition++;
+ return this;
+ }
+ }
+
+ /**
+ * @param mccRule A 2-character or 3-character string which can be either MCC or MNC. The
+ * character 'E' is used as a wild char to match any digit.
+ * @param mcc A 2-character or 3-character string which can be either MCC or MNC.
+ * @return Whether the {@code mccRule} matches {@code mcc}.
+ *
+ * @hide
+ */
+ @VisibleForTesting
+ public static boolean match(String mccRule, String mcc) {
+ if (mccRule.length() < mcc.length()) {
+ return false;
+ }
+ for (int i = 0; i < mccRule.length(); i++) {
+ // 'E' is the wild char to match any digit.
+ if (mccRule.charAt(i) == 'E'
+ || (i < mcc.length() && mccRule.charAt(i) == mcc.charAt(i))) {
+ continue;
+ }
+ return false;
+ }
+ return true;
+ }
+
+ private EuiccRat(int[] policyRules, CarrierIdentifier[][] carrierIds, int[] policyRuleFlags) {
+ mPolicyRules = policyRules;
+ mCarrierIds = carrierIds;
+ mPolicyRuleFlags = policyRuleFlags;
+ }
+
+ /**
+ * Finds the index of the first authorisation rule matching the given policy and carrier id. If
+ * the returned index is not negative, the carrier is allowed to apply this policy to its
+ * profile.
+ *
+ * @param policy The policy rule.
+ * @param carrierId The carrier id.
+ * @return The index of authorization rule. If no rule is found, -1 will be returned.
+ */
+ public int findIndex(@EuiccProfileInfo.PolicyRule int policy, CarrierIdentifier carrierId) {
+ for (int i = 0; i < mPolicyRules.length; i++) {
+ if ((mPolicyRules[i] & policy) == 0) {
+ continue;
+ }
+ CarrierIdentifier[] carrierIds = mCarrierIds[i];
+ if (carrierIds == null || carrierIds.length == 0) {
+ continue;
+ }
+ for (int j = 0; j < carrierIds.length; j++) {
+ CarrierIdentifier ruleCarrierId = carrierIds[j];
+ if (!match(ruleCarrierId.getMcc(), carrierId.getMcc())
+ || !match(ruleCarrierId.getMnc(), carrierId.getMnc())) {
+ continue;
+ }
+ String gid = ruleCarrierId.getGid1();
+ if (!TextUtils.isEmpty(gid) && !gid.equals(carrierId.getGid1())) {
+ continue;
+ }
+ gid = ruleCarrierId.getGid2();
+ if (!TextUtils.isEmpty(gid) && !gid.equals(carrierId.getGid2())) {
+ continue;
+ }
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ /**
+ * Tests if the entry in the table has the given policy rule flag.
+ *
+ * @param index The index of the entry.
+ * @param flag The policy rule flag to be tested.
+ * @throws ArrayIndexOutOfBoundsException If the {@code index} is negative or larger than the
+ * size of this table.
+ */
+ public boolean hasPolicyRuleFlag(int index, @PolicyRuleFlag int flag) {
+ if (index < 0 || index >= mPolicyRules.length) {
+ throw new ArrayIndexOutOfBoundsException(index);
+ }
+ return (mPolicyRuleFlags[index] & flag) != 0;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeIntArray(mPolicyRules);
+ for (CarrierIdentifier[] ids : mCarrierIds) {
+ dest.writeTypedArray(ids, flags);
+ }
+ dest.writeIntArray(mPolicyRuleFlags);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null || getClass() != obj.getClass()) {
+ return false;
+ }
+
+ EuiccRat that = (EuiccRat) obj;
+ if (mCarrierIds.length != that.mCarrierIds.length) {
+ return false;
+ }
+ for (int i = 0; i < mCarrierIds.length; i++) {
+ CarrierIdentifier[] carrierIds = mCarrierIds[i];
+ CarrierIdentifier[] thatCarrierIds = that.mCarrierIds[i];
+ if (carrierIds != null && thatCarrierIds != null) {
+ if (carrierIds.length != thatCarrierIds.length) {
+ return false;
+ }
+ for (int j = 0; j < carrierIds.length; j++) {
+ if (!carrierIds[j].equals(thatCarrierIds[j])) {
+ return false;
+ }
+ }
+ continue;
+ } else if (carrierIds == null && thatCarrierIds == null) {
+ continue;
+ }
+ return false;
+ }
+
+ return Arrays.equals(mPolicyRules, that.mPolicyRules)
+ && Arrays.equals(mPolicyRuleFlags, that.mPolicyRuleFlags);
+ }
+
+ private EuiccRat(Parcel source) {
+ mPolicyRules = source.createIntArray();
+ int len = mPolicyRules.length;
+ mCarrierIds = new CarrierIdentifier[len][];
+ for (int i = 0; i < len; i++) {
+ mCarrierIds[i] = source.createTypedArray(CarrierIdentifier.CREATOR);
+ }
+ mPolicyRuleFlags = source.createIntArray();
+ }
+
+ public static final Creator<EuiccRat> CREATOR =
+ new Creator<EuiccRat>() {
+ @Override
+ public EuiccRat createFromParcel(Parcel source) {
+ return new EuiccRat(source);
+ }
+
+ @Override
+ public EuiccRat[] newArray(int size) {
+ return new EuiccRat[size];
+ }
+ };
+}
diff --git a/telephony/java/android/telephony/ims/ImsService.java b/telephony/java/android/telephony/ims/ImsService.java
index 8230eaf..aaa0f08 100644
--- a/telephony/java/android/telephony/ims/ImsService.java
+++ b/telephony/java/android/telephony/ims/ImsService.java
@@ -26,12 +26,14 @@
import android.telephony.ims.feature.ImsFeature;
import android.telephony.ims.feature.MMTelFeature;
import android.telephony.ims.feature.RcsFeature;
+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;
@@ -113,6 +115,12 @@
throws RemoteException {
ImsService.this.removeImsFeature(slotId, featureType, c);
}
+
+ @Override
+ public IImsRegistration getRegistration(int slotId) throws RemoteException {
+ ImsRegistrationImplBase r = ImsService.this.getRegistration(slotId);
+ return r != null ? r.getBinder() : null;
+ }
};
/**
@@ -174,6 +182,8 @@
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) {
@@ -236,4 +246,13 @@
public @Nullable RcsFeature onCreateRcsFeature(int slotId) {
return null;
}
+
+ /**
+ * @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();
+ }
}
diff --git a/telephony/java/android/telephony/ims/feature/ImsFeature.java b/telephony/java/android/telephony/ims/feature/ImsFeature.java
index ca4a210..d47cea30 100644
--- a/telephony/java/android/telephony/ims/feature/ImsFeature.java
+++ b/telephony/java/android/telephony/ims/feature/ImsFeature.java
@@ -96,7 +96,7 @@
new WeakHashMap<IImsFeatureStatusCallback, Boolean>());
private @ImsState int mState = STATE_NOT_AVAILABLE;
private int mSlotId = SubscriptionManager.INVALID_SIM_SLOT_INDEX;
- private Context mContext;
+ protected Context mContext;
public void setContext(Context context) {
mContext = context;
diff --git a/telephony/java/android/telephony/ims/internal/ImsService.java b/telephony/java/android/telephony/ims/internal/ImsService.java
index b7c8ca0..afaf332 100644
--- a/telephony/java/android/telephony/ims/internal/ImsService.java
+++ b/telephony/java/android/telephony/ims/internal/ImsService.java
@@ -24,7 +24,6 @@
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.IImsRegistration;
import android.telephony.ims.internal.aidl.IImsServiceController;
import android.telephony.ims.internal.aidl.IImsServiceControllerListener;
import android.telephony.ims.internal.feature.ImsFeature;
@@ -32,11 +31,12 @@
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.internal.stub.ImsRegistrationImplBase;
+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;
/**
diff --git a/telephony/java/android/telephony/ims/internal/SmsImplBase.java b/telephony/java/android/telephony/ims/internal/SmsImplBase.java
index 47414cf..eb805a8 100644
--- a/telephony/java/android/telephony/ims/internal/SmsImplBase.java
+++ b/telephony/java/android/telephony/ims/internal/SmsImplBase.java
@@ -124,70 +124,79 @@
* method should be implemented by the IMS providers to provide implementation of sending an SMS
* over IMS.
*
- * @param smsc the Short Message Service Center address.
- * @param format the format of the message. Valid values are {@link SmsMessage#FORMAT_3GPP} and
- * {@link SmsMessage#FORMAT_3GPP2}.
+ * @param token unique token generated by the platform that should be used when triggering
+ * callbacks for this specific message.
* @param messageRef the message reference.
+ * @param format the format of the message. Valid values are {@link SmsMessage#FORMAT_3GPP} and
+ * @param smsc the Short Message Service Center address.
+ * {@link SmsMessage#FORMAT_3GPP2}.
* @param isRetry whether it is a retry of an already attempted message or not.
* @param pdu PDUs representing the contents of the message.
*/
- public void sendSms(int messageRef, String format, String smsc, boolean isRetry, byte[] pdu) {
+ public void sendSms(int token, int messageRef, String format, String smsc, boolean isRetry,
+ byte[] pdu) {
// Base implementation returns error. Should be overridden.
try {
- onSendSmsResult(messageRef, SEND_STATUS_ERROR, SmsManager.RESULT_ERROR_GENERIC_FAILURE);
+ onSendSmsResult(token, messageRef, SEND_STATUS_ERROR,
+ SmsManager.RESULT_ERROR_GENERIC_FAILURE);
} catch (RemoteException e) {
Log.e(LOG_TAG, "Can not send sms: " + e.getMessage());
}
}
/**
- * This method will be triggered by the platform after {@link #onSmsReceived(String, byte[])} has
- * been called to deliver the result to the IMS provider.
+ * This method will be triggered by the platform after {@link #onSmsReceived(int, String, byte[])}
+ * has been called to deliver the result to the IMS provider.
*
+ * @param token token provided in {@link #onSmsReceived(int, String, byte[])}
* @param result result of delivering the message. Valid values are defined in
* {@link DeliverStatusResult}
- * @param messageRef the message reference or -1 of unavailable.
+ * @param messageRef the message reference
*/
- public void acknowledgeSms(int messageRef, @DeliverStatusResult int result) {
+ public void acknowledgeSms(int token, int messageRef, @DeliverStatusResult int result) {
}
/**
* This method will be triggered by the platform after
- * {@link #onSmsStatusReportReceived(int, int, byte[])} has been called to provide the result to
- * the IMS provider.
+ * {@link #onSmsStatusReportReceived(int, int, String, byte[])} has been called to provide the
+ * result to the IMS provider.
*
+ * @param token token provided in {@link #sendSms(int, int, String, String, boolean, byte[])}
* @param result result of delivering the message. Valid values are defined in
* {@link StatusReportResult}
- * @param messageRef the message reference or -1 of unavailable.
+ * @param messageRef the message reference
*/
- public void acknowledgeSmsReport(int messageRef, @StatusReportResult int result) {
+ public void acknowledgeSmsReport(int token, int messageRef, @StatusReportResult int result) {
}
/**
* This method should be triggered by the IMS providers when there is an incoming message. The
* platform will deliver the message to the messages database and notify the IMS provider of the
- * result by calling {@link #acknowledgeSms(int, int)}.
+ * result by calling {@link #acknowledgeSms(int, int, int)}.
*
* This method must not be called before {@link MmTelFeature#onFeatureReady()} is called.
*
+ * @param token unique token generated by IMS providers that the platform will use to trigger
+ * callbacks for this message.
* @param format the format of the message. Valid values are {@link SmsMessage#FORMAT_3GPP} and
* {@link SmsMessage#FORMAT_3GPP2}.
* @param pdu PDUs representing the contents of the message.
* @throws IllegalStateException if called before {@link MmTelFeature#onFeatureReady()}
*/
- public final void onSmsReceived(String format, byte[] pdu) throws IllegalStateException {
+ public final void onSmsReceived(int token, String format, byte[] pdu)
+ throws IllegalStateException {
synchronized (mLock) {
if (mListener == null) {
throw new IllegalStateException("Feature not ready.");
}
try {
- mListener.onSmsReceived(format, pdu);
- acknowledgeSms(-1, DELIVER_STATUS_OK);
+ mListener.onSmsReceived(token, format, pdu);
+ acknowledgeSms(token, 0, DELIVER_STATUS_OK);
} catch (RemoteException e) {
Log.e(LOG_TAG, "Can not deliver sms: " + e.getMessage());
- acknowledgeSms(-1, DELIVER_STATUS_ERROR);
+ acknowledgeSms(token, 0, DELIVER_STATUS_ERROR);
}
}
}
@@ -198,6 +207,7 @@
*
* This method must not be called before {@link MmTelFeature#onFeatureReady()} is called.
*
+ * @param token token provided in {@link #sendSms(int, int, String, String, boolean, byte[])}
* @param messageRef the message reference. Should be between 0 and 255 per TS.123.040
* @param status result of sending the SMS. Valid values are defined in {@link SendStatusResult}
* @param reason reason in case status is failure. Valid values are:
@@ -213,35 +223,37 @@
* @throws RemoteException if the connection to the framework is not available. If this happens
* attempting to send the SMS should be aborted.
*/
- public final void onSendSmsResult(int messageRef, @SendStatusResult int status, int reason)
- throws IllegalStateException, RemoteException {
+ public final void onSendSmsResult(int token, int messageRef, @SendStatusResult int status,
+ int reason) throws IllegalStateException, RemoteException {
synchronized (mLock) {
if (mListener == null) {
throw new IllegalStateException("Feature not ready.");
}
- mListener.onSendSmsResult(messageRef, status, reason);
+ mListener.onSendSmsResult(token, messageRef, status, reason);
}
}
/**
* Sets the status report of the sent message.
*
+ * @param token token provided in {@link #sendSms(int, int, String, String, boolean, byte[])}
* @param messageRef the message reference.
* @param format the format of the message. Valid values are {@link SmsMessage#FORMAT_3GPP} and
* {@link SmsMessage#FORMAT_3GPP2}.
* @param pdu PDUs representing the content of the status report.
* @throws IllegalStateException if called before {@link MmTelFeature#onFeatureReady()}
*/
- public final void onSmsStatusReportReceived(int messageRef, String format, byte[] pdu) {
+ public final void onSmsStatusReportReceived(int token, int messageRef, String format,
+ byte[] pdu) {
synchronized (mLock) {
if (mListener == null) {
throw new IllegalStateException("Feature not ready.");
}
try {
- mListener.onSmsStatusReportReceived(messageRef, format, pdu);
+ mListener.onSmsStatusReportReceived(token, messageRef, format, pdu);
} catch (RemoteException e) {
Log.e(LOG_TAG, "Can not process sms status report: " + e.getMessage());
- acknowledgeSmsReport(messageRef, STATUS_REPORT_STATUS_ERROR);
+ acknowledgeSmsReport(token, messageRef, STATUS_REPORT_STATUS_ERROR);
}
}
}
diff --git a/telephony/java/android/telephony/ims/internal/aidl/IImsMmTelFeature.aidl b/telephony/java/android/telephony/ims/internal/aidl/IImsMmTelFeature.aidl
index d976686..785113f 100644
--- a/telephony/java/android/telephony/ims/internal/aidl/IImsMmTelFeature.aidl
+++ b/telephony/java/android/telephony/ims/internal/aidl/IImsMmTelFeature.aidl
@@ -52,8 +52,9 @@
IImsCapabilityCallback c);
// SMS APIs
void setSmsListener(IImsSmsListener l);
- oneway void sendSms(int messageRef, String format, String smsc, boolean retry, in byte[] pdu);
- oneway void acknowledgeSms(int messageRef, int result);
- oneway void acknowledgeSmsReport(int messageRef, int result);
+ 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();
}
diff --git a/telephony/java/android/telephony/ims/internal/aidl/IImsMmTelListener.aidl b/telephony/java/android/telephony/ims/internal/aidl/IImsMmTelListener.aidl
index 8332bc0..43f5098 100644
--- a/telephony/java/android/telephony/ims/internal/aidl/IImsMmTelListener.aidl
+++ b/telephony/java/android/telephony/ims/internal/aidl/IImsMmTelListener.aidl
@@ -24,4 +24,5 @@
*/
oneway interface IImsMmTelListener {
void onIncomingCall(IImsCallSession c);
+ void onVoiceMessageCountUpdate(int count);
}
\ No newline at end of file
diff --git a/telephony/java/android/telephony/ims/internal/aidl/IImsServiceController.aidl b/telephony/java/android/telephony/ims/internal/aidl/IImsServiceController.aidl
index 8afb955..82a8525 100644
--- a/telephony/java/android/telephony/ims/internal/aidl/IImsServiceController.aidl
+++ b/telephony/java/android/telephony/ims/internal/aidl/IImsServiceController.aidl
@@ -18,12 +18,12 @@
import android.telephony.ims.internal.aidl.IImsMmTelFeature;
import android.telephony.ims.internal.aidl.IImsRcsFeature;
-import android.telephony.ims.internal.aidl.IImsRegistration;
import android.telephony.ims.internal.aidl.IImsConfig;
import android.telephony.ims.internal.aidl.IImsServiceControllerListener;
import android.telephony.ims.internal.stub.ImsFeatureConfiguration;
import com.android.ims.internal.IImsFeatureStatusCallback;
+import com.android.ims.internal.IImsRegistration;
/**
* See ImsService and MmTelFeature for more information.
diff --git a/telephony/java/android/telephony/ims/internal/aidl/IImsSmsListener.aidl b/telephony/java/android/telephony/ims/internal/aidl/IImsSmsListener.aidl
index 468629a..bf8d90b 100644
--- a/telephony/java/android/telephony/ims/internal/aidl/IImsSmsListener.aidl
+++ b/telephony/java/android/telephony/ims/internal/aidl/IImsSmsListener.aidl
@@ -21,7 +21,8 @@
* {@hide}
*/
interface IImsSmsListener {
- void onSendSmsResult(in int messageRef, in int status, in int reason);
- void onSmsStatusReportReceived(in int messageRef, in String format, in byte[] pdu);
- void onSmsReceived(in String format, in byte[] pdu);
+ void onSendSmsResult(in int token, in int messageRef, in int status, in int reason);
+ void onSmsStatusReportReceived(in int token, in int messageRef, in String format,
+ in byte[] pdu);
+ void onSmsReceived(in int token, in String format, in byte[] pdu);
}
\ No newline at end of file
diff --git a/telephony/java/android/telephony/ims/internal/feature/CapabilityChangeRequest.java b/telephony/java/android/telephony/ims/internal/feature/CapabilityChangeRequest.java
index 4d18873..5dbf077 100644
--- a/telephony/java/android/telephony/ims/internal/feature/CapabilityChangeRequest.java
+++ b/telephony/java/android/telephony/ims/internal/feature/CapabilityChangeRequest.java
@@ -18,7 +18,7 @@
import android.os.Parcel;
import android.os.Parcelable;
-import android.telephony.ims.internal.stub.ImsRegistrationImplBase;
+import android.telephony.ims.stub.ImsRegistrationImplBase;
import android.util.ArraySet;
import java.util.ArrayList;
diff --git a/telephony/java/android/telephony/ims/internal/feature/MmTelFeature.java b/telephony/java/android/telephony/ims/internal/feature/MmTelFeature.java
index 2f350c8..8d888c2 100644
--- a/telephony/java/android/telephony/ims/internal/feature/MmTelFeature.java
+++ b/telephony/java/android/telephony/ims/internal/feature/MmTelFeature.java
@@ -28,8 +28,8 @@
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.internal.stub.ImsRegistrationImplBase;
import android.telephony.ims.internal.aidl.IImsSmsListener;
+import android.telephony.ims.stub.ImsRegistrationImplBase;
import android.telephony.ims.stub.ImsEcbmImplBase;
import android.telephony.ims.stub.ImsMultiEndpointImplBase;
import android.telephony.ims.stub.ImsUtImplBase;
@@ -154,23 +154,24 @@
}
@Override
- public void sendSms(int messageRef, String format, String smsc, boolean retry, byte[] pdu) {
+ public void sendSms(int token, int messageRef, String format, String smsc, boolean retry,
+ byte[] pdu) {
synchronized (mLock) {
- MmTelFeature.this.sendSms(messageRef, format, smsc, retry, pdu);
+ MmTelFeature.this.sendSms(token, messageRef, format, smsc, retry, pdu);
}
}
@Override
- public void acknowledgeSms(int messageRef, int result) {
+ public void acknowledgeSms(int token, int messageRef, int result) {
synchronized (mLock) {
- MmTelFeature.this.acknowledgeSms(messageRef, result);
+ MmTelFeature.this.acknowledgeSms(token, messageRef, result);
}
}
@Override
- public void acknowledgeSmsReport(int messageRef, int result) {
+ public void acknowledgeSmsReport(int token, int messageRef, int result) {
synchronized (mLock) {
- MmTelFeature.this.acknowledgeSmsReport(messageRef, result);
+ MmTelFeature.this.acknowledgeSmsReport(token, messageRef, result);
}
}
@@ -261,6 +262,15 @@
}
/**
+ * 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.
*/
@@ -447,16 +457,17 @@
// Base Implementation - Should be overridden
}
- private void sendSms(int messageRef, String format, String smsc, boolean isRetry, byte[] pdu) {
- getSmsImplementation().sendSms(messageRef, format, smsc, isRetry, pdu);
+ 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 messageRef, @DeliverStatusResult int result) {
- getSmsImplementation().acknowledgeSms(messageRef, result);
+ private void acknowledgeSms(int token, int messageRef, @DeliverStatusResult int result) {
+ getSmsImplementation().acknowledgeSms(token, messageRef, result);
}
- private void acknowledgeSmsReport(int messageRef, @StatusReportResult int result) {
- getSmsImplementation().acknowledgeSmsReport(messageRef, result);
+ private void acknowledgeSmsReport(int token, int messageRef, @StatusReportResult int result) {
+ getSmsImplementation().acknowledgeSmsReport(token, messageRef, result);
}
private String getSmsFormat() {
diff --git a/telephony/java/android/telephony/ims/internal/stub/ImsRegistrationImplBase.java b/telephony/java/android/telephony/ims/stub/ImsRegistrationImplBase.java
similarity index 83%
rename from telephony/java/android/telephony/ims/internal/stub/ImsRegistrationImplBase.java
rename to telephony/java/android/telephony/ims/stub/ImsRegistrationImplBase.java
index 558b009..42af083 100644
--- a/telephony/java/android/telephony/ims/internal/stub/ImsRegistrationImplBase.java
+++ b/telephony/java/android/telephony/ims/stub/ImsRegistrationImplBase.java
@@ -14,16 +14,19 @@
* limitations under the License
*/
-package android.telephony.ims.internal.stub;
+package android.telephony.ims.stub;
import android.annotation.IntDef;
+import android.net.Uri;
+import android.os.IBinder;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
-import android.telephony.ims.internal.aidl.IImsRegistration;
-import android.telephony.ims.internal.aidl.IImsRegistrationCallback;
import android.util.Log;
import com.android.ims.ImsReasonInfo;
+import com.android.ims.internal.IImsRegistration;
+import com.android.ims.internal.IImsRegistrationCallback;
+import com.android.internal.annotations.VisibleForTesting;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -62,23 +65,25 @@
// Registration states, used to notify new ImsRegistrationImplBase#Callbacks of the current
// state.
+ // The unknown state is set as the initialization state. This is so that we do not call back
+ // with NOT_REGISTERED in the case where the ImsService has not updated the registration state
+ // yet.
+ private static final int REGISTRATION_STATE_UNKNOWN = -1;
private static final int REGISTRATION_STATE_NOT_REGISTERED = 0;
private static final int REGISTRATION_STATE_REGISTERING = 1;
private static final int REGISTRATION_STATE_REGISTERED = 2;
-
/**
* Callback class for receiving Registration callback events.
+ * @hide
*/
- public static class Callback extends IImsRegistrationCallback.Stub {
-
+ public static class Callback {
/**
* Notifies the framework when the IMS Provider is connected to the IMS network.
*
* @param imsRadioTech the radio access technology. Valid values are defined in
* {@link ImsRegistrationTech}.
*/
- @Override
public void onRegistered(@ImsRegistrationTech int imsRadioTech) {
}
@@ -88,7 +93,6 @@
* @param imsRadioTech the radio access technology. Valid values are defined in
* {@link ImsRegistrationTech}.
*/
- @Override
public void onRegistering(@ImsRegistrationTech int imsRadioTech) {
}
@@ -97,7 +101,6 @@
*
* @param info the {@link ImsReasonInfo} associated with why registration was disconnected.
*/
- @Override
public void onDeregistered(ImsReasonInfo info) {
}
@@ -108,10 +111,19 @@
* @param imsRadioTech The {@link ImsRegistrationTech} type that has failed
* @param info A {@link ImsReasonInfo} that identifies the reason for failure.
*/
- @Override
public void onTechnologyChangeFailed(@ImsRegistrationTech int imsRadioTech,
ImsReasonInfo info) {
}
+
+ /**
+ * Returns a list of subscriber {@link Uri}s associated with this IMS subscription when
+ * it changes.
+ * @param uris new array of subscriber {@link Uri}s that are associated with this IMS
+ * subscription.
+ */
+ public void onSubscriberAssociatedUriChanged(Uri[] uris) {
+
+ }
}
private final IImsRegistration mBinder = new IImsRegistration.Stub() {
@@ -139,9 +151,9 @@
private @ImsRegistrationTech
int mConnectionType = REGISTRATION_TECH_NONE;
// Locked on mLock
- private int mRegistrationState = REGISTRATION_STATE_NOT_REGISTERED;
- // Locked on mLock
- private ImsReasonInfo mLastDisconnectCause;
+ private int mRegistrationState = REGISTRATION_STATE_UNKNOWN;
+ // Locked on mLock, create unspecified disconnect cause.
+ private ImsReasonInfo mLastDisconnectCause = new ImsReasonInfo();
public final IImsRegistration getBinder() {
return mBinder;
@@ -221,6 +233,17 @@
});
}
+ public final void onSubscriberAssociatedUriChanged(Uri[] uris) {
+ mCallbacks.broadcast((c) -> {
+ try {
+ c.onSubscriberAssociatedUriChanged(uris);
+ } catch (RemoteException e) {
+ Log.w(LOG_TAG, e + " " + "onSubscriberAssociatedUriChanged() - Skipping " +
+ "callback.");
+ }
+ });
+ }
+
private void updateToState(@ImsRegistrationTech int connType, int newState) {
synchronized (mLock) {
mConnectionType = connType;
@@ -241,7 +264,8 @@
}
}
- private @ImsRegistrationTech int getConnectionType() {
+ @VisibleForTesting
+ public final @ImsRegistrationTech int getConnectionType() {
synchronized (mLock) {
return mConnectionType;
}
@@ -271,6 +295,10 @@
c.onRegistered(getConnectionType());
break;
}
+ case REGISTRATION_STATE_UNKNOWN: {
+ // Do not callback if the state has not been updated yet by the ImsService.
+ break;
+ }
}
}
}
diff --git a/telephony/java/com/android/ims/ImsCallProfile.java b/telephony/java/com/android/ims/ImsCallProfile.java
index 489c208..693aaff 100644
--- a/telephony/java/com/android/ims/ImsCallProfile.java
+++ b/telephony/java/com/android/ims/ImsCallProfile.java
@@ -351,7 +351,7 @@
mServiceType = in.readInt();
mCallType = in.readInt();
mCallExtras = in.readBundle();
- mMediaProfile = in.readParcelable(null);
+ mMediaProfile = in.readParcelable(ImsStreamMediaProfile.class.getClassLoader());
}
public static final Creator<ImsCallProfile> CREATOR = new Creator<ImsCallProfile>() {
diff --git a/telephony/java/android/telephony/ims/internal/aidl/IImsRegistration.aidl b/telephony/java/com/android/ims/internal/IImsRegistration.aidl
similarity index 82%
rename from telephony/java/android/telephony/ims/internal/aidl/IImsRegistration.aidl
rename to telephony/java/com/android/ims/internal/IImsRegistration.aidl
index 687b7ca..6de264e 100644
--- a/telephony/java/android/telephony/ims/internal/aidl/IImsRegistration.aidl
+++ b/telephony/java/com/android/ims/internal/IImsRegistration.aidl
@@ -15,10 +15,9 @@
*/
-package android.telephony.ims.internal.aidl;
+package com.android.ims.internal;
-import android.telephony.ims.internal.aidl.IImsRegistrationCallback;
-import android.telephony.ims.internal.stub.ImsFeatureConfiguration;
+import com.android.ims.internal.IImsRegistrationCallback;
/**
* See ImsRegistration for more information.
diff --git a/telephony/java/android/telephony/ims/internal/aidl/IImsRegistrationCallback.aidl b/telephony/java/com/android/ims/internal/IImsRegistrationCallback.aidl
similarity index 89%
rename from telephony/java/android/telephony/ims/internal/aidl/IImsRegistrationCallback.aidl
rename to telephony/java/com/android/ims/internal/IImsRegistrationCallback.aidl
index a50575b..5f21167 100644
--- a/telephony/java/android/telephony/ims/internal/aidl/IImsRegistrationCallback.aidl
+++ b/telephony/java/com/android/ims/internal/IImsRegistrationCallback.aidl
@@ -15,8 +15,9 @@
*/
-package android.telephony.ims.internal.aidl;
+package com.android.ims.internal;
+import android.net.Uri;
import android.telephony.ims.internal.stub.ImsFeatureConfiguration;
import com.android.ims.ImsReasonInfo;
@@ -31,4 +32,5 @@
void onRegistering(int imsRadioTech);
void onDeregistered(in ImsReasonInfo info);
void onTechnologyChangeFailed(int imsRadioTech, in ImsReasonInfo info);
+ void onSubscriberAssociatedUriChanged(in Uri[] uris);
}
\ No newline at end of file
diff --git a/telephony/java/com/android/ims/internal/IImsServiceController.aidl b/telephony/java/com/android/ims/internal/IImsServiceController.aidl
index 857089f..7ac25ac 100644
--- a/telephony/java/com/android/ims/internal/IImsServiceController.aidl
+++ b/telephony/java/com/android/ims/internal/IImsServiceController.aidl
@@ -18,6 +18,7 @@
import com.android.ims.internal.IImsFeatureStatusCallback;
import com.android.ims.internal.IImsMMTelFeature;
+import com.android.ims.internal.IImsRegistration;
import com.android.ims.internal.IImsRcsFeature;
/**
@@ -29,4 +30,5 @@
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/internal/telephony/IPhoneStateListener.aidl b/telephony/java/com/android/internal/telephony/IPhoneStateListener.aidl
index ac16139..8e3f4c0 100644
--- a/telephony/java/com/android/internal/telephony/IPhoneStateListener.aidl
+++ b/telephony/java/com/android/internal/telephony/IPhoneStateListener.aidl
@@ -46,5 +46,6 @@
void onVoiceActivationStateChanged(int activationState);
void onDataActivationStateChanged(int activationState);
void onCarrierNetworkChange(in boolean active);
+ void onUserMobileDataStateChanged(in boolean enabled);
}
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index 416146f..fba82ee 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -40,6 +40,7 @@
import android.telephony.VisualVoicemailSmsFilterSettings;
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.telephony.CellNetworkScanResult;
import com.android.internal.telephony.OperatorInfo;
@@ -808,6 +809,11 @@
IImsRcsFeature getRcsFeatureAndListen(int slotId, in IImsServiceFeatureCallback callback);
/**
+ * Returns the IImsRegistration associated with the slot and feature specified.
+ */
+ IImsRegistration getImsRegistration(int slotId, int feature);
+
+ /**
* Set the network selection mode to automatic.
*
* @param subId the id of the subscription to update.
diff --git a/telephony/java/com/android/internal/telephony/ITelephonyRegistry.aidl b/telephony/java/com/android/internal/telephony/ITelephonyRegistry.aidl
index 75d8f3f..188167c 100644
--- a/telephony/java/com/android/internal/telephony/ITelephonyRegistry.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephonyRegistry.aidl
@@ -69,4 +69,5 @@
int activationState, int activationType);
void notifySubscriptionInfoChanged();
void notifyCarrierNetworkChange(in boolean active);
+ void notifyUserMobileDataStateChangedForPhoneId(in int phoneId, in int subId, in boolean state);
}
diff --git a/telephony/java/android/telephony/data/InterfaceAddress.aidl b/telephony/java/com/android/internal/telephony/euicc/IEuiccCardController.aidl
similarity index 65%
copy from telephony/java/android/telephony/data/InterfaceAddress.aidl
copy to telephony/java/com/android/internal/telephony/euicc/IEuiccCardController.aidl
index d750363..2846a1a 100644
--- a/telephony/java/android/telephony/data/InterfaceAddress.aidl
+++ b/telephony/java/com/android/internal/telephony/euicc/IEuiccCardController.aidl
@@ -1,5 +1,5 @@
/*
- * Copyright 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,7 +14,11 @@
* limitations under the License.
*/
-/** @hide */
-package android.telephony.data;
+package com.android.internal.telephony.euicc;
-parcelable InterfaceAddress;
+import com.android.internal.telephony.euicc.IGetAllProfilesCallback;
+
+/** @hide */
+interface IEuiccCardController {
+ oneway void getAllProfiles(String callingPackage, in IGetAllProfilesCallback callback);
+}
diff --git a/telephony/java/android/telephony/data/InterfaceAddress.aidl b/telephony/java/com/android/internal/telephony/euicc/IGetAllProfilesCallback.aidl
similarity index 68%
copy from telephony/java/android/telephony/data/InterfaceAddress.aidl
copy to telephony/java/com/android/internal/telephony/euicc/IGetAllProfilesCallback.aidl
index d750363..97b0768 100644
--- a/telephony/java/android/telephony/data/InterfaceAddress.aidl
+++ b/telephony/java/com/android/internal/telephony/euicc/IGetAllProfilesCallback.aidl
@@ -1,5 +1,5 @@
/*
- * Copyright 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.
@@ -13,8 +13,11 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+package com.android.internal.telephony.euicc;
+
+import android.service.euicc.EuiccProfileInfo;
/** @hide */
-package android.telephony.data;
-
-parcelable InterfaceAddress;
+oneway interface IGetAllProfilesCallback {
+ void onComplete(int resultCode, in EuiccProfileInfo[] profiles);
+}
diff --git a/test-base/Android.bp b/test-base/Android.bp
index 0088962..ccf57b0 100644
--- a/test-base/Android.bp
+++ b/test-base/Android.bp
@@ -49,7 +49,8 @@
// Build the repackaged.android.test.base library
// ==============================================
-// This contains repackaged versions of the classes from legacy-test.
+// This contains repackaged versions of the classes from
+// android.test.base.
java_library_static {
name: "repackaged.android.test.base",
@@ -97,7 +98,7 @@
],
static_libs: [
- "android.test.runner",
+ "android.test.runner-minus-junit",
"android.test.mock",
],
diff --git a/test-mock/Android.bp b/test-mock/Android.bp
index 8eddec4..b1ae40e 100644
--- a/test-mock/Android.bp
+++ b/test-mock/Android.bp
@@ -24,7 +24,6 @@
no_framework_libs: true,
libs: [
"framework",
- "legacy-test",
],
}
diff --git a/test-mock/src/android/test/mock/MockPackageManager.java b/test-mock/src/android/test/mock/MockPackageManager.java
index 7207ebc..13e3693 100644
--- a/test-mock/src/android/test/mock/MockPackageManager.java
+++ b/test-mock/src/android/test/mock/MockPackageManager.java
@@ -1174,4 +1174,20 @@
public ArtManager getArtManager() {
throw new UnsupportedOperationException();
}
+
+ /**
+ * @hide
+ */
+ @Override
+ public void setHarmfulAppWarning(String packageName, CharSequence warning) {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * @hide
+ */
+ @Override
+ public CharSequence getHarmfulAppWarning(String packageName) {
+ throw new UnsupportedOperationException();
+ }
}
diff --git a/test-runner/Android.bp b/test-runner/Android.bp
index 104ae82..dfaeed5 100644
--- a/test-runner/Android.bp
+++ b/test-runner/Android.bp
@@ -24,11 +24,28 @@
no_framework_libs: true,
libs: [
"framework",
- "legacy-test",
+ "android.test.base",
"android.test.mock",
],
}
+// Build the android.test.runner-minus-junit library
+// =================================================
+// This is provided solely for use by the legacy-android-test module.
+java_library {
+ name: "android.test.runner-minus-junit",
+
+ srcs: ["src/android/**/*.java"],
+
+ no_framework_libs: true,
+ libs: [
+ "framework",
+ "android.test.base",
+ "android.test.mock",
+ "junit",
+ ],
+}
+
// Build the repackaged.android.test.runner library
// ================================================
java_library_static {
diff --git a/tests/ActivityManagerPerfTests/README.txt b/tests/ActivityManagerPerfTests/README.txt
new file mode 100644
index 0000000..77e0e90
--- /dev/null
+++ b/tests/ActivityManagerPerfTests/README.txt
@@ -0,0 +1,34 @@
+ActivityManagerPerfTests
+
+Performance tests for various ActivityManager components, e.g. Services, Broadcasts
+
+Command to run tests (not working yet, atest seems buggy)
+* atest .../frameworks/base/tests/ActivityManagerPerfTests
+* m ActivityManagerPerfTests ActivityManagerPerfTestsTestApp && \
+ adb install $OUT/data/app/ActivityManagerPerfTests/ActivityManagerPerfTests.apk && \
+ adb install $OUT/data/app/ActivityManagerPerfTestsTestApp/ActivityManagerPerfTestsTestApp.apk && \
+ adb shell am instrument -w \
+ com.android.frameworks.perftests.amtests/android.support.test.runner.AndroidJUnitRunner
+
+Overview
+* The numbers we are trying to measure are end-to-end numbers
+ * For example, the time it takes from sending an Intent to start a Service
+ to the time the Service runs its callbacks
+* System.nanoTime() is monotonic and consistent between processes, so we use that for measuring time
+* To make sure the test app is running, we start an Activity
+* If the test app is involved, it will measure the time and send it back to the instrumentation test
+ * The time is sent back through a Binder interface in the Intent
+ * Each sent time is tagged with an id since there can be multiple events that send back a time
+ * For example, one is sent when the Activity is started, and another could be sent when a
+ Broadcast is received
+
+Structure
+* tests
+ * Instrumentation test which runs the various performance tests and reports the results
+
+* test-app
+ * Target package which contains the Services, BroadcastReceivers, etc. to test against
+ * Sends the time it measures back to the test package
+
+* utils
+ * Utilities that both the instrumentation test and test app can use
diff --git a/tests/ActivityManagerPerfTests/test-app/Android.mk b/tests/ActivityManagerPerfTests/test-app/Android.mk
new file mode 100644
index 0000000..b0a5db7
--- /dev/null
+++ b/tests/ActivityManagerPerfTests/test-app/Android.mk
@@ -0,0 +1,28 @@
+# Copyright (C) 2018 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_PATH := $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_SRC_FILES := \
+ $(call all-java-files-under, src)
+
+LOCAL_STATIC_JAVA_LIBRARIES := \
+ ActivityManagerPerfTestsUtils
+
+LOCAL_PACKAGE_NAME := ActivityManagerPerfTestsTestApp
+
+include $(BUILD_PACKAGE)
diff --git a/tests/ActivityManagerPerfTests/test-app/AndroidManifest.xml b/tests/ActivityManagerPerfTests/test-app/AndroidManifest.xml
new file mode 100644
index 0000000..7145110
--- /dev/null
+++ b/tests/ActivityManagerPerfTests/test-app/AndroidManifest.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2018 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.frameworks.perftests.amteststestapp">
+ <application android:name=".TestApplication">
+ <activity android:name=".TestActivity" android:exported="true"/>
+ <receiver
+ android:name=".TestBroadcastReceiver"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="com.android.frameworks.perftests.ACTION_BROADCAST_MANIFEST_RECEIVE" />
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
+ </receiver>
+ </application>
+</manifest>
diff --git a/tests/ActivityManagerPerfTests/test-app/src/com/android/frameworks/perftests/amteststestapp/TestActivity.java b/tests/ActivityManagerPerfTests/test-app/src/com/android/frameworks/perftests/amteststestapp/TestActivity.java
new file mode 100644
index 0000000..7ea9ba3
--- /dev/null
+++ b/tests/ActivityManagerPerfTests/test-app/src/com/android/frameworks/perftests/amteststestapp/TestActivity.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.android.frameworks.perftests.amteststestapp;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+import com.android.frameworks.perftests.am.util.Constants;
+import com.android.frameworks.perftests.am.util.Utils;
+
+public class TestActivity extends Activity {
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ Utils.sendTime(getIntent(), Constants.TYPE_ACTIVITY_CREATED);
+ }
+}
diff --git a/tests/ActivityManagerPerfTests/test-app/src/com/android/frameworks/perftests/amteststestapp/TestApplication.java b/tests/ActivityManagerPerfTests/test-app/src/com/android/frameworks/perftests/amteststestapp/TestApplication.java
new file mode 100644
index 0000000..e26ffcd
--- /dev/null
+++ b/tests/ActivityManagerPerfTests/test-app/src/com/android/frameworks/perftests/amteststestapp/TestApplication.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.android.frameworks.perftests.amteststestapp;
+
+import android.app.Application;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.util.Log;
+
+import com.android.frameworks.perftests.am.util.Constants;
+import com.android.frameworks.perftests.am.util.Utils;
+
+public class TestApplication extends Application {
+ private static final String TAG = TestApplication.class.getSimpleName();
+
+ @Override
+ public void onCreate() {
+ createRegisteredReceiver();
+
+ super.onCreate();
+ }
+
+ // Create registered BroadcastReceiver
+ private void createRegisteredReceiver() {
+ BroadcastReceiver registered = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ Log.i(TAG, "RegisteredReceiver.onReceive");
+ Utils.sendTime(intent, Constants.TYPE_BROADCAST_RECEIVE);
+ }
+ };
+ IntentFilter intentFilter = new IntentFilter(Constants.ACTION_BROADCAST_REGISTERED_RECEIVE);
+ registerReceiver(registered, intentFilter);
+ }
+}
diff --git a/tests/ActivityManagerPerfTests/test-app/src/com/android/frameworks/perftests/amteststestapp/TestBroadcastReceiver.java b/tests/ActivityManagerPerfTests/test-app/src/com/android/frameworks/perftests/amteststestapp/TestBroadcastReceiver.java
new file mode 100644
index 0000000..336bf9d
--- /dev/null
+++ b/tests/ActivityManagerPerfTests/test-app/src/com/android/frameworks/perftests/amteststestapp/TestBroadcastReceiver.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.android.frameworks.perftests.amteststestapp;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.util.Log;
+
+import com.android.frameworks.perftests.am.util.Constants;
+import com.android.frameworks.perftests.am.util.Utils;
+
+public class TestBroadcastReceiver extends BroadcastReceiver {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ Utils.sendTime(intent, Constants.TYPE_BROADCAST_RECEIVE);
+ }
+}
diff --git a/tests/ActivityManagerPerfTests/tests/Android.mk b/tests/ActivityManagerPerfTests/tests/Android.mk
new file mode 100644
index 0000000..daf603d
--- /dev/null
+++ b/tests/ActivityManagerPerfTests/tests/Android.mk
@@ -0,0 +1,33 @@
+# Copyright (C) 2018 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_PATH := $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_SRC_FILES := \
+ $(call all-java-files-under, src)
+
+LOCAL_STATIC_JAVA_LIBRARIES := \
+ android-support-test \
+ apct-perftests-utils \
+ ActivityManagerPerfTestsUtils
+
+LOCAL_PACKAGE_NAME := ActivityManagerPerfTests
+
+# For android.permission.FORCE_STOP_PACKAGES permission
+LOCAL_CERTIFICATE := platform
+
+include $(BUILD_PACKAGE)
diff --git a/tests/ActivityManagerPerfTests/tests/AndroidManifest.xml b/tests/ActivityManagerPerfTests/tests/AndroidManifest.xml
new file mode 100644
index 0000000..4e194c6
--- /dev/null
+++ b/tests/ActivityManagerPerfTests/tests/AndroidManifest.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2018 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.frameworks.perftests.amtests">
+ <uses-permission android:name="android.permission.DUMP" />
+ <uses-permission android:name="android.permission.FORCE_STOP_PACKAGES" />
+
+ <application>
+ <uses-library android:name="android.test.runner" />
+ </application>
+
+ <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
+ android:targetPackage="com.android.frameworks.perftests.amtests"/>
+</manifest>
diff --git a/tests/ActivityManagerPerfTests/tests/AndroidTest.xml b/tests/ActivityManagerPerfTests/tests/AndroidTest.xml
new file mode 100644
index 0000000..ffb5404
--- /dev/null
+++ b/tests/ActivityManagerPerfTests/tests/AndroidTest.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2018 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<configuration description="Runs ActivityManager Performance Tests">
+ <target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup">
+ <option name="test-file-name" value="ActivityManagerPerfTests.apk"/>
+ <option name="test-file-name" value="ActivityManagerPerfTestsTestApp.apk"/>
+ <option name="cleanup-apks" value="true"/>
+ </target_preparer>
+
+ <option name="test-suite-tag" value="apct"/>
+ <option name="test-tag" value="ActivityManagerPerfTests"/>
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest">
+ <option name="package" value="com.android.frameworks.perftests.amtests"/>
+ <option name="runner" value="android.support.test.runner.AndroidJUnitRunner"/>
+ </test>
+</configuration>
\ No newline at end of file
diff --git a/tests/ActivityManagerPerfTests/tests/src/com/android/frameworks/perftests/am/tests/BasePerfTest.java b/tests/ActivityManagerPerfTests/tests/src/com/android/frameworks/perftests/am/tests/BasePerfTest.java
new file mode 100644
index 0000000..661abe9
--- /dev/null
+++ b/tests/ActivityManagerPerfTests/tests/src/com/android/frameworks/perftests/am/tests/BasePerfTest.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.android.frameworks.perftests.am.tests;
+
+import android.content.Context;
+import android.content.Intent;
+import android.perftests.utils.ManualBenchmarkState;
+import android.perftests.utils.PerfManualStatusReporter;
+import android.support.test.InstrumentationRegistry;
+
+import com.android.frameworks.perftests.am.util.TargetPackageUtils;
+import com.android.frameworks.perftests.am.util.TimeReceiver;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+
+import java.util.function.LongSupplier;
+
+public class BasePerfTest {
+ private static final String TAG = BasePerfTest.class.getSimpleName();
+
+ private TimeReceiver mTimeReceiver;
+
+ @Rule
+ public PerfManualStatusReporter mPerfManualStatusReporter = new PerfManualStatusReporter();
+
+ protected Context mContext;
+
+ @Before
+ public void setUp() {
+ mContext = InstrumentationRegistry.getTargetContext();
+ mTimeReceiver = new TimeReceiver();
+ }
+
+ @After
+ public void tearDown() {
+ TargetPackageUtils.killTargetPackage(mContext);
+ }
+
+ protected Intent createIntent(String action) {
+ final Intent intent = new Intent(action);
+ intent.addFlags(
+ Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND | Intent.FLAG_INCLUDE_STOPPED_PACKAGES);
+ intent.putExtras(mTimeReceiver.createReceiveTimeExtraBinder());
+ return intent;
+ }
+
+ private void setUpIteration() {
+ mTimeReceiver.clear();
+ TargetPackageUtils.killTargetPackage(mContext);
+ }
+
+ protected void startTargetPackage() {
+ TargetPackageUtils.startTargetPackage(mContext, mTimeReceiver);
+ }
+
+ protected long getReceivedTimeNs(String type) {
+ return mTimeReceiver.getReceivedTimeNs(type);
+ }
+
+ protected void runPerfFunction(LongSupplier func) {
+ final ManualBenchmarkState benchmarkState = mPerfManualStatusReporter.getBenchmarkState();
+ long elapsedTimeNs = 0;
+ while (benchmarkState.keepRunning(elapsedTimeNs)) {
+ setUpIteration();
+ elapsedTimeNs = func.getAsLong();
+ }
+ }
+}
diff --git a/tests/ActivityManagerPerfTests/tests/src/com/android/frameworks/perftests/am/tests/BroadcastPerfTest.java b/tests/ActivityManagerPerfTests/tests/src/com/android/frameworks/perftests/am/tests/BroadcastPerfTest.java
new file mode 100644
index 0000000..795f498
--- /dev/null
+++ b/tests/ActivityManagerPerfTests/tests/src/com/android/frameworks/perftests/am/tests/BroadcastPerfTest.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * 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.frameworks.perftests.am.tests;
+
+import android.content.Intent;
+import android.support.test.filters.LargeTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import com.android.frameworks.perftests.am.util.Constants;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@LargeTest
+public class BroadcastPerfTest extends BasePerfTest {
+ @Test
+ public void manifestBroadcastRunning() {
+ runPerfFunction(() -> {
+ startTargetPackage();
+
+ final Intent intent = createIntent(Constants.ACTION_BROADCAST_MANIFEST_RECEIVE);
+
+ final long startTime = System.nanoTime();
+
+ mContext.sendBroadcast(intent);
+
+ final long endTime = getReceivedTimeNs(Constants.TYPE_BROADCAST_RECEIVE);
+
+ return endTime - startTime;
+ });
+ }
+
+ @Test
+ public void manifestBroadcastNotRunning() {
+ runPerfFunction(() -> {
+ final Intent intent = createIntent(Constants.ACTION_BROADCAST_MANIFEST_RECEIVE);
+
+ final long startTime = System.nanoTime();
+
+ mContext.sendBroadcast(intent);
+
+ final long endTime = getReceivedTimeNs(Constants.TYPE_BROADCAST_RECEIVE);
+
+ return endTime - startTime;
+ });
+ }
+
+ @Test
+ public void registeredBroadcast() {
+ runPerfFunction(() -> {
+ startTargetPackage();
+
+ final Intent intent = createIntent(Constants.ACTION_BROADCAST_REGISTERED_RECEIVE);
+
+ final long startTime = System.nanoTime();
+
+ mContext.sendBroadcast(intent);
+
+ final long endTime = getReceivedTimeNs(Constants.TYPE_BROADCAST_RECEIVE);
+
+ return endTime - startTime;
+ });
+ }
+}
diff --git a/tests/ActivityManagerPerfTests/tests/src/com/android/frameworks/perftests/am/util/TargetPackageUtils.java b/tests/ActivityManagerPerfTests/tests/src/com/android/frameworks/perftests/am/util/TargetPackageUtils.java
new file mode 100644
index 0000000..c867141
--- /dev/null
+++ b/tests/ActivityManagerPerfTests/tests/src/com/android/frameworks/perftests/am/util/TargetPackageUtils.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 com.android.frameworks.perftests.am.util;
+
+import android.app.ActivityManager;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.os.SystemClock;
+
+public class TargetPackageUtils {
+ private static final String TAG = TargetPackageUtils.class.getSimpleName();
+
+ public static final String PACKAGE_NAME = "com.android.frameworks.perftests.amteststestapp";
+ public static final String ACTIVITY_NAME = PACKAGE_NAME + ".TestActivity";
+
+ private static final long WAIT_TIME_MS = 100L;
+
+ // Cache for test app's uid, so we only have to query it once.
+ private static int sTestAppUid = -1;
+
+ /**
+ * Kills the test package synchronously.
+ */
+ public static void killTargetPackage(Context context) {
+ ActivityManager activityManager = context.getSystemService(ActivityManager.class);
+ activityManager.forceStopPackage(PACKAGE_NAME);
+ while (targetPackageIsRunning(context)) {
+ sleep();
+ }
+
+ Utils.drainBroadcastQueue();
+ }
+
+ /**
+ * Starts the test package synchronously. It does so by starting an Activity.
+ */
+ public static void startTargetPackage(Context context, TimeReceiver timeReceiver) {
+ // "am start-activity -W PACKAGE_NAME/ACTIVITY_CLASS_NAME" still requires a sleep even
+ // though it should be synchronous, so just use Intent instead
+ final Intent intent = new Intent();
+ intent.putExtras(timeReceiver.createReceiveTimeExtraBinder());
+ intent.setClassName(PACKAGE_NAME, ACTIVITY_NAME);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ context.startActivity(intent);
+
+ while (!targetPackageIsRunning(context)) {
+ sleep();
+ }
+ // make sure Application has run
+ timeReceiver.getReceivedTimeNs(Constants.TYPE_ACTIVITY_CREATED);
+ Utils.drainBroadcastQueue();
+ }
+
+ private static boolean targetPackageIsRunning(Context context) {
+ final int uid = getTestAppUid(context);
+ final String result = Utils.runShellCommand(
+ String.format("cmd activity get-uid-state %d", uid));
+ return !result.contains("(NONEXISTENT)");
+ }
+
+ private static void sleep() {
+ SystemClock.sleep(WAIT_TIME_MS);
+ }
+
+ private static int getTestAppUid(Context context) {
+ if (sTestAppUid == -1) {
+ final PackageManager pm = context.getPackageManager();
+ try {
+ sTestAppUid = pm.getPackageUid(PACKAGE_NAME, 0);
+ } catch (PackageManager.NameNotFoundException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ return sTestAppUid;
+ }
+
+}
+
diff --git a/tests/ActivityManagerPerfTests/tests/src/com/android/frameworks/perftests/am/util/TimeReceiver.java b/tests/ActivityManagerPerfTests/tests/src/com/android/frameworks/perftests/am/util/TimeReceiver.java
new file mode 100644
index 0000000..9cf6ee7
--- /dev/null
+++ b/tests/ActivityManagerPerfTests/tests/src/com/android/frameworks/perftests/am/util/TimeReceiver.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.android.frameworks.perftests.am.util;
+
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.util.Log;
+
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
+
+
+/**
+ * TimeReceiver will listen for any messages containing a timestamp by starting a BroadcastReceiver
+ * which listens for Intents with the SendTime.ACTION_SEND_TIME action.
+ */
+public class TimeReceiver {
+ private static final String TAG = TimeReceiver.class.getSimpleName();
+ private static final long DEFAULT_RECEIVE_TIME_TIMEOUT_MILLIS = 10000L;
+
+ private BlockingQueue<ReceivedMessage> mQueue = new LinkedBlockingQueue<>();
+
+ private static class ReceivedMessage {
+ private final String mReceivedMessageType;
+ private final long mReceivedTimeNs;
+
+ public ReceivedMessage(String receivedMessageType, long receivedTimeNs) {
+ mReceivedMessageType = receivedMessageType;
+ mReceivedTimeNs = receivedTimeNs;
+ }
+ }
+
+ public Bundle createReceiveTimeExtraBinder() {
+ Bundle extras = new Bundle();
+ extras.putBinder(Constants.EXTRA_RECEIVER_CALLBACK, new ITimeReceiverCallback.Stub() {
+ @Override
+ public void sendTime(String type, long timeNs) throws RemoteException {
+ if (type == null) {
+ throw new RuntimeException("receivedType is null");
+ }
+ if (timeNs < 0) {
+ throw new RuntimeException(
+ "receivedTime is negative/non-existant: " + timeNs);
+ }
+ Log.i(TAG, type + " " + timeNs);
+ mQueue.add(new ReceivedMessage(type, timeNs));
+ }
+ });
+ return extras;
+ }
+
+ public long getReceivedTimeNs(String type) {
+ return getReceivedTimeNs(type, DEFAULT_RECEIVE_TIME_TIMEOUT_MILLIS);
+ }
+
+ /**
+ * Returns a received timestamp with the given type tag. Will throw away any messages with a
+ * different type tag. If it times out, a RuntimeException is thrown.
+ */
+ public long getReceivedTimeNs(String type, long timeoutMs) {
+ ReceivedMessage message;
+ long endTimeNs = System.nanoTime() + TimeUnit.MILLISECONDS.toNanos(timeoutMs);
+ do {
+ long curTimeNs = System.nanoTime();
+ if (curTimeNs > endTimeNs) {
+ throw new RuntimeException("Timed out when listening for a time: " + type);
+ }
+ try {
+ Log.i(TAG, "waiting for message " + type);
+ message = mQueue.poll(endTimeNs - curTimeNs, TimeUnit.NANOSECONDS);
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+ if (message == null) {
+ throw new RuntimeException("Timed out when listening for a time: " + type);
+ }
+ Log.i(TAG, "got message " + message.mReceivedMessageType);
+ if (!type.equals(message.mReceivedMessageType)) {
+ Log.i(TAG, String.format("Expected type \"%s\", got \"%s\" (%d), skipping", type,
+ message.mReceivedMessageType, message.mReceivedTimeNs));
+ }
+ } while (!type.equals(message.mReceivedMessageType));
+ return message.mReceivedTimeNs;
+ }
+
+ /**
+ * Clears the message queue.
+ */
+ public void clear() {
+ mQueue.clear();
+ }
+}
diff --git a/tests/ActivityManagerPerfTests/utils/Android.mk b/tests/ActivityManagerPerfTests/utils/Android.mk
new file mode 100644
index 0000000..7276e37
--- /dev/null
+++ b/tests/ActivityManagerPerfTests/utils/Android.mk
@@ -0,0 +1,31 @@
+# Copyright (C) 2018 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_PATH := $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_SRC_FILES := \
+ $(call all-java-files-under, src) \
+ src/com/android/frameworks/perftests/am/util/ITimeReceiverCallback.aidl
+
+LOCAL_STATIC_JAVA_LIBRARIES := \
+ android-support-test \
+ junit \
+ ub-uiautomator
+
+LOCAL_MODULE := ActivityManagerPerfTestsUtils
+
+include $(BUILD_STATIC_JAVA_LIBRARY)
diff --git a/tests/ActivityManagerPerfTests/utils/src/com/android/frameworks/perftests/am/util/Constants.java b/tests/ActivityManagerPerfTests/utils/src/com/android/frameworks/perftests/am/util/Constants.java
new file mode 100644
index 0000000..6528028
--- /dev/null
+++ b/tests/ActivityManagerPerfTests/utils/src/com/android/frameworks/perftests/am/util/Constants.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.android.frameworks.perftests.am.util;
+
+public class Constants {
+ public static final String TYPE_ACTIVITY_CREATED = "activity_create";
+ public static final String TYPE_BROADCAST_RECEIVE = "broadcast_receive";
+
+ public static final String ACTION_BROADCAST_MANIFEST_RECEIVE =
+ "com.android.frameworks.perftests.ACTION_BROADCAST_MANIFEST_RECEIVE";
+ public static final String ACTION_BROADCAST_REGISTERED_RECEIVE =
+ "com.android.frameworks.perftests.ACTION_BROADCAST_REGISTERED_RECEIVE";
+
+ public static final String EXTRA_RECEIVER_CALLBACK = "receiver_callback_binder";
+}
diff --git a/core/java/android/security/recoverablekeystore/KeyStoreRecoveryData.aidl b/tests/ActivityManagerPerfTests/utils/src/com/android/frameworks/perftests/am/util/ITimeReceiverCallback.aidl
similarity index 74%
copy from core/java/android/security/recoverablekeystore/KeyStoreRecoveryData.aidl
copy to tests/ActivityManagerPerfTests/utils/src/com/android/frameworks/perftests/am/util/ITimeReceiverCallback.aidl
index bd76051..b43d49a 100644
--- a/core/java/android/security/recoverablekeystore/KeyStoreRecoveryData.aidl
+++ b/tests/ActivityManagerPerfTests/utils/src/com/android/frameworks/perftests/am/util/ITimeReceiverCallback.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,7 +14,8 @@
* limitations under the License.
*/
-package android.security.recoverablekeystore;
+package com.android.frameworks.perftests.am.util;
-/* @hide */
-parcelable KeyStoreRecoveryData;
+interface ITimeReceiverCallback {
+ void sendTime(String type, long timeNs);
+}
\ No newline at end of file
diff --git a/tests/ActivityManagerPerfTests/utils/src/com/android/frameworks/perftests/am/util/Utils.java b/tests/ActivityManagerPerfTests/utils/src/com/android/frameworks/perftests/am/util/Utils.java
new file mode 100644
index 0000000..493d8cd
--- /dev/null
+++ b/tests/ActivityManagerPerfTests/utils/src/com/android/frameworks/perftests/am/util/Utils.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.android.frameworks.perftests.am.util;
+
+import android.content.Intent;
+import android.os.RemoteException;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.uiautomator.UiDevice;
+import android.util.Log;
+
+import java.io.IOException;
+
+public class Utils {
+ private static final String TAG = "AmPerfTestsUtils";
+
+ public static void drainBroadcastQueue() {
+ runShellCommand("am wait-for-broadcast-idle");
+ }
+
+ /**
+ * Runs the command and returns the stdout.
+ */
+ public static String runShellCommand(String cmd) {
+ try {
+ return UiDevice.getInstance(
+ InstrumentationRegistry.getInstrumentation()).executeShellCommand(cmd);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Sends the current time in a message with the given type so TimeReceiver can receive it.
+ */
+ public static void sendTime(Intent intent, String type) {
+ final long time = System.nanoTime();
+ final ITimeReceiverCallback sendTimeBinder = ITimeReceiverCallback.Stub.asInterface(
+ intent.getExtras().getBinder(Constants.EXTRA_RECEIVER_CALLBACK));
+ try {
+ sendTimeBinder.sendTime(type, time);
+ } catch (RemoteException e) {
+ Log.e(TAG, e.getMessage());
+ }
+ }
+}
diff --git a/tests/net/java/android/net/MacAddressTest.java b/tests/net/java/android/net/MacAddressTest.java
index 473dc538..9aad413 100644
--- a/tests/net/java/android/net/MacAddressTest.java
+++ b/tests/net/java/android/net/MacAddressTest.java
@@ -67,7 +67,7 @@
assertEquals(msg, t.expectedType, got);
if (got != MacAddress.TYPE_UNKNOWN) {
- assertEquals(got, MacAddress.fromBytes(t.addr).addressType());
+ assertEquals(got, MacAddress.fromBytes(t.addr).getAddressType());
}
}
}
@@ -191,7 +191,7 @@
assertTrue(stringRepr + " expected to be a locally assigned address",
mac.isLocallyAssigned());
- assertEquals(MacAddress.TYPE_UNICAST, mac.addressType());
+ assertEquals(MacAddress.TYPE_UNICAST, mac.getAddressType());
assertTrue(stringRepr + " expected to begin with " + expectedLocalOui,
stringRepr.startsWith(expectedLocalOui));
}
diff --git a/tests/net/java/android/net/NetworkTest.java b/tests/net/java/android/net/NetworkTest.java
index bacf986..94d01e9 100644
--- a/tests/net/java/android/net/NetworkTest.java
+++ b/tests/net/java/android/net/NetworkTest.java
@@ -147,9 +147,9 @@
// Adjust as necessary to test an implementation's specific constants.
// When running with runtest, "adb logcat -s TestRunner" can be useful.
- assertEquals(4311403230L, one.getNetworkHandle());
- assertEquals(8606370526L, two.getNetworkHandle());
- assertEquals(12901337822L, three.getNetworkHandle());
+ assertEquals(7700664333L, one.getNetworkHandle());
+ assertEquals(11995631629L, two.getNetworkHandle());
+ assertEquals(16290598925L, three.getNetworkHandle());
}
private static <T> void assertNotEqual(T t1, T t2) {
diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java
index 2b0349c..b8e37f3 100644
--- a/tests/net/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java
@@ -1392,39 +1392,75 @@
return null;
}
- void expectAvailableCallbacks(
- MockNetworkAgent agent, boolean expectSuspended, int timeoutMs) {
+ // Expects onAvailable and the callbacks that follow it. These are:
+ // - onSuspended, iff the network was suspended when the callbacks fire.
+ // - onCapabilitiesChanged.
+ // - onLinkPropertiesChanged.
+ //
+ // @param agent the network to expect the callbacks on.
+ // @param expectSuspended whether to expect a SUSPENDED callback.
+ // @param expectValidated the expected value of the VALIDATED capability in the
+ // onCapabilitiesChanged callback.
+ // @param timeoutMs how long to wait for the callbacks.
+ void expectAvailableCallbacks(MockNetworkAgent agent, boolean expectSuspended,
+ boolean expectValidated, int timeoutMs) {
expectCallback(CallbackState.AVAILABLE, agent, timeoutMs);
if (expectSuspended) {
expectCallback(CallbackState.SUSPENDED, agent, timeoutMs);
}
- expectCallback(CallbackState.NETWORK_CAPABILITIES, agent, timeoutMs);
+ if (expectValidated) {
+ expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, agent);
+ } else {
+ expectCapabilitiesWithout(NET_CAPABILITY_VALIDATED, agent);
+ }
expectCallback(CallbackState.LINK_PROPERTIES, agent, timeoutMs);
}
- void expectAvailableCallbacks(MockNetworkAgent agent) {
- expectAvailableCallbacks(agent, false, TIMEOUT_MS);
+ // Expects the available callbacks (validated), plus onSuspended.
+ void expectAvailableAndSuspendedCallbacks(MockNetworkAgent agent, boolean expectValidated) {
+ expectAvailableCallbacks(agent, true, expectValidated, TIMEOUT_MS);
}
- void expectAvailableAndSuspendedCallbacks(MockNetworkAgent agent) {
- expectAvailableCallbacks(agent, true, TIMEOUT_MS);
+ void expectAvailableCallbacksValidated(MockNetworkAgent agent) {
+ expectAvailableCallbacks(agent, false, true, TIMEOUT_MS);
}
- void expectAvailableAndValidatedCallbacks(MockNetworkAgent agent) {
- expectAvailableCallbacks(agent, false, TIMEOUT_MS);
+ void expectAvailableCallbacksUnvalidated(MockNetworkAgent agent) {
+ expectAvailableCallbacks(agent, false, false, TIMEOUT_MS);
+ }
+
+ // Expects the available callbacks (where the onCapabilitiesChanged must contain the
+ // VALIDATED capability), plus another onCapabilitiesChanged which is identical to the
+ // one we just sent.
+ // TODO: this is likely a bug. Fix it and remove this method.
+ void expectAvailableDoubleValidatedCallbacks(MockNetworkAgent agent) {
+ expectCallback(CallbackState.AVAILABLE, agent, TIMEOUT_MS);
+ NetworkCapabilities nc1 = expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, agent);
+ expectCallback(CallbackState.LINK_PROPERTIES, agent, TIMEOUT_MS);
+ NetworkCapabilities nc2 = expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, agent);
+ assertEquals(nc1, nc2);
+ }
+
+ // Expects the available callbacks where the onCapabilitiesChanged must not have validated,
+ // then expects another onCapabilitiesChanged that has the validated bit set. This is used
+ // when a network connects and satisfies a callback, and then immediately validates.
+ void expectAvailableThenValidatedCallbacks(MockNetworkAgent agent) {
+ expectAvailableCallbacksUnvalidated(agent);
expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, agent);
}
- void expectCapabilitiesWith(int capability, MockNetworkAgent agent) {
+ NetworkCapabilities expectCapabilitiesWith(int capability, MockNetworkAgent agent) {
CallbackInfo cbi = expectCallback(CallbackState.NETWORK_CAPABILITIES, agent);
NetworkCapabilities nc = (NetworkCapabilities) cbi.arg;
assertTrue(nc.hasCapability(capability));
+ return nc;
}
- void expectCapabilitiesWithout(int capability, MockNetworkAgent agent) {
+ NetworkCapabilities expectCapabilitiesWithout(int capability, MockNetworkAgent agent) {
CallbackInfo cbi = expectCallback(CallbackState.NETWORK_CAPABILITIES, agent);
NetworkCapabilities nc = (NetworkCapabilities) cbi.arg;
assertFalse(nc.hasCapability(capability));
+ return nc;
}
void assertNoCallback() {
@@ -1461,8 +1497,8 @@
ConditionVariable cv = waitForConnectivityBroadcasts(1);
mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
mCellNetworkAgent.connect(false);
- genericNetworkCallback.expectAvailableCallbacks(mCellNetworkAgent);
- cellNetworkCallback.expectAvailableCallbacks(mCellNetworkAgent);
+ genericNetworkCallback.expectAvailableCallbacksUnvalidated(mCellNetworkAgent);
+ cellNetworkCallback.expectAvailableCallbacksUnvalidated(mCellNetworkAgent);
assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork());
waitFor(cv);
assertNoCallbacks(genericNetworkCallback, wifiNetworkCallback, cellNetworkCallback);
@@ -1476,8 +1512,8 @@
cv = waitForConnectivityBroadcasts(2);
mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
mWiFiNetworkAgent.connect(false);
- genericNetworkCallback.expectAvailableCallbacks(mWiFiNetworkAgent);
- wifiNetworkCallback.expectAvailableCallbacks(mWiFiNetworkAgent);
+ genericNetworkCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
+ wifiNetworkCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork());
waitFor(cv);
assertNoCallbacks(genericNetworkCallback, wifiNetworkCallback, cellNetworkCallback);
@@ -1500,8 +1536,8 @@
// Test validated networks
mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
mCellNetworkAgent.connect(true);
- genericNetworkCallback.expectAvailableAndValidatedCallbacks(mCellNetworkAgent);
- cellNetworkCallback.expectAvailableAndValidatedCallbacks(mCellNetworkAgent);
+ genericNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
+ cellNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork());
assertNoCallbacks(genericNetworkCallback, wifiNetworkCallback, cellNetworkCallback);
@@ -1513,10 +1549,10 @@
mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
mWiFiNetworkAgent.connect(true);
- genericNetworkCallback.expectAvailableCallbacks(mWiFiNetworkAgent);
+ genericNetworkCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
genericNetworkCallback.expectCallback(CallbackState.LOSING, mCellNetworkAgent);
genericNetworkCallback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent);
- wifiNetworkCallback.expectAvailableAndValidatedCallbacks(mWiFiNetworkAgent);
+ wifiNetworkCallback.expectAvailableThenValidatedCallbacks(mWiFiNetworkAgent);
cellNetworkCallback.expectCallback(CallbackState.LOSING, mCellNetworkAgent);
assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork());
assertNoCallbacks(genericNetworkCallback, wifiNetworkCallback, cellNetworkCallback);
@@ -1552,32 +1588,32 @@
mEthernetNetworkAgent.addCapability(NET_CAPABILITY_NOT_METERED);
mCellNetworkAgent.connect(true);
- callback.expectAvailableAndValidatedCallbacks(mCellNetworkAgent);
- defaultCallback.expectAvailableAndValidatedCallbacks(mCellNetworkAgent);
+ callback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
+ defaultCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork());
mWiFiNetworkAgent.connect(true);
// We get AVAILABLE on wifi when wifi connects and satisfies our unmetered request.
// We then get LOSING when wifi validates and cell is outscored.
- callback.expectAvailableCallbacks(mWiFiNetworkAgent);
+ callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
// TODO: Investigate sending validated before losing.
callback.expectCallback(CallbackState.LOSING, mCellNetworkAgent);
callback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent);
- defaultCallback.expectAvailableAndValidatedCallbacks(mWiFiNetworkAgent);
+ defaultCallback.expectAvailableDoubleValidatedCallbacks(mWiFiNetworkAgent);
assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork());
mEthernetNetworkAgent.connect(true);
- callback.expectAvailableCallbacks(mEthernetNetworkAgent);
+ callback.expectAvailableCallbacksUnvalidated(mEthernetNetworkAgent);
// TODO: Investigate sending validated before losing.
callback.expectCallback(CallbackState.LOSING, mWiFiNetworkAgent);
callback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mEthernetNetworkAgent);
- defaultCallback.expectAvailableAndValidatedCallbacks(mEthernetNetworkAgent);
+ defaultCallback.expectAvailableDoubleValidatedCallbacks(mEthernetNetworkAgent);
assertEquals(mEthernetNetworkAgent.getNetwork(), mCm.getActiveNetwork());
mEthernetNetworkAgent.disconnect();
callback.expectCallback(CallbackState.LOST, mEthernetNetworkAgent);
defaultCallback.expectCallback(CallbackState.LOST, mEthernetNetworkAgent);
- defaultCallback.expectAvailableCallbacks(mWiFiNetworkAgent);
+ defaultCallback.expectAvailableCallbacksValidated(mWiFiNetworkAgent);
for (int i = 0; i < 4; i++) {
MockNetworkAgent oldNetwork, newNetwork;
@@ -1594,7 +1630,7 @@
callback.expectCallback(CallbackState.LOSING, oldNetwork);
// TODO: should we send an AVAILABLE callback to newNetwork, to indicate that it is no
// longer lingering?
- defaultCallback.expectAvailableCallbacks(newNetwork);
+ defaultCallback.expectAvailableCallbacksValidated(newNetwork);
assertEquals(newNetwork.getNetwork(), mCm.getActiveNetwork());
}
assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork());
@@ -1614,7 +1650,7 @@
// Disconnect our test networks.
mWiFiNetworkAgent.disconnect();
defaultCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
- defaultCallback.expectAvailableCallbacks(mCellNetworkAgent);
+ defaultCallback.expectAvailableCallbacksValidated(mCellNetworkAgent);
mCellNetworkAgent.disconnect();
defaultCallback.expectCallback(CallbackState.LOST, mCellNetworkAgent);
@@ -1630,22 +1666,22 @@
mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
mCellNetworkAgent.connect(false); // Score: 10
- callback.expectAvailableCallbacks(mCellNetworkAgent);
- defaultCallback.expectAvailableCallbacks(mCellNetworkAgent);
+ callback.expectAvailableCallbacksUnvalidated(mCellNetworkAgent);
+ defaultCallback.expectAvailableCallbacksUnvalidated(mCellNetworkAgent);
assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork());
// Bring up wifi with a score of 20.
// Cell stays up because it would satisfy the default request if it validated.
mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
mWiFiNetworkAgent.connect(false); // Score: 20
- callback.expectAvailableCallbacks(mWiFiNetworkAgent);
- defaultCallback.expectAvailableCallbacks(mWiFiNetworkAgent);
+ callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
+ defaultCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork());
mWiFiNetworkAgent.disconnect();
callback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
defaultCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
- defaultCallback.expectAvailableCallbacks(mCellNetworkAgent);
+ defaultCallback.expectAvailableCallbacksUnvalidated(mCellNetworkAgent);
assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork());
// Bring up wifi with a score of 70.
@@ -1653,33 +1689,33 @@
mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
mWiFiNetworkAgent.adjustScore(50);
mWiFiNetworkAgent.connect(false); // Score: 70
- callback.expectAvailableCallbacks(mWiFiNetworkAgent);
+ callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
callback.expectCallback(CallbackState.LOSING, mCellNetworkAgent);
- defaultCallback.expectAvailableCallbacks(mWiFiNetworkAgent);
+ defaultCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork());
// Tear down wifi.
mWiFiNetworkAgent.disconnect();
callback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
defaultCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
- defaultCallback.expectAvailableCallbacks(mCellNetworkAgent);
+ defaultCallback.expectAvailableCallbacksUnvalidated(mCellNetworkAgent);
assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork());
// Bring up wifi, then validate it. Previous versions would immediately tear down cell, but
// it's arguably correct to linger it, since it was the default network before it validated.
mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
mWiFiNetworkAgent.connect(true);
- callback.expectAvailableCallbacks(mWiFiNetworkAgent);
+ callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
// TODO: Investigate sending validated before losing.
callback.expectCallback(CallbackState.LOSING, mCellNetworkAgent);
callback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent);
- defaultCallback.expectAvailableAndValidatedCallbacks(mWiFiNetworkAgent);
+ defaultCallback.expectAvailableThenValidatedCallbacks(mWiFiNetworkAgent);
assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork());
mWiFiNetworkAgent.disconnect();
callback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
defaultCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
- defaultCallback.expectAvailableCallbacks(mCellNetworkAgent);
+ defaultCallback.expectAvailableCallbacksUnvalidated(mCellNetworkAgent);
mCellNetworkAgent.disconnect();
callback.expectCallback(CallbackState.LOST, mCellNetworkAgent);
defaultCallback.expectCallback(CallbackState.LOST, mCellNetworkAgent);
@@ -1687,12 +1723,12 @@
// If a network is lingering, and we add and remove a request from it, resume lingering.
mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
mCellNetworkAgent.connect(true);
- callback.expectAvailableAndValidatedCallbacks(mCellNetworkAgent);
- defaultCallback.expectAvailableAndValidatedCallbacks(mCellNetworkAgent);
+ callback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
+ defaultCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
mWiFiNetworkAgent.connect(true);
- defaultCallback.expectAvailableAndValidatedCallbacks(mWiFiNetworkAgent);
- callback.expectAvailableCallbacks(mWiFiNetworkAgent);
+ defaultCallback.expectAvailableDoubleValidatedCallbacks(mWiFiNetworkAgent);
+ callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
// TODO: Investigate sending validated before losing.
callback.expectCallback(CallbackState.LOSING, mCellNetworkAgent);
callback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent);
@@ -1711,7 +1747,7 @@
mWiFiNetworkAgent.disconnect();
callback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
defaultCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
- defaultCallback.expectAvailableCallbacks(mCellNetworkAgent);
+ defaultCallback.expectAvailableCallbacksValidated(mCellNetworkAgent);
// Cell is now the default network. Pin it with a cell-specific request.
noopCallback = new NetworkCallback(); // Can't reuse NetworkCallbacks. http://b/20701525
@@ -1720,8 +1756,8 @@
// Now connect wifi, and expect it to become the default network.
mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
mWiFiNetworkAgent.connect(true);
- callback.expectAvailableAndValidatedCallbacks(mWiFiNetworkAgent);
- defaultCallback.expectAvailableAndValidatedCallbacks(mWiFiNetworkAgent);
+ callback.expectAvailableThenValidatedCallbacks(mWiFiNetworkAgent);
+ defaultCallback.expectAvailableDoubleValidatedCallbacks(mWiFiNetworkAgent);
// The default request is lingering on cell, but nothing happens to cell, and we send no
// callbacks for it, because it's kept up by cellRequest.
callback.assertNoCallback();
@@ -1737,14 +1773,14 @@
// Register a TRACK_DEFAULT request and check that it does not affect lingering.
TestNetworkCallback trackDefaultCallback = new TestNetworkCallback();
mCm.registerDefaultNetworkCallback(trackDefaultCallback);
- trackDefaultCallback.expectAvailableCallbacks(mWiFiNetworkAgent);
+ trackDefaultCallback.expectAvailableCallbacksValidated(mWiFiNetworkAgent);
mEthernetNetworkAgent = new MockNetworkAgent(TRANSPORT_ETHERNET);
mEthernetNetworkAgent.connect(true);
- callback.expectAvailableCallbacks(mEthernetNetworkAgent);
+ callback.expectAvailableCallbacksUnvalidated(mEthernetNetworkAgent);
callback.expectCallback(CallbackState.LOSING, mWiFiNetworkAgent);
callback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mEthernetNetworkAgent);
- trackDefaultCallback.expectAvailableAndValidatedCallbacks(mEthernetNetworkAgent);
- defaultCallback.expectAvailableAndValidatedCallbacks(mEthernetNetworkAgent);
+ trackDefaultCallback.expectAvailableDoubleValidatedCallbacks(mEthernetNetworkAgent);
+ defaultCallback.expectAvailableDoubleValidatedCallbacks(mEthernetNetworkAgent);
// Let linger run its course.
callback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent, lingerTimeoutMs);
@@ -1771,13 +1807,13 @@
// Bring up validated cell.
mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
mCellNetworkAgent.connect(true);
- callback.expectAvailableAndValidatedCallbacks(mCellNetworkAgent);
+ callback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
// Bring up unvalidated wifi with explicitlySelected=true.
mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
mWiFiNetworkAgent.explicitlySelected(false);
mWiFiNetworkAgent.connect(false);
- callback.expectAvailableCallbacks(mWiFiNetworkAgent);
+ callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
// Cell Remains the default.
assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork());
@@ -1800,7 +1836,7 @@
mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
mWiFiNetworkAgent.explicitlySelected(false);
mWiFiNetworkAgent.connect(false);
- callback.expectAvailableCallbacks(mWiFiNetworkAgent);
+ callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
// If the user chooses no on the "No Internet access, stay connected?" dialog, we ask the
// network to disconnect.
@@ -1811,7 +1847,7 @@
mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
mWiFiNetworkAgent.explicitlySelected(false);
mWiFiNetworkAgent.connect(true);
- callback.expectAvailableCallbacks(mWiFiNetworkAgent);
+ callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
callback.expectCallback(CallbackState.LOSING, mCellNetworkAgent);
callback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent);
assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork());
@@ -1820,7 +1856,7 @@
// TODO: fix this.
mEthernetNetworkAgent = new MockNetworkAgent(TRANSPORT_ETHERNET);
mEthernetNetworkAgent.connect(true);
- callback.expectAvailableAndValidatedCallbacks(mEthernetNetworkAgent);
+ callback.expectAvailableThenValidatedCallbacks(mEthernetNetworkAgent);
assertEquals(mEthernetNetworkAgent.getNetwork(), mCm.getActiveNetwork());
callback.assertNoCallback();
@@ -1993,7 +2029,7 @@
mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
mCellNetworkAgent.addCapability(NET_CAPABILITY_MMS);
mCellNetworkAgent.connectWithoutInternet();
- networkCallback.expectAvailableCallbacks(mCellNetworkAgent);
+ networkCallback.expectAvailableCallbacksUnvalidated(mCellNetworkAgent);
verifyActiveNetwork(TRANSPORT_WIFI);
// Test releasing NetworkRequest disconnects cellular with MMS
@@ -2022,7 +2058,7 @@
MockNetworkAgent mmsNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
mmsNetworkAgent.addCapability(NET_CAPABILITY_MMS);
mmsNetworkAgent.connectWithoutInternet();
- networkCallback.expectAvailableCallbacks(mmsNetworkAgent);
+ networkCallback.expectAvailableCallbacksUnvalidated(mmsNetworkAgent);
verifyActiveNetwork(TRANSPORT_CELLULAR);
// Test releasing MMS NetworkRequest does not disconnect main cellular NetworkAgent
@@ -2049,7 +2085,7 @@
mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
String firstRedirectUrl = "http://example.com/firstPath";
mWiFiNetworkAgent.connectWithCaptivePortal(firstRedirectUrl);
- captivePortalCallback.expectAvailableCallbacks(mWiFiNetworkAgent);
+ captivePortalCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
assertEquals(mWiFiNetworkAgent.waitForRedirectUrl(), firstRedirectUrl);
// Take down network.
@@ -2062,7 +2098,7 @@
mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
String secondRedirectUrl = "http://example.com/secondPath";
mWiFiNetworkAgent.connectWithCaptivePortal(secondRedirectUrl);
- captivePortalCallback.expectAvailableCallbacks(mWiFiNetworkAgent);
+ captivePortalCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
assertEquals(mWiFiNetworkAgent.waitForRedirectUrl(), secondRedirectUrl);
// Make captive portal disappear then revalidate.
@@ -2072,9 +2108,7 @@
captivePortalCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
// Expect NET_CAPABILITY_VALIDATED onAvailable callback.
- validatedCallback.expectAvailableCallbacks(mWiFiNetworkAgent);
- // TODO: Investigate only sending available callbacks.
- validatedCallback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent);
+ validatedCallback.expectAvailableDoubleValidatedCallbacks(mWiFiNetworkAgent);
// Break network connectivity.
// Expect NET_CAPABILITY_VALIDATED onLost callback.
@@ -2098,7 +2132,7 @@
// Bring up wifi.
mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
mWiFiNetworkAgent.connect(true);
- validatedCallback.expectAvailableAndValidatedCallbacks(mWiFiNetworkAgent);
+ validatedCallback.expectAvailableDoubleValidatedCallbacks(mWiFiNetworkAgent);
Network wifiNetwork = mWiFiNetworkAgent.getNetwork();
// Check that calling startCaptivePortalApp does nothing.
@@ -2109,7 +2143,7 @@
// Turn into a captive portal.
mWiFiNetworkAgent.getWrappedNetworkMonitor().gen204ProbeResult = 302;
mCm.reportNetworkConnectivity(wifiNetwork, false);
- captivePortalCallback.expectAvailableCallbacks(mWiFiNetworkAgent);
+ captivePortalCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
validatedCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
// Check that startCaptivePortalApp sends the expected intent.
@@ -2122,7 +2156,7 @@
mWiFiNetworkAgent.getWrappedNetworkMonitor().gen204ProbeResult = 204;
CaptivePortal c = (CaptivePortal) intent.getExtra(ConnectivityManager.EXTRA_CAPTIVE_PORTAL);
c.reportCaptivePortalDismissed();
- validatedCallback.expectAvailableCallbacks(mWiFiNetworkAgent);
+ validatedCallback.expectAvailableCallbacksValidated(mWiFiNetworkAgent);
captivePortalCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
mCm.unregisterNetworkCallback(validatedCallback);
@@ -2165,7 +2199,7 @@
mWiFiNetworkAgent.connectWithCaptivePortal(secondRedirectUrl);
// Expect NET_CAPABILITY_VALIDATED onAvailable callback.
- validatedCallback.expectAvailableCallbacks(mWiFiNetworkAgent);
+ validatedCallback.expectAvailableCallbacksValidated(mWiFiNetworkAgent);
// But there should be no CaptivePortal callback.
captivePortalCallback.assertNoCallback();
}
@@ -2203,14 +2237,14 @@
mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
mWiFiNetworkAgent.connect(false);
- cEmpty1.expectAvailableCallbacks(mWiFiNetworkAgent);
- cEmpty2.expectAvailableCallbacks(mWiFiNetworkAgent);
- cEmpty3.expectAvailableCallbacks(mWiFiNetworkAgent);
- cEmpty4.expectAvailableCallbacks(mWiFiNetworkAgent);
+ cEmpty1.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
+ cEmpty2.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
+ cEmpty3.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
+ cEmpty4.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
assertNoCallbacks(cFoo, cBar);
mWiFiNetworkAgent.setNetworkSpecifier(new StringNetworkSpecifier("foo"));
- cFoo.expectAvailableCallbacks(mWiFiNetworkAgent);
+ cFoo.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
for (TestNetworkCallback c: emptyCallbacks) {
c.expectCallback(CallbackState.NETWORK_CAPABILITIES, mWiFiNetworkAgent);
}
@@ -2219,7 +2253,7 @@
mWiFiNetworkAgent.setNetworkSpecifier(new StringNetworkSpecifier("bar"));
cFoo.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
- cBar.expectAvailableCallbacks(mWiFiNetworkAgent);
+ cBar.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
for (TestNetworkCallback c: emptyCallbacks) {
c.expectCallback(CallbackState.NETWORK_CAPABILITIES, mWiFiNetworkAgent);
}
@@ -2348,14 +2382,14 @@
// Bring up cell and expect CALLBACK_AVAILABLE.
mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
mCellNetworkAgent.connect(true);
- cellNetworkCallback.expectAvailableAndValidatedCallbacks(mCellNetworkAgent);
- defaultNetworkCallback.expectAvailableAndValidatedCallbacks(mCellNetworkAgent);
+ cellNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
+ defaultNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
// Bring up wifi and expect CALLBACK_AVAILABLE.
mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
mWiFiNetworkAgent.connect(true);
cellNetworkCallback.assertNoCallback();
- defaultNetworkCallback.expectAvailableAndValidatedCallbacks(mWiFiNetworkAgent);
+ defaultNetworkCallback.expectAvailableDoubleValidatedCallbacks(mWiFiNetworkAgent);
// Bring down cell. Expect no default network callback, since it wasn't the default.
mCellNetworkAgent.disconnect();
@@ -2365,7 +2399,7 @@
// Bring up cell. Expect no default network callback, since it won't be the default.
mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
mCellNetworkAgent.connect(true);
- cellNetworkCallback.expectAvailableAndValidatedCallbacks(mCellNetworkAgent);
+ cellNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
defaultNetworkCallback.assertNoCallback();
// Bring down wifi. Expect the default network callback to notified of LOST wifi
@@ -2373,7 +2407,7 @@
mWiFiNetworkAgent.disconnect();
cellNetworkCallback.assertNoCallback();
defaultNetworkCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
- defaultNetworkCallback.expectAvailableCallbacks(mCellNetworkAgent);
+ defaultNetworkCallback.expectAvailableCallbacksValidated(mCellNetworkAgent);
mCellNetworkAgent.disconnect();
cellNetworkCallback.expectCallback(CallbackState.LOST, mCellNetworkAgent);
defaultNetworkCallback.expectCallback(CallbackState.LOST, mCellNetworkAgent);
@@ -2394,7 +2428,7 @@
// We should get onAvailable(), onCapabilitiesChanged(), and
// onLinkPropertiesChanged() in rapid succession. Additionally, we
// should get onCapabilitiesChanged() when the mobile network validates.
- cellNetworkCallback.expectAvailableAndValidatedCallbacks(mCellNetworkAgent);
+ cellNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
cellNetworkCallback.assertNoCallback();
// Update LinkProperties.
@@ -2415,7 +2449,7 @@
mCm.registerDefaultNetworkCallback(dfltNetworkCallback);
// We should get onAvailable(), onCapabilitiesChanged(), onLinkPropertiesChanged(),
// as well as onNetworkSuspended() in rapid succession.
- dfltNetworkCallback.expectAvailableAndSuspendedCallbacks(mCellNetworkAgent);
+ dfltNetworkCallback.expectAvailableAndSuspendedCallbacks(mCellNetworkAgent, true);
dfltNetworkCallback.assertNoCallback();
mCm.unregisterNetworkCallback(dfltNetworkCallback);
@@ -2455,18 +2489,18 @@
mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
mCellNetworkAgent.connect(true);
- callback.expectAvailableAndValidatedCallbacks(mCellNetworkAgent);
- fgCallback.expectAvailableAndValidatedCallbacks(mCellNetworkAgent);
+ callback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
+ fgCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
assertTrue(isForegroundNetwork(mCellNetworkAgent));
mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
mWiFiNetworkAgent.connect(true);
// When wifi connects, cell lingers.
- callback.expectAvailableCallbacks(mWiFiNetworkAgent);
+ callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
callback.expectCallback(CallbackState.LOSING, mCellNetworkAgent);
callback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent);
- fgCallback.expectAvailableCallbacks(mWiFiNetworkAgent);
+ fgCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
fgCallback.expectCallback(CallbackState.LOSING, mCellNetworkAgent);
fgCallback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent);
assertTrue(isForegroundNetwork(mCellNetworkAgent));
@@ -2490,8 +2524,8 @@
// is currently delivered before the onAvailable() callbacks.
// TODO: Fix this.
cellCallback.expectCapabilitiesWith(NET_CAPABILITY_FOREGROUND, mCellNetworkAgent);
- cellCallback.expectAvailableCallbacks(mCellNetworkAgent);
- fgCallback.expectAvailableCallbacks(mCellNetworkAgent);
+ cellCallback.expectAvailableCallbacksValidated(mCellNetworkAgent);
+ fgCallback.expectAvailableCallbacksValidated(mCellNetworkAgent);
// Expect a network capabilities update with FOREGROUND, because the most recent
// request causes its state to change.
callback.expectCapabilitiesWith(NET_CAPABILITY_FOREGROUND, mCellNetworkAgent);
@@ -2511,7 +2545,7 @@
mWiFiNetworkAgent.disconnect();
callback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
fgCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
- fgCallback.expectAvailableCallbacks(mCellNetworkAgent);
+ fgCallback.expectAvailableCallbacksValidated(mCellNetworkAgent);
assertTrue(isForegroundNetwork(mCellNetworkAgent));
mCm.unregisterNetworkCallback(callback);
@@ -2651,7 +2685,7 @@
mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
testFactory.expectAddRequests(2); // Because the cell request changes score twice.
mCellNetworkAgent.connect(true);
- cellNetworkCallback.expectAvailableAndValidatedCallbacks(mCellNetworkAgent);
+ cellNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
testFactory.waitForNetworkRequests(2);
assertFalse(testFactory.getMyStartRequested()); // Because the cell network outscores us.
@@ -2742,16 +2776,15 @@
// Bring up validated cell.
mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
mCellNetworkAgent.connect(true);
- cellNetworkCallback.expectAvailableAndValidatedCallbacks(mCellNetworkAgent);
- defaultCallback.expectAvailableAndValidatedCallbacks(mCellNetworkAgent);
+ cellNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
+ defaultCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
Network cellNetwork = mCellNetworkAgent.getNetwork();
// Bring up validated wifi.
mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
mWiFiNetworkAgent.connect(true);
- defaultCallback.expectAvailableAndValidatedCallbacks(mWiFiNetworkAgent);
- validatedWifiCallback.expectAvailableCallbacks(mWiFiNetworkAgent);
- validatedWifiCallback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent);
+ defaultCallback.expectAvailableDoubleValidatedCallbacks(mWiFiNetworkAgent);
+ validatedWifiCallback.expectAvailableDoubleValidatedCallbacks(mWiFiNetworkAgent);
Network wifiNetwork = mWiFiNetworkAgent.getNetwork();
// Fail validation on wifi.
@@ -2772,18 +2805,18 @@
// that we switch back to cell.
tracker.configRestrictsAvoidBadWifi = false;
tracker.reevaluate();
- defaultCallback.expectAvailableCallbacks(mCellNetworkAgent);
+ defaultCallback.expectAvailableCallbacksValidated(mCellNetworkAgent);
assertEquals(mCm.getActiveNetwork(), cellNetwork);
// Switch back to a restrictive carrier.
tracker.configRestrictsAvoidBadWifi = true;
tracker.reevaluate();
- defaultCallback.expectAvailableCallbacks(mWiFiNetworkAgent);
+ defaultCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
assertEquals(mCm.getActiveNetwork(), wifiNetwork);
// Simulate the user selecting "switch" on the dialog, and check that we switch to cell.
mCm.setAvoidUnvalidated(wifiNetwork);
- defaultCallback.expectAvailableCallbacks(mCellNetworkAgent);
+ defaultCallback.expectAvailableCallbacksValidated(mCellNetworkAgent);
assertFalse(mCm.getNetworkCapabilities(wifiNetwork).hasCapability(
NET_CAPABILITY_VALIDATED));
assertTrue(mCm.getNetworkCapabilities(cellNetwork).hasCapability(
@@ -2794,9 +2827,8 @@
mWiFiNetworkAgent.disconnect();
mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
mWiFiNetworkAgent.connect(true);
- defaultCallback.expectAvailableAndValidatedCallbacks(mWiFiNetworkAgent);
- validatedWifiCallback.expectAvailableCallbacks(mWiFiNetworkAgent);
- validatedWifiCallback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent);
+ defaultCallback.expectAvailableDoubleValidatedCallbacks(mWiFiNetworkAgent);
+ validatedWifiCallback.expectAvailableDoubleValidatedCallbacks(mWiFiNetworkAgent);
wifiNetwork = mWiFiNetworkAgent.getNetwork();
// Fail validation on wifi and expect the dialog to appear.
@@ -2810,7 +2842,7 @@
tracker.reevaluate();
// We now switch to cell.
- defaultCallback.expectAvailableCallbacks(mCellNetworkAgent);
+ defaultCallback.expectAvailableCallbacksValidated(mCellNetworkAgent);
assertFalse(mCm.getNetworkCapabilities(wifiNetwork).hasCapability(
NET_CAPABILITY_VALIDATED));
assertTrue(mCm.getNetworkCapabilities(cellNetwork).hasCapability(
@@ -2821,17 +2853,17 @@
// We switch to wifi and then to cell.
Settings.Global.putString(cr, Settings.Global.NETWORK_AVOID_BAD_WIFI, null);
tracker.reevaluate();
- defaultCallback.expectAvailableCallbacks(mWiFiNetworkAgent);
+ defaultCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
assertEquals(mCm.getActiveNetwork(), wifiNetwork);
Settings.Global.putInt(cr, Settings.Global.NETWORK_AVOID_BAD_WIFI, 1);
tracker.reevaluate();
- defaultCallback.expectAvailableCallbacks(mCellNetworkAgent);
+ defaultCallback.expectAvailableCallbacksValidated(mCellNetworkAgent);
assertEquals(mCm.getActiveNetwork(), cellNetwork);
// If cell goes down, we switch to wifi.
mCellNetworkAgent.disconnect();
defaultCallback.expectCallback(CallbackState.LOST, mCellNetworkAgent);
- defaultCallback.expectAvailableCallbacks(mWiFiNetworkAgent);
+ defaultCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
validatedWifiCallback.assertNoCallback();
mCm.unregisterNetworkCallback(cellNetworkCallback);
@@ -2873,7 +2905,7 @@
mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
mWiFiNetworkAgent.connect(false);
- networkCallback.expectAvailableCallbacks(mWiFiNetworkAgent, false, timeoutMs);
+ networkCallback.expectAvailableCallbacks(mWiFiNetworkAgent, false, false, timeoutMs);
// pass timeout and validate that UNAVAILABLE is not called
networkCallback.assertNoCallback();
@@ -2894,7 +2926,7 @@
mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
mWiFiNetworkAgent.connect(false);
final int assertTimeoutMs = 100;
- networkCallback.expectAvailableCallbacks(mWiFiNetworkAgent, false, assertTimeoutMs);
+ networkCallback.expectAvailableCallbacks(mWiFiNetworkAgent, false, false, assertTimeoutMs);
mWiFiNetworkAgent.disconnect();
networkCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
@@ -3381,7 +3413,7 @@
// Bring up wifi aware network.
wifiAware.connect(false, false);
- callback.expectAvailableCallbacks(wifiAware);
+ callback.expectAvailableCallbacksUnvalidated(wifiAware);
assertNull(mCm.getActiveNetworkInfo());
assertNull(mCm.getActiveNetwork());
diff --git a/tools/aapt2/ResourceParser.cpp b/tools/aapt2/ResourceParser.cpp
index 24b28dd..7cffeea 100644
--- a/tools/aapt2/ResourceParser.cpp
+++ b/tools/aapt2/ResourceParser.cpp
@@ -17,6 +17,7 @@
#include "ResourceParser.h"
#include <functional>
+#include <limits>
#include <sstream>
#include "android-base/logging.h"
@@ -987,8 +988,7 @@
type_mask = ParseFormatAttribute(maybe_format.value());
if (type_mask == 0) {
diag_->Error(DiagMessage(source_.WithLine(parser->line_number()))
- << "invalid attribute format '" << maybe_format.value()
- << "'");
+ << "invalid attribute format '" << maybe_format.value() << "'");
return false;
}
}
@@ -1000,8 +1000,7 @@
if (!min_str.empty()) {
std::u16string min_str16 = util::Utf8ToUtf16(min_str);
android::Res_value value;
- if (android::ResTable::stringToInt(min_str16.data(), min_str16.size(),
- &value)) {
+ if (android::ResTable::stringToInt(min_str16.data(), min_str16.size(), &value)) {
maybe_min = static_cast<int32_t>(value.data);
}
}
@@ -1018,8 +1017,7 @@
if (!max_str.empty()) {
std::u16string max_str16 = util::Utf8ToUtf16(max_str);
android::Res_value value;
- if (android::ResTable::stringToInt(max_str16.data(), max_str16.size(),
- &value)) {
+ if (android::ResTable::stringToInt(max_str16.data(), max_str16.size(), &value)) {
maybe_max = static_cast<int32_t>(value.data);
}
}
@@ -1061,8 +1059,7 @@
const Source item_source = source_.WithLine(parser->line_number());
const std::string& element_namespace = parser->element_namespace();
const std::string& element_name = parser->element_name();
- if (element_namespace.empty() &&
- (element_name == "flag" || element_name == "enum")) {
+ if (element_namespace.empty() && (element_name == "flag" || element_name == "enum")) {
if (element_name == "enum") {
if (type_mask & android::ResTable_map::TYPE_FLAGS) {
diag_->Error(DiagMessage(item_source)
@@ -1120,17 +1117,12 @@
return false;
}
- std::unique_ptr<Attribute> attr = util::make_unique<Attribute>(weak);
+ std::unique_ptr<Attribute> attr = util::make_unique<Attribute>(
+ type_mask ? type_mask : uint32_t{android::ResTable_map::TYPE_ANY});
+ attr->SetWeak(weak);
attr->symbols = std::vector<Attribute::Symbol>(items.begin(), items.end());
- attr->type_mask =
- type_mask ? type_mask : uint32_t(android::ResTable_map::TYPE_ANY);
- if (maybe_min) {
- attr->min_int = maybe_min.value();
- }
-
- if (maybe_max) {
- attr->max_int = maybe_max.value();
- }
+ attr->min_int = maybe_min.value_or_default(std::numeric_limits<int32_t>::min());
+ attr->max_int = maybe_max.value_or_default(std::numeric_limits<int32_t>::max());
out_resource->value = std::move(attr);
return true;
}
@@ -1445,11 +1437,9 @@
const std::string& element_namespace = parser->element_namespace();
const std::string& element_name = parser->element_name();
if (element_namespace.empty() && element_name == "attr") {
- Maybe<StringPiece> maybe_name =
- xml::FindNonEmptyAttribute(parser, "name");
+ Maybe<StringPiece> maybe_name = xml::FindNonEmptyAttribute(parser, "name");
if (!maybe_name) {
- diag_->Error(DiagMessage(item_source)
- << "<attr> tag must have a 'name' attribute");
+ diag_->Error(DiagMessage(item_source) << "<attr> tag must have a 'name' attribute");
error = true;
continue;
}
@@ -1457,8 +1447,7 @@
// If this is a declaration, the package name may be in the name. Separate
// these out.
// Eg. <attr name="android:text" />
- Maybe<Reference> maybe_ref =
- ResourceUtils::ParseXmlAttributeName(maybe_name.value());
+ Maybe<Reference> maybe_ref = ResourceUtils::ParseXmlAttributeName(maybe_name.value());
if (!maybe_ref) {
diag_->Error(DiagMessage(item_source) << "<attr> tag has invalid name '"
<< maybe_name.value() << "'");
diff --git a/tools/aapt2/ResourceTable.cpp b/tools/aapt2/ResourceTable.cpp
index 3172892..9905f82 100644
--- a/tools/aapt2/ResourceTable.cpp
+++ b/tools/aapt2/ResourceTable.cpp
@@ -262,9 +262,8 @@
// attributes all-over, we do special handling to see
// which definition sticks.
//
- if (existing_attr->type_mask == incoming_attr->type_mask) {
- // The two attributes are both DECLs, but they are plain attributes
- // with the same formats.
+ if (existing_attr->IsCompatibleWith(*incoming_attr)) {
+ // The two attributes are both DECLs, but they are plain attributes with compatible formats.
// Keep the strongest one.
return existing_attr->IsWeak() ? CollisionResult::kTakeNew : CollisionResult::kKeepOriginal;
}
diff --git a/tools/aapt2/ResourceTable.h b/tools/aapt2/ResourceTable.h
index eaa2d0b..95e30c4 100644
--- a/tools/aapt2/ResourceTable.h
+++ b/tools/aapt2/ResourceTable.h
@@ -81,10 +81,7 @@
DISALLOW_COPY_AND_ASSIGN(ResourceConfigValue);
};
-/**
- * Represents a resource entry, which may have
- * varying values for each defined configuration.
- */
+// Represents a resource entry, which may have varying values for each defined configuration.
class ResourceEntry {
public:
// The name of the resource. Immutable, as this determines the order of this resource
diff --git a/tools/aapt2/ResourceTable_test.cpp b/tools/aapt2/ResourceTable_test.cpp
index eb75f94..7fa8ea2 100644
--- a/tools/aapt2/ResourceTable_test.cpp
+++ b/tools/aapt2/ResourceTable_test.cpp
@@ -102,23 +102,37 @@
TEST(ResourceTableTest, OverrideWeakResourceValue) {
ResourceTable table;
- ASSERT_TRUE(table.AddResource(
- test::ParseNameOrDie("android:attr/foo"), ConfigDescription{}, "",
- util::make_unique<Attribute>(true), test::GetDiagnostics()));
+ ASSERT_TRUE(table.AddResource(test::ParseNameOrDie("android:attr/foo"), ConfigDescription{}, "",
+ test::AttributeBuilder().SetWeak(true).Build(),
+ test::GetDiagnostics()));
Attribute* attr = test::GetValue<Attribute>(&table, "android:attr/foo");
ASSERT_THAT(attr, NotNull());
EXPECT_TRUE(attr->IsWeak());
- ASSERT_TRUE(table.AddResource(
- test::ParseNameOrDie("android:attr/foo"), ConfigDescription{}, "",
- util::make_unique<Attribute>(false), test::GetDiagnostics()));
+ ASSERT_TRUE(table.AddResource(test::ParseNameOrDie("android:attr/foo"), ConfigDescription{}, "",
+ util::make_unique<Attribute>(), test::GetDiagnostics()));
attr = test::GetValue<Attribute>(&table, "android:attr/foo");
ASSERT_THAT(attr, NotNull());
EXPECT_FALSE(attr->IsWeak());
}
+TEST(ResourceTableTest, AllowCompatibleDuplicateAttributes) {
+ ResourceTable table;
+
+ const ResourceName name = test::ParseNameOrDie("android:attr/foo");
+ Attribute attr_one(android::ResTable_map::TYPE_STRING);
+ attr_one.SetWeak(true);
+ Attribute attr_two(android::ResTable_map::TYPE_STRING | android::ResTable_map::TYPE_REFERENCE);
+ attr_two.SetWeak(true);
+
+ ASSERT_TRUE(table.AddResource(name, ConfigDescription{}, "",
+ util::make_unique<Attribute>(attr_one), test::GetDiagnostics()));
+ ASSERT_TRUE(table.AddResource(name, ConfigDescription{}, "",
+ util::make_unique<Attribute>(attr_two), test::GetDiagnostics()));
+}
+
TEST(ResourceTableTest, ProductVaryingValues) {
ResourceTable table;
diff --git a/tools/aapt2/ResourceUtils_test.cpp b/tools/aapt2/ResourceUtils_test.cpp
index e637c3e..cb786d3 100644
--- a/tools/aapt2/ResourceUtils_test.cpp
+++ b/tools/aapt2/ResourceUtils_test.cpp
@@ -179,12 +179,11 @@
}
TEST(ResourceUtilsTest, ParseEmptyFlag) {
- std::unique_ptr<Attribute> attr =
- test::AttributeBuilder(false)
- .SetTypeMask(ResTable_map::TYPE_FLAGS)
- .AddItem("one", 0x01)
- .AddItem("two", 0x02)
- .Build();
+ std::unique_ptr<Attribute> attr = test::AttributeBuilder()
+ .SetTypeMask(ResTable_map::TYPE_FLAGS)
+ .AddItem("one", 0x01)
+ .AddItem("two", 0x02)
+ .Build();
std::unique_ptr<BinaryPrimitive> result = ResourceUtils::TryParseFlagSymbol(attr.get(), "");
ASSERT_THAT(result, NotNull());
diff --git a/tools/aapt2/ResourceValues.cpp b/tools/aapt2/ResourceValues.cpp
index a782cd3..77cee06 100644
--- a/tools/aapt2/ResourceValues.cpp
+++ b/tools/aapt2/ResourceValues.cpp
@@ -507,17 +507,10 @@
}
}
-Attribute::Attribute()
- : type_mask(0u),
- min_int(std::numeric_limits<int32_t>::min()),
- max_int(std::numeric_limits<int32_t>::max()) {
-}
-
-Attribute::Attribute(bool w, uint32_t t)
+Attribute::Attribute(uint32_t t)
: type_mask(t),
min_int(std::numeric_limits<int32_t>::min()),
max_int(std::numeric_limits<int32_t>::max()) {
- weak_ = w;
}
std::ostream& operator<<(std::ostream& out, const Attribute::Symbol& s) {
@@ -568,6 +561,20 @@
});
}
+bool Attribute::IsCompatibleWith(const Attribute& attr) const {
+ // If the high bits are set on any of these attribute type masks, then they are incompatible.
+ // We don't check that flags and enums are identical.
+ if ((type_mask & ~android::ResTable_map::TYPE_ANY) != 0 ||
+ (attr.type_mask & ~android::ResTable_map::TYPE_ANY) != 0) {
+ return false;
+ }
+
+ // Every attribute accepts a reference.
+ uint32_t this_type_mask = type_mask | android::ResTable_map::TYPE_REFERENCE;
+ uint32_t that_type_mask = attr.type_mask | android::ResTable_map::TYPE_REFERENCE;
+ return this_type_mask == that_type_mask;
+}
+
Attribute* Attribute::Clone(StringPool* /*new_pool*/) const {
return new Attribute(*this);
}
diff --git a/tools/aapt2/ResourceValues.h b/tools/aapt2/ResourceValues.h
index b2ec8bdd..6371c4c 100644
--- a/tools/aapt2/ResourceValues.h
+++ b/tools/aapt2/ResourceValues.h
@@ -300,10 +300,15 @@
int32_t max_int;
std::vector<Symbol> symbols;
- Attribute();
- explicit Attribute(bool w, uint32_t t = 0u);
+ explicit Attribute(uint32_t t = 0u);
bool Equals(const Value* value) const override;
+
+ // Returns true if this Attribute's format is compatible with the given Attribute. The basic
+ // rule is that TYPE_REFERENCE can be ignored for both of the Attributes, and TYPE_FLAGS and
+ // TYPE_ENUMS are never compatible.
+ bool IsCompatibleWith(const Attribute& attr) const;
+
Attribute* Clone(StringPool* new_pool) const override;
std::string MaskString() const;
void Print(std::ostream* out) const override;
diff --git a/tools/aapt2/ResourceValues_test.cpp b/tools/aapt2/ResourceValues_test.cpp
index a80a9dc..c4a1108 100644
--- a/tools/aapt2/ResourceValues_test.cpp
+++ b/tools/aapt2/ResourceValues_test.cpp
@@ -24,6 +24,18 @@
namespace aapt {
+namespace {
+
+// Attribute types.
+constexpr const uint32_t TYPE_DIMENSION = android::ResTable_map::TYPE_DIMENSION;
+constexpr const uint32_t TYPE_ENUM = android::ResTable_map::TYPE_ENUM;
+constexpr const uint32_t TYPE_FLAGS = android::ResTable_map::TYPE_FLAGS;
+constexpr const uint32_t TYPE_INTEGER = android::ResTable_map::TYPE_INTEGER;
+constexpr const uint32_t TYPE_REFERENCE = android::Res_value::TYPE_REFERENCE;
+constexpr const uint32_t TYPE_STRING = android::ResTable_map::TYPE_STRING;
+
+} // namespace
+
TEST(ResourceValuesTest, PluralEquals) {
StringPool pool;
@@ -206,23 +218,19 @@
android::Res_value value = {};
ASSERT_TRUE(Reference().Flatten(&value));
- EXPECT_EQ(android::Res_value::TYPE_REFERENCE, value.dataType);
- EXPECT_EQ(0x0u, value.data);
+ EXPECT_THAT(value.dataType, Eq(android::Res_value::TYPE_REFERENCE));
+ EXPECT_THAT(value.data, Eq(0u));
}
TEST(ResourcesValuesTest, AttributeMatches) {
- constexpr const uint32_t TYPE_DIMENSION = android::ResTable_map::TYPE_DIMENSION;
- constexpr const uint32_t TYPE_ENUM = android::ResTable_map::TYPE_ENUM;
- constexpr const uint32_t TYPE_FLAGS = android::ResTable_map::TYPE_FLAGS;
- constexpr const uint32_t TYPE_INTEGER = android::ResTable_map::TYPE_INTEGER;
constexpr const uint8_t TYPE_INT_DEC = android::Res_value::TYPE_INT_DEC;
- Attribute attr1(false /*weak*/, TYPE_DIMENSION);
+ Attribute attr1(TYPE_DIMENSION);
EXPECT_FALSE(attr1.Matches(*ResourceUtils::TryParseColor("#7fff00")));
EXPECT_TRUE(attr1.Matches(*ResourceUtils::TryParseFloat("23dp")));
EXPECT_TRUE(attr1.Matches(*ResourceUtils::TryParseReference("@android:string/foo")));
- Attribute attr2(false /*weak*/, TYPE_INTEGER | TYPE_ENUM);
+ Attribute attr2(TYPE_INTEGER | TYPE_ENUM);
attr2.min_int = 0;
attr2.symbols.push_back(Attribute::Symbol{Reference(test::ParseNameOrDie("android:id/foo")),
static_cast<uint32_t>(-1)});
@@ -231,7 +239,7 @@
EXPECT_TRUE(attr2.Matches(BinaryPrimitive(TYPE_INT_DEC, 1u)));
EXPECT_FALSE(attr2.Matches(BinaryPrimitive(TYPE_INT_DEC, static_cast<uint32_t>(-2))));
- Attribute attr3(false /*weak*/, TYPE_INTEGER | TYPE_FLAGS);
+ Attribute attr3(TYPE_INTEGER | TYPE_FLAGS);
attr3.max_int = 100;
attr3.symbols.push_back(
Attribute::Symbol{Reference(test::ParseNameOrDie("android:id/foo")), 0x01u});
@@ -251,11 +259,33 @@
// Not a flag and greater than max_int.
EXPECT_FALSE(attr3.Matches(BinaryPrimitive(TYPE_INT_DEC, 127u)));
- Attribute attr4(false /*weak*/, TYPE_ENUM);
+ Attribute attr4(TYPE_ENUM);
attr4.symbols.push_back(
Attribute::Symbol{Reference(test::ParseNameOrDie("android:id/foo")), 0x01u});
EXPECT_TRUE(attr4.Matches(BinaryPrimitive(TYPE_INT_DEC, 0x01u)));
EXPECT_FALSE(attr4.Matches(BinaryPrimitive(TYPE_INT_DEC, 0x02u)));
}
+TEST(ResourcesValuesTest, AttributeIsCompatible) {
+ Attribute attr_one(TYPE_STRING | TYPE_REFERENCE);
+ Attribute attr_two(TYPE_STRING);
+ Attribute attr_three(TYPE_ENUM);
+ Attribute attr_four(TYPE_REFERENCE);
+
+ EXPECT_TRUE(attr_one.IsCompatibleWith(attr_one));
+ EXPECT_TRUE(attr_one.IsCompatibleWith(attr_two));
+ EXPECT_FALSE(attr_one.IsCompatibleWith(attr_three));
+ EXPECT_FALSE(attr_one.IsCompatibleWith(attr_four));
+
+ EXPECT_TRUE(attr_two.IsCompatibleWith(attr_one));
+ EXPECT_TRUE(attr_two.IsCompatibleWith(attr_two));
+ EXPECT_FALSE(attr_two.IsCompatibleWith(attr_three));
+ EXPECT_FALSE(attr_two.IsCompatibleWith(attr_four));
+
+ EXPECT_FALSE(attr_three.IsCompatibleWith(attr_one));
+ EXPECT_FALSE(attr_three.IsCompatibleWith(attr_two));
+ EXPECT_FALSE(attr_three.IsCompatibleWith(attr_three));
+ EXPECT_FALSE(attr_three.IsCompatibleWith(attr_four));
+}
+
} // namespace aapt
diff --git a/tools/aapt2/cmd/Compile.cpp b/tools/aapt2/cmd/Compile.cpp
index 3bec082..101f74e 100644
--- a/tools/aapt2/cmd/Compile.cpp
+++ b/tools/aapt2/cmd/Compile.cpp
@@ -772,8 +772,8 @@
if (*type != ResourceType::kRaw) {
if (path_data.extension == "xml") {
compile_func = &CompileXml;
- } else if (!options.no_png_crunch &&
- (path_data.extension == "png" || path_data.extension == "9.png")) {
+ } else if ((!options.no_png_crunch && path_data.extension == "png") ||
+ path_data.extension == "9.png") {
compile_func = &CompilePng;
}
}
diff --git a/tools/aapt2/cmd/Link.cpp b/tools/aapt2/cmd/Link.cpp
index 72e07dc..c9e272c 100644
--- a/tools/aapt2/cmd/Link.cpp
+++ b/tools/aapt2/cmd/Link.cpp
@@ -388,10 +388,8 @@
// generated from the attribute definitions themselves (b/62028956).
if (const SymbolTable::Symbol* s = symm->FindById(R::attr::paddingHorizontal)) {
std::vector<ReplacementAttr> replacements{
- {"paddingLeft", R::attr::paddingLeft,
- Attribute(false, android::ResTable_map::TYPE_DIMENSION)},
- {"paddingRight", R::attr::paddingRight,
- Attribute(false, android::ResTable_map::TYPE_DIMENSION)},
+ {"paddingLeft", R::attr::paddingLeft, Attribute(android::ResTable_map::TYPE_DIMENSION)},
+ {"paddingRight", R::attr::paddingRight, Attribute(android::ResTable_map::TYPE_DIMENSION)},
};
rules_[R::attr::paddingHorizontal] =
util::make_unique<DegradeToManyRule>(std::move(replacements));
@@ -399,10 +397,8 @@
if (const SymbolTable::Symbol* s = symm->FindById(R::attr::paddingVertical)) {
std::vector<ReplacementAttr> replacements{
- {"paddingTop", R::attr::paddingTop,
- Attribute(false, android::ResTable_map::TYPE_DIMENSION)},
- {"paddingBottom", R::attr::paddingBottom,
- Attribute(false, android::ResTable_map::TYPE_DIMENSION)},
+ {"paddingTop", R::attr::paddingTop, Attribute(android::ResTable_map::TYPE_DIMENSION)},
+ {"paddingBottom", R::attr::paddingBottom, Attribute(android::ResTable_map::TYPE_DIMENSION)},
};
rules_[R::attr::paddingVertical] =
util::make_unique<DegradeToManyRule>(std::move(replacements));
@@ -411,9 +407,9 @@
if (const SymbolTable::Symbol* s = symm->FindById(R::attr::layout_marginHorizontal)) {
std::vector<ReplacementAttr> replacements{
{"layout_marginLeft", R::attr::layout_marginLeft,
- Attribute(false, android::ResTable_map::TYPE_DIMENSION)},
+ Attribute(android::ResTable_map::TYPE_DIMENSION)},
{"layout_marginRight", R::attr::layout_marginRight,
- Attribute(false, android::ResTable_map::TYPE_DIMENSION)},
+ Attribute(android::ResTable_map::TYPE_DIMENSION)},
};
rules_[R::attr::layout_marginHorizontal] =
util::make_unique<DegradeToManyRule>(std::move(replacements));
@@ -422,9 +418,9 @@
if (const SymbolTable::Symbol* s = symm->FindById(R::attr::layout_marginVertical)) {
std::vector<ReplacementAttr> replacements{
{"layout_marginTop", R::attr::layout_marginTop,
- Attribute(false, android::ResTable_map::TYPE_DIMENSION)},
+ Attribute(android::ResTable_map::TYPE_DIMENSION)},
{"layout_marginBottom", R::attr::layout_marginBottom,
- Attribute(false, android::ResTable_map::TYPE_DIMENSION)},
+ Attribute(android::ResTable_map::TYPE_DIMENSION)},
};
rules_[R::attr::layout_marginVertical] =
util::make_unique<DegradeToManyRule>(std::move(replacements));
diff --git a/tools/aapt2/format/binary/BinaryResourceParser.cpp b/tools/aapt2/format/binary/BinaryResourceParser.cpp
index 8d079ff..8215ddf 100644
--- a/tools/aapt2/format/binary/BinaryResourceParser.cpp
+++ b/tools/aapt2/format/binary/BinaryResourceParser.cpp
@@ -504,8 +504,8 @@
std::unique_ptr<Attribute> BinaryResourceParser::ParseAttr(const ResourceNameRef& name,
const ConfigDescription& config,
const ResTable_map_entry* map) {
- const bool is_weak = (util::DeviceToHost16(map->flags) & ResTable_entry::FLAG_WEAK) != 0;
- std::unique_ptr<Attribute> attr = util::make_unique<Attribute>(is_weak);
+ std::unique_ptr<Attribute> attr = util::make_unique<Attribute>();
+ attr->SetWeak((util::DeviceToHost16(map->flags) & ResTable_entry::FLAG_WEAK) != 0);
// First we must discover what type of attribute this is. Find the type mask.
auto type_mask_iter = std::find_if(begin(map), end(map), [](const ResTable_map& entry) -> bool {
diff --git a/tools/aapt2/format/binary/TableFlattener_test.cpp b/tools/aapt2/format/binary/TableFlattener_test.cpp
index 51ccdc7..bab7010 100644
--- a/tools/aapt2/format/binary/TableFlattener_test.cpp
+++ b/tools/aapt2/format/binary/TableFlattener_test.cpp
@@ -215,7 +215,7 @@
}
TEST_F(TableFlattenerTest, FlattenMinMaxAttributes) {
- Attribute attr(false);
+ Attribute attr;
attr.type_mask = android::ResTable_map::TYPE_INTEGER;
attr.min_int = 10;
attr.max_int = 23;
diff --git a/tools/aapt2/format/proto/ProtoDeserialize.cpp b/tools/aapt2/format/proto/ProtoDeserialize.cpp
index 3d6975d..d8635a9 100644
--- a/tools/aapt2/format/proto/ProtoDeserialize.cpp
+++ b/tools/aapt2/format/proto/ProtoDeserialize.cpp
@@ -635,8 +635,7 @@
switch (pb_compound_value.value_case()) {
case pb::CompoundValue::kAttr: {
const pb::Attribute& pb_attr = pb_compound_value.attr();
- std::unique_ptr<Attribute> attr = util::make_unique<Attribute>();
- attr->type_mask = pb_attr.format_flags();
+ std::unique_ptr<Attribute> attr = util::make_unique<Attribute>(pb_attr.format_flags());
attr->min_int = pb_attr.min_int();
attr->max_int = pb_attr.max_int();
for (const pb::Attribute_Symbol& pb_symbol : pb_attr.symbol()) {
diff --git a/tools/aapt2/format/proto/ProtoSerialize_test.cpp b/tools/aapt2/format/proto/ProtoSerialize_test.cpp
index d7f83fd..ccba5c6 100644
--- a/tools/aapt2/format/proto/ProtoSerialize_test.cpp
+++ b/tools/aapt2/format/proto/ProtoSerialize_test.cpp
@@ -180,7 +180,7 @@
attr.name = "name";
attr.namespace_uri = xml::kSchemaAndroid;
attr.value = "23dp";
- attr.compiled_attribute = xml::AaptAttribute({}, ResourceId(0x01010000));
+ attr.compiled_attribute = xml::AaptAttribute(Attribute{}, ResourceId(0x01010000));
attr.compiled_value =
ResourceUtils::TryParseItemForAttribute(attr.value, android::ResTable_map::TYPE_DIMENSION);
attr.compiled_value->SetSource(Source().WithLine(25));
diff --git a/tools/aapt2/integration-tests/BasicTest/Android.mk b/tools/aapt2/integration-tests/BasicTest/Android.mk
new file mode 100644
index 0000000..6d2aac6
--- /dev/null
+++ b/tools/aapt2/integration-tests/BasicTest/Android.mk
@@ -0,0 +1,23 @@
+#
+# Copyright (C) 2017 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+LOCAL_USE_AAPT2 := true
+LOCAL_PACKAGE_NAME := AaptBasicTest
+LOCAL_MODULE_TAGS := tests
+include $(BUILD_PACKAGE)
diff --git a/tools/aapt2/integration-tests/BasicTest/AndroidManifest.xml b/tools/aapt2/integration-tests/BasicTest/AndroidManifest.xml
new file mode 100644
index 0000000..3743c40
--- /dev/null
+++ b/tools/aapt2/integration-tests/BasicTest/AndroidManifest.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.aapt.basic">
+
+ <uses-sdk android:minSdkVersion="21" />
+</manifest>
diff --git a/tools/aapt2/integration-tests/BasicTest/res/values/values.xml b/tools/aapt2/integration-tests/BasicTest/res/values/values.xml
new file mode 100644
index 0000000..8d6bb43
--- /dev/null
+++ b/tools/aapt2/integration-tests/BasicTest/res/values/values.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<resources>
+ <declare-styleable name="AttrConflictStyleableOne">
+ <attr name="format_conflict" format="string|reference" />
+ </declare-styleable>
+
+ <declare-styleable name="AttrConflictStyleableTwo">
+ <attr name="format_conflict" format="string" />
+ </declare-styleable>
+</resources>
diff --git a/tools/aapt2/java/JavaClassGenerator_test.cpp b/tools/aapt2/java/JavaClassGenerator_test.cpp
index 5beb594..e449546 100644
--- a/tools/aapt2/java/JavaClassGenerator_test.cpp
+++ b/tools/aapt2/java/JavaClassGenerator_test.cpp
@@ -57,7 +57,7 @@
.SetPackageId("android", 0x01)
.AddSimple("android:id/hey-man", ResourceId(0x01020000))
.AddValue("android:attr/cool.attr", ResourceId(0x01010000),
- test::AttributeBuilder(false).Build())
+ test::AttributeBuilder().Build())
.AddValue("android:styleable/hey.dude", ResourceId(0x01030000),
test::StyleableBuilder()
.AddItem("android:attr/cool.attr", ResourceId(0x01010000))
@@ -229,10 +229,8 @@
test::ResourceTableBuilder()
.SetPackageId("android", 0x01)
.SetPackageId("com.lib", 0x02)
- .AddValue("android:attr/bar", ResourceId(0x01010000),
- test::AttributeBuilder(false).Build())
- .AddValue("com.lib:attr/bar", ResourceId(0x02010000),
- test::AttributeBuilder(false).Build())
+ .AddValue("android:attr/bar", ResourceId(0x01010000), test::AttributeBuilder().Build())
+ .AddValue("com.lib:attr/bar", ResourceId(0x02010000), test::AttributeBuilder().Build())
.AddValue("android:styleable/foo", ResourceId(0x01030000),
test::StyleableBuilder()
.AddItem("android:attr/bar", ResourceId(0x01010000))
@@ -290,7 +288,7 @@
TEST(JavaClassGeneratorTest, CommentsForEnumAndFlagAttributesArePresent) {}
TEST(JavaClassGeneratorTest, CommentsForStyleablesAndNestedAttributesArePresent) {
- Attribute attr(false);
+ Attribute attr;
attr.SetComment(StringPiece("This is an attribute"));
Styleable styleable;
@@ -375,7 +373,7 @@
}
TEST(JavaClassGeneratorTest, CommentsForRemovedAttributesAreNotPresentInClass) {
- Attribute attr(false);
+ Attribute attr;
attr.SetComment(StringPiece("removed"));
std::unique_ptr<ResourceTable> table =
@@ -413,7 +411,7 @@
std::unique_ptr<ResourceTable> table =
test::ResourceTableBuilder()
.SetPackageId("android", 0x00)
- .AddValue("android:attr/foo", ResourceId(0x00010000), util::make_unique<Attribute>(false))
+ .AddValue("android:attr/foo", ResourceId(0x00010000), util::make_unique<Attribute>())
.AddValue("android:id/foo", ResourceId(0x00020000), util::make_unique<Id>())
.AddValue(
"android:style/foo", ResourceId(0x00030000),
diff --git a/tools/aapt2/link/XmlCompatVersioner_test.cpp b/tools/aapt2/link/XmlCompatVersioner_test.cpp
index 29ad25f..1ed4536 100644
--- a/tools/aapt2/link/XmlCompatVersioner_test.cpp
+++ b/tools/aapt2/link/XmlCompatVersioner_test.cpp
@@ -54,17 +54,17 @@
.AddSymbolSource(
test::StaticSymbolSourceBuilder()
.AddPublicSymbol("android:attr/paddingLeft", R::attr::paddingLeft,
- util::make_unique<Attribute>(false, TYPE_DIMENSION))
+ util::make_unique<Attribute>(TYPE_DIMENSION))
.AddPublicSymbol("android:attr/paddingRight", R::attr::paddingRight,
- util::make_unique<Attribute>(false, TYPE_DIMENSION))
+ util::make_unique<Attribute>(TYPE_DIMENSION))
.AddPublicSymbol("android:attr/progressBarPadding", R::attr::progressBarPadding,
- util::make_unique<Attribute>(false, TYPE_DIMENSION))
+ util::make_unique<Attribute>(TYPE_DIMENSION))
.AddPublicSymbol("android:attr/paddingStart", R::attr::paddingStart,
- util::make_unique<Attribute>(false, TYPE_DIMENSION))
+ util::make_unique<Attribute>(TYPE_DIMENSION))
.AddPublicSymbol("android:attr/paddingHorizontal", R::attr::paddingHorizontal,
- util::make_unique<Attribute>(false, TYPE_DIMENSION))
+ util::make_unique<Attribute>(TYPE_DIMENSION))
.AddSymbol("com.app:attr/foo", ResourceId(0x7f010000),
- util::make_unique<Attribute>(false, TYPE_STRING))
+ util::make_unique<Attribute>(TYPE_STRING))
.Build())
.Build();
}
@@ -126,9 +126,8 @@
XmlCompatVersioner::Rules rules;
rules[R::attr::paddingHorizontal] =
util::make_unique<DegradeToManyRule>(std::vector<ReplacementAttr>(
- {ReplacementAttr{"paddingLeft", R::attr::paddingLeft, Attribute(false, TYPE_DIMENSION)},
- ReplacementAttr{"paddingRight", R::attr::paddingRight,
- Attribute(false, TYPE_DIMENSION)}}));
+ {ReplacementAttr{"paddingLeft", R::attr::paddingLeft, Attribute(TYPE_DIMENSION)},
+ ReplacementAttr{"paddingRight", R::attr::paddingRight, Attribute(TYPE_DIMENSION)}}));
const util::Range<ApiVersion> api_range{SDK_GINGERBREAD, SDK_O + 1};
@@ -187,12 +186,11 @@
XmlCompatVersioner::Rules rules;
rules[R::attr::progressBarPadding] =
util::make_unique<DegradeToManyRule>(std::vector<ReplacementAttr>(
- {ReplacementAttr{"paddingLeft", R::attr::paddingLeft, Attribute(false, TYPE_DIMENSION)},
- ReplacementAttr{"paddingRight", R::attr::paddingRight,
- Attribute(false, TYPE_DIMENSION)}}));
+ {ReplacementAttr{"paddingLeft", R::attr::paddingLeft, Attribute(TYPE_DIMENSION)},
+ ReplacementAttr{"paddingRight", R::attr::paddingRight, Attribute(TYPE_DIMENSION)}}));
rules[R::attr::paddingHorizontal] =
util::make_unique<DegradeToManyRule>(std::vector<ReplacementAttr>({ReplacementAttr{
- "progressBarPadding", R::attr::progressBarPadding, Attribute(false, TYPE_DIMENSION)}}));
+ "progressBarPadding", R::attr::progressBarPadding, Attribute(TYPE_DIMENSION)}}));
const util::Range<ApiVersion> api_range{SDK_GINGERBREAD, SDK_O + 1};
@@ -267,9 +265,8 @@
XmlCompatVersioner::Rules rules;
rules[R::attr::paddingHorizontal] =
util::make_unique<DegradeToManyRule>(std::vector<ReplacementAttr>(
- {ReplacementAttr{"paddingLeft", R::attr::paddingLeft, Attribute(false, TYPE_DIMENSION)},
- ReplacementAttr{"paddingRight", R::attr::paddingRight,
- Attribute(false, TYPE_DIMENSION)}}));
+ {ReplacementAttr{"paddingLeft", R::attr::paddingLeft, Attribute(TYPE_DIMENSION)},
+ ReplacementAttr{"paddingRight", R::attr::paddingRight, Attribute(TYPE_DIMENSION)}}));
const util::Range<ApiVersion> api_range{SDK_GINGERBREAD, SDK_O + 1};
diff --git a/tools/aapt2/link/XmlReferenceLinker.cpp b/tools/aapt2/link/XmlReferenceLinker.cpp
index 8852c8e..160ff92 100644
--- a/tools/aapt2/link/XmlReferenceLinker.cpp
+++ b/tools/aapt2/link/XmlReferenceLinker.cpp
@@ -79,16 +79,15 @@
void Visit(xml::Element* el) override {
// The default Attribute allows everything except enums or flags.
- constexpr const static uint32_t kDefaultTypeMask =
- 0xffffffffu & ~(android::ResTable_map::TYPE_ENUM | android::ResTable_map::TYPE_FLAGS);
- const static Attribute kDefaultAttribute(true /* weak */, kDefaultTypeMask);
+ Attribute default_attribute(android::ResTable_map::TYPE_ANY);
+ default_attribute.SetWeak(true);
const Source source = source_.WithLine(el->line_number);
for (xml::Attribute& attr : el->attributes) {
// If the attribute has no namespace, interpret values as if
// they were assigned to the default Attribute.
- const Attribute* attribute = &kDefaultAttribute;
+ const Attribute* attribute = &default_attribute;
if (Maybe<xml::ExtractedPackage> maybe_package =
xml::ExtractPackageFromNamespace(attr.namespace_uri)) {
diff --git a/tools/aapt2/process/SymbolTable.cpp b/tools/aapt2/process/SymbolTable.cpp
index 3cae0e8..2e97a2f 100644
--- a/tools/aapt2/process/SymbolTable.cpp
+++ b/tools/aapt2/process/SymbolTable.cpp
@@ -243,7 +243,7 @@
// Check to see if it is an attribute.
for (size_t i = 0; i < (size_t)count; i++) {
if (entry[i].map.name.ident == android::ResTable_map::ATTR_TYPE) {
- s->attribute = std::make_shared<Attribute>(false, entry[i].map.value.data);
+ s->attribute = std::make_shared<Attribute>(entry[i].map.value.data);
break;
}
}
diff --git a/tools/aapt2/test/Builders.cpp b/tools/aapt2/test/Builders.cpp
index 495a48a..c4eab12 100644
--- a/tools/aapt2/test/Builders.cpp
+++ b/tools/aapt2/test/Builders.cpp
@@ -156,8 +156,8 @@
return util::make_unique<BinaryPrimitive>(value);
}
-AttributeBuilder::AttributeBuilder(bool weak) : attr_(util::make_unique<Attribute>(weak)) {
- attr_->type_mask = android::ResTable_map::TYPE_ANY;
+AttributeBuilder::AttributeBuilder()
+ : attr_(util::make_unique<Attribute>(android::ResTable_map::TYPE_ANY)) {
}
AttributeBuilder& AttributeBuilder::SetTypeMask(uint32_t typeMask) {
@@ -165,6 +165,11 @@
return *this;
}
+AttributeBuilder& AttributeBuilder::SetWeak(bool weak) {
+ attr_->SetWeak(weak);
+ return *this;
+}
+
AttributeBuilder& AttributeBuilder::AddItem(const StringPiece& name, uint32_t value) {
attr_->symbols.push_back(
Attribute::Symbol{Reference(ResourceName({}, ResourceType::kId, name)), value});
diff --git a/tools/aapt2/test/Builders.h b/tools/aapt2/test/Builders.h
index 0d7451b..fd5262a 100644
--- a/tools/aapt2/test/Builders.h
+++ b/tools/aapt2/test/Builders.h
@@ -113,8 +113,9 @@
class AttributeBuilder {
public:
- explicit AttributeBuilder(bool weak = false);
+ AttributeBuilder();
AttributeBuilder& SetTypeMask(uint32_t typeMask);
+ AttributeBuilder& SetWeak(bool weak);
AttributeBuilder& AddItem(const android::StringPiece& name, uint32_t value);
std::unique_ptr<Attribute> Build();
diff --git a/tools/apilint/apilint.py b/tools/apilint/apilint.py
index 421e545..399b0c6 100644
--- a/tools/apilint/apilint.py
+++ b/tools/apilint/apilint.py
@@ -100,9 +100,11 @@
self.typ = raw[0]
self.name = raw[1]
self.args = []
+ self.throws = []
+ target = self.args
for r in raw[2:]:
- if r == "throws": break
- self.args.append(r)
+ if r == "throws": target = self.throws
+ else: target.append(r)
# identity for compat purposes
ident = self.raw
@@ -391,7 +393,7 @@
prefix = clazz.pkg.name + ".action"
expected = prefix + "." + f.name[7:]
if f.value != expected:
- error(clazz, f, "C4", "Inconsistent action value; expected %s" % (expected))
+ error(clazz, f, "C4", "Inconsistent action value; expected '%s'" % (expected))
def verify_extras(clazz):
@@ -421,7 +423,7 @@
prefix = clazz.pkg.name + ".extra"
expected = prefix + "." + f.name[6:]
if f.value != expected:
- error(clazz, f, "C4", "Inconsistent extra value; expected %s" % (expected))
+ error(clazz, f, "C4", "Inconsistent extra value; expected '%s'" % (expected))
def verify_equals(clazz):
@@ -450,6 +452,10 @@
(" final deprecated class " not in clazz.raw)):
error(clazz, None, "FW8", "Parcelable classes must be final")
+ for c in clazz.ctors:
+ if c.args == ["android.os.Parcel"]:
+ error(clazz, c, "FW3", "Parcelable inflation is exposed through CREATOR, not raw constructors")
+
def verify_protected(clazz):
"""Verify that no protected methods or fields are allowed."""
@@ -572,7 +578,7 @@
if f.name == "SERVICE_INTERFACE":
found = True
if f.value != clazz.fullname:
- error(clazz, f, "C4", "Inconsistent interface constant; expected %s" % (clazz.fullname))
+ error(clazz, f, "C4", "Inconsistent interface constant; expected '%s'" % (clazz.fullname))
if "extends android.content.ContentProvider" in clazz.raw:
test_methods = True
@@ -584,7 +590,7 @@
if f.name == "PROVIDER_INTERFACE":
found = True
if f.value != clazz.fullname:
- error(clazz, f, "C4", "Inconsistent interface constant; expected %s" % (clazz.fullname))
+ error(clazz, f, "C4", "Inconsistent interface constant; expected '%s'" % (clazz.fullname))
if "extends android.content.BroadcastReceiver" in clazz.raw:
test_methods = True
@@ -764,15 +770,19 @@
def verify_exception(clazz):
"""Verifies that methods don't throw generic exceptions."""
for m in clazz.methods:
- if "throws java.lang.Exception" in m.raw or "throws java.lang.Throwable" in m.raw or "throws java.lang.Error" in m.raw:
- error(clazz, m, "S1", "Methods must not throw generic exceptions")
+ for t in m.throws:
+ if t in ["java.lang.Exception", "java.lang.Throwable", "java.lang.Error"]:
+ error(clazz, m, "S1", "Methods must not throw generic exceptions")
- if "throws android.os.RemoteException" in m.raw:
- if clazz.name == "android.content.ContentProviderClient": continue
- if clazz.name == "android.os.Binder": continue
- if clazz.name == "android.os.IBinder": continue
+ if t in ["android.os.RemoteException"]:
+ if clazz.name == "android.content.ContentProviderClient": continue
+ if clazz.name == "android.os.Binder": continue
+ if clazz.name == "android.os.IBinder": continue
- error(clazz, m, "FW9", "Methods calling into system server should rethrow RemoteException as RuntimeException")
+ error(clazz, m, "FW9", "Methods calling into system server should rethrow RemoteException as RuntimeException")
+
+ if len(m.args) == 0 and t in ["java.lang.IllegalArgumentException", "java.lang.NullPointerException"]:
+ warn(clazz, m, "S1", "Methods taking no arguments should throw IllegalStateException")
def verify_google(clazz):
@@ -927,7 +937,8 @@
found = {}
by_name = collections.defaultdict(list)
- for m in clazz.methods:
+ examine = clazz.ctors + clazz.methods
+ for m in examine:
if m.name.startswith("unregister"): continue
if m.name.startswith("remove"): continue
if re.match("on[A-Z]+", m.name): continue
@@ -971,7 +982,7 @@
for a in m.args:
if a.endswith("Callback") or a.endswith("Callbacks") or a.endswith("Listener"):
found = True
- elif found and a != "android.os.Handler" and a != "java.util.concurrent.Executor":
+ elif found:
warn(clazz, m, "M3", "Listeners should always be at end of argument list")
@@ -1078,16 +1089,11 @@
"java.nio.BufferOverflowException",
]
- test = []
- test.extend(clazz.ctors)
- test.extend(clazz.methods)
-
- for t in test:
- if " throws " not in t.raw: continue
- throws = t.raw[t.raw.index(" throws "):]
- for b in banned:
- if b in throws:
- error(clazz, t, None, "Methods must not mention RuntimeException subclasses in throws clauses")
+ examine = clazz.ctors + clazz.methods
+ for m in examine:
+ for t in m.throws:
+ if t in banned:
+ error(clazz, m, None, "Methods must not mention RuntimeException subclasses in throws clauses")
def verify_error(clazz):
@@ -1233,6 +1239,58 @@
warn(clazz, m, None, "Method argument should be Collection<> (or subclass) instead of raw array")
+def verify_user_handle(clazz):
+ """Methods taking UserHandle should be ForUser or AsUser."""
+ if clazz.name.endswith("Listener") or clazz.name.endswith("Callback") or clazz.name.endswith("Callbacks"): return
+ if clazz.fullname == "android.app.admin.DeviceAdminReceiver": return
+ if clazz.fullname == "android.content.pm.LauncherApps": return
+ if clazz.fullname == "android.os.UserHandle": return
+ if clazz.fullname == "android.os.UserManager": return
+
+ for m in clazz.methods:
+ if m.name.endswith("AsUser") or m.name.endswith("ForUser"): continue
+ if re.match("on[A-Z]+", m.name): continue
+ if "android.os.UserHandle" in m.args:
+ warn(clazz, m, None, "Method taking UserHandle should be named 'doFooAsUser' or 'queryFooForUser'")
+
+
+def verify_params(clazz):
+ """Parameter classes should be 'Params'."""
+ if clazz.name.endswith("Params"): return
+ if clazz.fullname == "android.app.ActivityOptions": return
+ if clazz.fullname == "android.app.BroadcastOptions": return
+ if clazz.fullname == "android.os.Bundle": return
+ if clazz.fullname == "android.os.BaseBundle": return
+ if clazz.fullname == "android.os.PersistableBundle": return
+
+ bad = ["Param","Parameter","Parameters","Args","Arg","Argument","Arguments","Options","Bundle"]
+ for b in bad:
+ if clazz.name.endswith(b):
+ error(clazz, None, None, "Classes holding a set of parameters should be called 'FooParams'")
+
+
+def verify_services(clazz):
+ """Service name should be FOO_BAR_SERVICE = 'foo_bar'."""
+ if clazz.fullname != "android.content.Context": return
+
+ for f in clazz.fields:
+ if f.typ != "java.lang.String": continue
+ found = re.match(r"([A-Z_]+)_SERVICE", f.name)
+ if found:
+ expected = found.group(1).lower()
+ if f.value != expected:
+ error(clazz, f, "C4", "Inconsistent service value; expected '%s'" % (expected))
+
+
+def verify_tense(clazz):
+ """Verify tenses of method names."""
+ if clazz.fullname.startswith("android.opengl"): return
+
+ for m in clazz.methods:
+ if m.name.endswith("Enable"):
+ warn(clazz, m, None, "Unexpected tense; probably meant 'enabled'")
+
+
def examine_clazz(clazz):
"""Find all style issues in the given class."""
@@ -1290,6 +1348,10 @@
verify_member_name_not_kotlin_keyword(clazz)
verify_method_name_not_kotlin_operator(clazz)
verify_collections_over_arrays(clazz)
+ verify_user_handle(clazz)
+ verify_params(clazz)
+ verify_services(clazz)
+ verify_tense(clazz)
def examine_stream(stream):
diff --git a/tools/stats_log_api_gen/Collation.cpp b/tools/stats_log_api_gen/Collation.cpp
index 80853b1..0e57f7f 100644
--- a/tools/stats_log_api_gen/Collation.cpp
+++ b/tools/stats_log_api_gen/Collation.cpp
@@ -15,6 +15,7 @@
*/
#include "Collation.h"
+#include "frameworks/base/cmds/statsd/src/atoms.pb.h"
#include <stdio.h>
#include <map>
@@ -137,6 +138,16 @@
}
/**
+ * Gather the enums info.
+ */
+void collate_enums(const EnumDescriptor &enumDescriptor, AtomField *atomField) {
+ for (int i = 0; i < enumDescriptor.value_count(); i++) {
+ atomField->enumValues[enumDescriptor.value(i)->number()] =
+ enumDescriptor.value(i)->name().c_str();
+ }
+}
+
+/**
* Gather the info about an atom proto.
*/
int collate_atom(const Descriptor *atom, AtomDecl *atomDecl,
@@ -221,11 +232,7 @@
if (javaType == JAVA_TYPE_ENUM) {
// All enums are treated as ints when it comes to function signatures.
signature->push_back(JAVA_TYPE_INT);
- const EnumDescriptor *enumDescriptor = field->enum_type();
- for (int i = 0; i < enumDescriptor->value_count(); i++) {
- atField.enumValues[enumDescriptor->value(i)->number()] =
- enumDescriptor->value(i)->name().c_str();
- }
+ collate_enums(*field->enum_type(), &atField);
} else {
signature->push_back(javaType);
}
@@ -235,6 +242,53 @@
return errorCount;
}
+// This function flattens the fields of the AttributionNode proto in an Atom proto and generates
+// the corresponding atom decl and signature.
+bool get_non_chained_node(const Descriptor *atom, AtomDecl *atomDecl,
+ vector<java_type_t> *signature) {
+ // Build a sorted list of the fields. Descriptor has them in source file
+ // order.
+ map<int, const FieldDescriptor *> fields;
+ for (int j = 0; j < atom->field_count(); j++) {
+ const FieldDescriptor *field = atom->field(j);
+ fields[field->number()] = field;
+ }
+
+ AtomDecl attributionDecl;
+ vector<java_type_t> attributionSignature;
+ collate_atom(android::os::statsd::AttributionNode::descriptor(),
+ &attributionDecl, &attributionSignature);
+
+ // Build the type signature and the atom data.
+ bool has_attribution_node = false;
+ for (map<int, const FieldDescriptor *>::const_iterator it = fields.begin();
+ it != fields.end(); it++) {
+ const FieldDescriptor *field = it->second;
+ java_type_t javaType = java_type(field);
+ if (javaType == JAVA_TYPE_ATTRIBUTION_CHAIN) {
+ atomDecl->fields.insert(
+ atomDecl->fields.end(),
+ attributionDecl.fields.begin(), attributionDecl.fields.end());
+ signature->insert(
+ signature->end(),
+ attributionSignature.begin(), attributionSignature.end());
+ has_attribution_node = true;
+
+ } else {
+ AtomField atField(field->name(), javaType);
+ if (javaType == JAVA_TYPE_ENUM) {
+ // All enums are treated as ints when it comes to function signatures.
+ signature->push_back(JAVA_TYPE_INT);
+ collate_enums(*field->enum_type(), &atField);
+ } else {
+ signature->push_back(javaType);
+ }
+ atomDecl->fields.push_back(atField);
+ }
+ }
+ return has_attribution_node;
+}
+
/**
* Gather the info about the atoms.
*/
@@ -266,6 +320,13 @@
errorCount += collate_atom(atom, &atomDecl, &signature);
atoms->signatures.insert(signature);
atoms->decls.insert(atomDecl);
+
+ AtomDecl nonChainedAtomDecl(atomField->number(), atomField->name(), atom->name());
+ vector<java_type_t> nonChainedSignature;
+ if (get_non_chained_node(atom, &nonChainedAtomDecl, &nonChainedSignature)) {
+ atoms->non_chained_signatures.insert(nonChainedSignature);
+ atoms->non_chained_decls.insert(nonChainedAtomDecl);
+ }
}
if (dbg) {
diff --git a/tools/stats_log_api_gen/Collation.h b/tools/stats_log_api_gen/Collation.h
index cd0625c..0455eca 100644
--- a/tools/stats_log_api_gen/Collation.h
+++ b/tools/stats_log_api_gen/Collation.h
@@ -32,6 +32,7 @@
using std::string;
using std::vector;
using google::protobuf::Descriptor;
+using google::protobuf::FieldDescriptor;
/**
* The types for atom parameters.
@@ -93,14 +94,15 @@
struct Atoms {
set<vector<java_type_t>> signatures;
set<AtomDecl> decls;
+ set<AtomDecl> non_chained_decls;
+ set<vector<java_type_t>> non_chained_signatures;
};
/**
* Gather the information about the atoms. Returns the number of errors.
*/
int collate_atoms(const Descriptor* descriptor, Atoms* atoms);
-int collate_atom(const Descriptor *atom, AtomDecl *atomDecl,
- vector<java_type_t> *signature);
+int collate_atom(const Descriptor *atom, AtomDecl *atomDecl, vector<java_type_t> *signature);
} // namespace stats_log_api_gen
} // namespace android
diff --git a/tools/stats_log_api_gen/main.cpp b/tools/stats_log_api_gen/main.cpp
index bbe6d63..e0e6b58 100644
--- a/tools/stats_log_api_gen/main.cpp
+++ b/tools/stats_log_api_gen/main.cpp
@@ -195,6 +195,47 @@
fprintf(out, "\n");
}
+ for (set<vector<java_type_t>>::const_iterator signature = atoms.non_chained_signatures.begin();
+ signature != atoms.non_chained_signatures.end(); signature++) {
+ int argIndex;
+
+ fprintf(out, "void\n");
+ fprintf(out, "stats_write_non_chained(int32_t code");
+ argIndex = 1;
+ for (vector<java_type_t>::const_iterator arg = signature->begin();
+ arg != signature->end(); arg++) {
+ fprintf(out, ", %s arg%d", cpp_type_name(*arg), argIndex);
+ argIndex++;
+ }
+ fprintf(out, ")\n");
+
+ fprintf(out, "{\n");
+ argIndex = 1;
+ fprintf(out, " android_log_event_list event(kStatsEventTag);\n");
+ fprintf(out, " event << code;\n\n");
+ for (vector<java_type_t>::const_iterator arg = signature->begin();
+ arg != signature->end(); arg++) {
+ if (argIndex == 1) {
+ fprintf(out, " event.begin();\n\n");
+ fprintf(out, " event.begin();\n");
+ }
+ if (*arg == JAVA_TYPE_STRING) {
+ fprintf(out, " if (arg%d == NULL) {\n", argIndex);
+ fprintf(out, " arg%d = \"\";\n", argIndex);
+ fprintf(out, " }\n");
+ }
+ fprintf(out, " event << arg%d;\n", argIndex);
+ if (argIndex == 2) {
+ fprintf(out, " event.end();\n\n");
+ fprintf(out, " event.end();\n\n");
+ }
+ argIndex++;
+ }
+
+ fprintf(out, " event.write(LOG_ID_STATS);\n");
+ fprintf(out, "}\n");
+ fprintf(out, "\n");
+ }
// Print footer
fprintf(out, "\n");
fprintf(out, "} // namespace util\n");
@@ -203,6 +244,68 @@
return 0;
}
+void build_non_chained_decl_map(const Atoms& atoms,
+ std::map<int, set<AtomDecl>::const_iterator>* decl_map){
+ for (set<AtomDecl>::const_iterator atom = atoms.non_chained_decls.begin();
+ atom != atoms.non_chained_decls.end(); atom++) {
+ decl_map->insert(std::make_pair(atom->code, atom));
+ }
+}
+
+static void write_cpp_usage(
+ FILE* out, const string& method_name, const string& atom_code_name,
+ const AtomDecl& atom, const AtomDecl &attributionDecl) {
+ fprintf(out, " * Usage: %s(StatsLog.%s", method_name.c_str(), atom_code_name.c_str());
+ for (vector<AtomField>::const_iterator field = atom.fields.begin();
+ field != atom.fields.end(); field++) {
+ if (field->javaType == JAVA_TYPE_ATTRIBUTION_CHAIN) {
+ for (auto chainField : attributionDecl.fields) {
+ if (chainField.javaType == JAVA_TYPE_STRING) {
+ fprintf(out, ", const std::vector<%s>& %s",
+ cpp_type_name(chainField.javaType),
+ chainField.name.c_str());
+ } else {
+ fprintf(out, ", const %s* %s, size_t %s_length",
+ cpp_type_name(chainField.javaType),
+ chainField.name.c_str(), chainField.name.c_str());
+ }
+ }
+ } else {
+ fprintf(out, ", %s %s", cpp_type_name(field->javaType), field->name.c_str());
+ }
+ }
+ fprintf(out, ");\n");
+}
+
+static void write_cpp_method_header(
+ FILE* out, const string& method_name, const set<vector<java_type_t>>& signatures,
+ const AtomDecl &attributionDecl) {
+ for (set<vector<java_type_t>>::const_iterator signature = signatures.begin();
+ signature != signatures.end(); signature++) {
+ fprintf(out, "void %s(int32_t code ", method_name.c_str());
+ int argIndex = 1;
+ for (vector<java_type_t>::const_iterator arg = signature->begin();
+ arg != signature->end(); arg++) {
+ if (*arg == JAVA_TYPE_ATTRIBUTION_CHAIN) {
+ for (auto chainField : attributionDecl.fields) {
+ if (chainField.javaType == JAVA_TYPE_STRING) {
+ fprintf(out, ", const std::vector<%s>& %s",
+ cpp_type_name(chainField.javaType), chainField.name.c_str());
+ } else {
+ fprintf(out, ", const %s* %s, size_t %s_length",
+ cpp_type_name(chainField.javaType),
+ chainField.name.c_str(), chainField.name.c_str());
+ }
+ }
+ } else {
+ fprintf(out, ", %s arg%d", cpp_type_name(*arg), argIndex);
+ }
+ argIndex++;
+ }
+ fprintf(out, ");\n");
+
+ }
+}
static int
write_stats_log_header(FILE* out, const Atoms& atoms, const AtomDecl &attributionDecl)
@@ -228,6 +331,9 @@
fprintf(out, " */\n");
fprintf(out, "enum {\n");
+ std::map<int, set<AtomDecl>::const_iterator> atom_code_to_non_chained_decl_map;
+ build_non_chained_decl_map(atoms, &atom_code_to_non_chained_decl_map);
+
size_t i = 0;
// Print constants
for (set<AtomDecl>::const_iterator atom = atoms.decls.begin();
@@ -236,26 +342,13 @@
fprintf(out, "\n");
fprintf(out, " /**\n");
fprintf(out, " * %s %s\n", atom->message.c_str(), atom->name.c_str());
- fprintf(out, " * Usage: stats_write(StatsLog.%s", constant.c_str());
- for (vector<AtomField>::const_iterator field = atom->fields.begin();
- field != atom->fields.end(); field++) {
- if (field->javaType == JAVA_TYPE_ATTRIBUTION_CHAIN) {
- for (auto chainField : attributionDecl.fields) {
- if (chainField.javaType == JAVA_TYPE_STRING) {
- fprintf(out, ", const std::vector<%s>& %s",
- cpp_type_name(chainField.javaType),
- chainField.name.c_str());
- } else {
- fprintf(out, ", const %s* %s, size_t %s_length",
- cpp_type_name(chainField.javaType),
- chainField.name.c_str(), chainField.name.c_str());
- }
- }
- } else {
- fprintf(out, ", %s %s", cpp_type_name(field->javaType), field->name.c_str());
- }
+ write_cpp_usage(out, "stats_write", constant, *atom, attributionDecl);
+
+ auto non_chained_decl = atom_code_to_non_chained_decl_map.find(atom->code);
+ if (non_chained_decl != atom_code_to_non_chained_decl_map.end()) {
+ write_cpp_usage(out, "stats_write_non_chained", constant, *non_chained_decl->second,
+ attributionDecl);
}
- fprintf(out, ");\n");
fprintf(out, " */\n");
char const* const comma = (i == atoms.decls.size() - 1) ? "" : ",";
fprintf(out, " %s = %d%s\n", constant.c_str(), atom->code, comma);
@@ -274,30 +367,13 @@
fprintf(out, "//\n");
fprintf(out, "// Write methods\n");
fprintf(out, "//\n");
- for (set<vector<java_type_t>>::const_iterator signature = atoms.signatures.begin();
- signature != atoms.signatures.end(); signature++) {
- fprintf(out, "void stats_write(int32_t code ");
- int argIndex = 1;
- for (vector<java_type_t>::const_iterator arg = signature->begin();
- arg != signature->end(); arg++) {
- if (*arg == JAVA_TYPE_ATTRIBUTION_CHAIN) {
- for (auto chainField : attributionDecl.fields) {
- if (chainField.javaType == JAVA_TYPE_STRING) {
- fprintf(out, ", const std::vector<%s>& %s",
- cpp_type_name(chainField.javaType), chainField.name.c_str());
- } else {
- fprintf(out, ", const %s* %s, size_t %s_length",
- cpp_type_name(chainField.javaType),
- chainField.name.c_str(), chainField.name.c_str());
- }
- }
- } else {
- fprintf(out, ", %s arg%d", cpp_type_name(*arg), argIndex);
- }
- argIndex++;
- }
- fprintf(out, ");\n");
- }
+ write_cpp_method_header(out, "stats_write", atoms.signatures, attributionDecl);
+
+ fprintf(out, "//\n");
+ fprintf(out, "// Write flattened methods\n");
+ fprintf(out, "//\n");
+ write_cpp_method_header(out, "stats_write_non_chained", atoms.non_chained_signatures,
+ attributionDecl);
fprintf(out, "\n");
fprintf(out, "} // namespace util\n");
@@ -306,6 +382,49 @@
return 0;
}
+static void write_java_usage(
+ FILE* out, const string& method_name, const string& atom_code_name,
+ const AtomDecl& atom, const AtomDecl &attributionDecl) {
+ fprintf(out, " * Usage: StatsLog.%s(StatsLog.%s",
+ method_name.c_str(), atom_code_name.c_str());
+ for (vector<AtomField>::const_iterator field = atom.fields.begin();
+ field != atom.fields.end(); field++) {
+ if (field->javaType == JAVA_TYPE_ATTRIBUTION_CHAIN) {
+ for (auto chainField : attributionDecl.fields) {
+ fprintf(out, ", %s[] %s",
+ java_type_name(chainField.javaType), chainField.name.c_str());
+ }
+ } else {
+ fprintf(out, ", %s %s", java_type_name(field->javaType), field->name.c_str());
+ }
+ }
+ fprintf(out, ");\n");
+}
+
+static void write_java_method(
+ FILE* out, const string& method_name, const set<vector<java_type_t>>& signatures,
+ const AtomDecl &attributionDecl) {
+ for (set<vector<java_type_t>>::const_iterator signature = signatures.begin();
+ signature != signatures.end(); signature++) {
+ fprintf(out, " public static native void %s(int code", method_name.c_str());
+ int argIndex = 1;
+ for (vector<java_type_t>::const_iterator arg = signature->begin();
+ arg != signature->end(); arg++) {
+ if (*arg == JAVA_TYPE_ATTRIBUTION_CHAIN) {
+ for (auto chainField : attributionDecl.fields) {
+ fprintf(out, ", %s[] %s",
+ java_type_name(chainField.javaType), chainField.name.c_str());
+ }
+ } else {
+ fprintf(out, ", %s arg%d", java_type_name(*arg), argIndex);
+ }
+ argIndex++;
+ }
+ fprintf(out, ");\n");
+ }
+}
+
+
static int
write_stats_log_java(FILE* out, const Atoms& atoms, const AtomDecl &attributionDecl)
{
@@ -322,6 +441,9 @@
fprintf(out, "public class StatsLogInternal {\n");
fprintf(out, " // Constants for atom codes.\n");
+ std::map<int, set<AtomDecl>::const_iterator> atom_code_to_non_chained_decl_map;
+ build_non_chained_decl_map(atoms, &atom_code_to_non_chained_decl_map);
+
// Print constants for the atom codes.
for (set<AtomDecl>::const_iterator atom = atoms.decls.begin();
atom != atoms.decls.end(); atom++) {
@@ -329,19 +451,12 @@
fprintf(out, "\n");
fprintf(out, " /**\n");
fprintf(out, " * %s %s\n", atom->message.c_str(), atom->name.c_str());
- fprintf(out, " * Usage: StatsLog.write(StatsLog.%s", constant.c_str());
- for (vector<AtomField>::const_iterator field = atom->fields.begin();
- field != atom->fields.end(); field++) {
- if (field->javaType == JAVA_TYPE_ATTRIBUTION_CHAIN) {
- for (auto chainField : attributionDecl.fields) {
- fprintf(out, ", %s[] %s",
- java_type_name(chainField.javaType), chainField.name.c_str());
- }
- } else {
- fprintf(out, ", %s %s", java_type_name(field->javaType), field->name.c_str());
- }
+ write_java_usage(out, "write", constant, *atom, attributionDecl);
+ auto non_chained_decl = atom_code_to_non_chained_decl_map.find(atom->code);
+ if (non_chained_decl != atom_code_to_non_chained_decl_map.end()) {
+ write_java_usage(out, "write_non_chained", constant, *non_chained_decl->second,
+ attributionDecl);
}
- fprintf(out, ");\n");
fprintf(out, " */\n");
fprintf(out, " public static final int %s = %d;\n", constant.c_str(), atom->code);
}
@@ -371,24 +486,8 @@
// Print write methods
fprintf(out, " // Write methods\n");
- for (set<vector<java_type_t>>::const_iterator signature = atoms.signatures.begin();
- signature != atoms.signatures.end(); signature++) {
- fprintf(out, " public static native void write(int code");
- int argIndex = 1;
- for (vector<java_type_t>::const_iterator arg = signature->begin();
- arg != signature->end(); arg++) {
- if (*arg == JAVA_TYPE_ATTRIBUTION_CHAIN) {
- for (auto chainField : attributionDecl.fields) {
- fprintf(out, ", %s[] %s",
- java_type_name(chainField.javaType), chainField.name.c_str());
- }
- } else {
- fprintf(out, ", %s arg%d", java_type_name(*arg), argIndex);
- }
- argIndex++;
- }
- fprintf(out, ");\n");
- }
+ write_java_method(out, "write", atoms.signatures, attributionDecl);
+ write_java_method(out, "write_non_chained", atoms.non_chained_signatures, attributionDecl);
fprintf(out, "}\n");
@@ -431,9 +530,9 @@
}
static string
-jni_function_name(const vector<java_type_t>& signature)
+jni_function_name(const string& method_name, const vector<java_type_t>& signature)
{
- string result("StatsLog_write");
+ string result("StatsLog_" + method_name);
for (vector<java_type_t>::const_iterator arg = signature.begin();
arg != signature.end(); arg++) {
switch (*arg) {
@@ -509,34 +608,17 @@
}
static int
-write_stats_log_jni(FILE* out, const Atoms& atoms, const AtomDecl &attributionDecl)
+write_stats_log_jni(FILE* out, const string& java_method_name, const string& cpp_method_name,
+ const set<vector<java_type_t>>& signatures, const AtomDecl &attributionDecl)
{
- // Print prelude
- fprintf(out, "// This file is autogenerated\n");
- fprintf(out, "\n");
-
- fprintf(out, "#include <statslog.h>\n");
- fprintf(out, "\n");
- fprintf(out, "#include <nativehelper/JNIHelp.h>\n");
- fprintf(out, "#include <nativehelper/ScopedUtfChars.h>\n");
- fprintf(out, "#include <utils/Vector.h>\n");
- fprintf(out, "#include \"core_jni_helpers.h\"\n");
- fprintf(out, "#include \"jni.h\"\n");
- fprintf(out, "\n");
- fprintf(out, "#define UNUSED __attribute__((__unused__))\n");
- fprintf(out, "\n");
-
- fprintf(out, "namespace android {\n");
- fprintf(out, "\n");
-
// Print write methods
- for (set<vector<java_type_t>>::const_iterator signature = atoms.signatures.begin();
- signature != atoms.signatures.end(); signature++) {
+ for (set<vector<java_type_t>>::const_iterator signature = signatures.begin();
+ signature != signatures.end(); signature++) {
int argIndex;
fprintf(out, "static void\n");
fprintf(out, "%s(JNIEnv* env, jobject clazz UNUSED, jint code",
- jni_function_name(*signature).c_str());
+ jni_function_name(java_method_name, *signature).c_str());
argIndex = 1;
for (vector<java_type_t>::const_iterator arg = signature->begin();
arg != signature->end(); arg++) {
@@ -624,7 +706,7 @@
// stats_write call
argIndex = 1;
- fprintf(out, " android::util::stats_write(code");
+ fprintf(out, " android::util::%s(code", cpp_method_name.c_str());
for (vector<java_type_t>::const_iterator arg = signature->begin();
arg != signature->end(); arg++) {
if (*arg == JAVA_TYPE_ATTRIBUTION_CHAIN) {
@@ -675,17 +757,53 @@
fprintf(out, "\n");
}
+
+ return 0;
+}
+
+void write_jni_registration(FILE* out, const string& java_method_name,
+ const set<vector<java_type_t>>& signatures, const AtomDecl &attributionDecl) {
+ for (set<vector<java_type_t>>::const_iterator signature = signatures.begin();
+ signature != signatures.end(); signature++) {
+ fprintf(out, " { \"%s\", \"%s\", (void*)%s },\n",
+ java_method_name.c_str(),
+ jni_function_signature(*signature, attributionDecl).c_str(),
+ jni_function_name(java_method_name, *signature).c_str());
+ }
+}
+
+static int
+write_stats_log_jni(FILE* out, const Atoms& atoms, const AtomDecl &attributionDecl)
+{
+ // Print prelude
+ fprintf(out, "// This file is autogenerated\n");
+ fprintf(out, "\n");
+
+ fprintf(out, "#include <statslog.h>\n");
+ fprintf(out, "\n");
+ fprintf(out, "#include <nativehelper/JNIHelp.h>\n");
+ fprintf(out, "#include <nativehelper/ScopedUtfChars.h>\n");
+ fprintf(out, "#include <utils/Vector.h>\n");
+ fprintf(out, "#include \"core_jni_helpers.h\"\n");
+ fprintf(out, "#include \"jni.h\"\n");
+ fprintf(out, "\n");
+ fprintf(out, "#define UNUSED __attribute__((__unused__))\n");
+ fprintf(out, "\n");
+
+ fprintf(out, "namespace android {\n");
+ fprintf(out, "\n");
+
+ write_stats_log_jni(out, "write", "stats_write", atoms.signatures, attributionDecl);
+ write_stats_log_jni(out, "write_non_chained", "stats_write_non_chained",
+ atoms.non_chained_signatures, attributionDecl);
+
// Print registration function table
fprintf(out, "/*\n");
fprintf(out, " * JNI registration.\n");
fprintf(out, " */\n");
fprintf(out, "static const JNINativeMethod gRegisterMethods[] = {\n");
- for (set<vector<java_type_t>>::const_iterator signature = atoms.signatures.begin();
- signature != atoms.signatures.end(); signature++) {
- fprintf(out, " { \"write\", \"%s\", (void*)%s },\n",
- jni_function_signature(*signature, attributionDecl).c_str(),
- jni_function_name(*signature).c_str());
- }
+ write_jni_registration(out, "write", atoms.signatures, attributionDecl);
+ write_jni_registration(out, "write_non_chained", atoms.non_chained_signatures, attributionDecl);
fprintf(out, "};\n");
fprintf(out, "\n");
@@ -699,11 +817,9 @@
fprintf(out, "\n");
fprintf(out, "} // namespace android\n");
-
return 0;
}
-
static void
print_usage()
{
diff --git a/wifi/java/android/net/wifi/IWifiManager.aidl b/wifi/java/android/net/wifi/IWifiManager.aidl
index 0dd964c..70d6ce4 100644
--- a/wifi/java/android/net/wifi/IWifiManager.aidl
+++ b/wifi/java/android/net/wifi/IWifiManager.aidl
@@ -28,7 +28,6 @@
import android.net.wifi.ScanSettings;
import android.net.wifi.ScanResult;
import android.net.wifi.PasspointManagementObjectDefinition;
-import android.net.wifi.WifiConnectionStatistics;
import android.net.wifi.WifiActivityEnergyInfo;
import android.net.Network;
@@ -166,8 +165,6 @@
void enableWifiConnectivityManager(boolean enabled);
- WifiConnectionStatistics getConnectionStatistics();
-
void disableEphemeralNetwork(String SSID);
void factoryReset();
diff --git a/wifi/java/android/net/wifi/WifiConnectionStatistics.aidl b/wifi/java/android/net/wifi/WifiConnectionStatistics.aidl
deleted file mode 100644
index 601face..0000000
--- a/wifi/java/android/net/wifi/WifiConnectionStatistics.aidl
+++ /dev/null
@@ -1,19 +0,0 @@
-/**
- * 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.
- */
-
-package android.net.wifi;
-
-parcelable WifiConnectionStatistics;
diff --git a/wifi/java/android/net/wifi/WifiConnectionStatistics.java b/wifi/java/android/net/wifi/WifiConnectionStatistics.java
deleted file mode 100644
index 1120c66..0000000
--- a/wifi/java/android/net/wifi/WifiConnectionStatistics.java
+++ /dev/null
@@ -1,158 +0,0 @@
-/*
- * 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.
- */
-
-package android.net.wifi;
-
-import android.annotation.SystemApi;
-
-import android.os.Parcel;
-import android.os.Parcelable;
-import android.text.TextUtils;
-
-import java.util.HashMap;
-
-/**
- * Wifi Connection Statistics: gather various stats regarding WiFi connections,
- * connection requests, auto-join
- * and WiFi usage.
- * @hide
- * @removed
- */
-@SystemApi
-public class WifiConnectionStatistics implements Parcelable {
- private static final String TAG = "WifiConnnectionStatistics";
-
- /**
- * history of past connection to untrusted SSID
- * Key = SSID
- * Value = num connection
- */
- public HashMap<String, WifiNetworkConnectionStatistics> untrustedNetworkHistory;
-
- // Number of time we polled the chip and were on 5GHz
- public int num5GhzConnected;
-
- // Number of time we polled the chip and were on 2.4GHz
- public int num24GhzConnected;
-
- // Number autojoin attempts
- public int numAutoJoinAttempt;
-
- // Number auto-roam attempts
- public int numAutoRoamAttempt;
-
- // Number wifimanager join attempts
- public int numWifiManagerJoinAttempt;
-
- public WifiConnectionStatistics() {
- untrustedNetworkHistory = new HashMap<String, WifiNetworkConnectionStatistics>();
- }
-
- public void incrementOrAddUntrusted(String SSID, int connection, int usage) {
- WifiNetworkConnectionStatistics stats;
- if (TextUtils.isEmpty(SSID))
- return;
- if (untrustedNetworkHistory.containsKey(SSID)) {
- stats = untrustedNetworkHistory.get(SSID);
- if (stats != null){
- stats.numConnection = connection + stats.numConnection;
- stats.numUsage = usage + stats.numUsage;
- }
- } else {
- stats = new WifiNetworkConnectionStatistics(connection, usage);
- }
- if (stats != null) {
- untrustedNetworkHistory.put(SSID, stats);
- }
- }
-
- @Override
- public String toString() {
- StringBuilder sbuf = new StringBuilder();
- sbuf.append("Connected on: 2.4Ghz=").append(num24GhzConnected);
- sbuf.append(" 5Ghz=").append(num5GhzConnected).append("\n");
- sbuf.append(" join=").append(numWifiManagerJoinAttempt);
- sbuf.append("\\").append(numAutoJoinAttempt).append("\n");
- sbuf.append(" roam=").append(numAutoRoamAttempt).append("\n");
-
- for (String Key : untrustedNetworkHistory.keySet()) {
- WifiNetworkConnectionStatistics stats = untrustedNetworkHistory.get(Key);
- if (stats != null) {
- sbuf.append(Key).append(" ").append(stats.toString()).append("\n");
- }
- }
- return sbuf.toString();
- }
-
- /** copy constructor*/
- public WifiConnectionStatistics(WifiConnectionStatistics source) {
- untrustedNetworkHistory = new HashMap<String, WifiNetworkConnectionStatistics>();
- if (source != null) {
- untrustedNetworkHistory.putAll(source.untrustedNetworkHistory);
- }
- }
-
- /** Implement the Parcelable interface */
- public int describeContents() {
- return 0;
- }
-
- /** Implement the Parcelable interface */
- @Override
- public void writeToParcel(Parcel dest, int flags) {
- dest.writeInt(num24GhzConnected);
- dest.writeInt(num5GhzConnected);
- dest.writeInt(numAutoJoinAttempt);
- dest.writeInt(numAutoRoamAttempt);
- dest.writeInt(numWifiManagerJoinAttempt);
-
- dest.writeInt(untrustedNetworkHistory.size());
- for (String Key : untrustedNetworkHistory.keySet()) {
- WifiNetworkConnectionStatistics num = untrustedNetworkHistory.get(Key);
- dest.writeString(Key);
- dest.writeInt(num.numConnection);
- dest.writeInt(num.numUsage);
-
- }
- }
-
- /** Implement the Parcelable interface */
- public static final Creator<WifiConnectionStatistics> CREATOR =
- new Creator<WifiConnectionStatistics>() {
- public WifiConnectionStatistics createFromParcel(Parcel in) {
- WifiConnectionStatistics stats = new WifiConnectionStatistics();
- stats.num24GhzConnected = in.readInt();
- stats.num5GhzConnected = in.readInt();
- stats.numAutoJoinAttempt = in.readInt();
- stats.numAutoRoamAttempt = in.readInt();
- stats.numWifiManagerJoinAttempt = in.readInt();
- int n = in.readInt();
- while (n-- > 0) {
- String Key = in.readString();
- int numConnection = in.readInt();
- int numUsage = in.readInt();
- WifiNetworkConnectionStatistics st =
- new WifiNetworkConnectionStatistics(numConnection, numUsage);
- stats.untrustedNetworkHistory.put(Key, st);
- }
- return stats;
- }
-
- public WifiConnectionStatistics[] newArray(int size) {
- return new WifiConnectionStatistics[size];
- }
- };
-}
diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java
index 70e83db..aa75a07 100644
--- a/wifi/java/android/net/wifi/WifiManager.java
+++ b/wifi/java/android/net/wifi/WifiManager.java
@@ -999,20 +999,6 @@
}
/**
- * @hide
- * @removed
- */
- @SystemApi
- @RequiresPermission(android.Manifest.permission.READ_WIFI_CREDENTIAL)
- public WifiConnectionStatistics getConnectionStatistics() {
- try {
- return mService.getConnectionStatistics();
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
- }
-
- /**
* Returns a WifiConfiguration matching this ScanResult
*
* @param scanResult scanResult that represents the BSSID